Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Softlock when nothing is drawn #35

Open
TurtleP opened this issue Jul 13, 2017 · 21 comments
Open

Softlock when nothing is drawn #35

TurtleP opened this issue Jul 13, 2017 · 21 comments
Labels
drawing-nothing User sets up drawing state, then proceeds to draw nothing at all.

Comments

@TurtleP
Copy link

TurtleP commented Jul 13, 2017

Specifically, if I have Citro render the top screen render target, but I don't draw anything, it will go through two cycles before soft locking. Removing this rendertarget stuff in aptMainLoop fixes this (which broke my FPS timer :P)

Emulation on Citra

Apparently the solution is to fool the render targets by applying a completely transparent texture at all times--something I'd rather not do. I've tested both builds on my 3DS and they behave as shown on Citra.

@fincs
Copy link
Member

fincs commented Jul 13, 2017

If you don't draw anything to a target, why do you bind it?

@TurtleP
Copy link
Author

TurtleP commented Jul 13, 2017

Well I'm unsure how to check if the end user will be drawing anything immediately. They could be writing their draw function into the script to set up for later. Add other events like keypressing and it doesn't work because of the softlock. If there's a way to fix this issue that I'm unaware of (if it's on my end), please let me know.

@fincs
Copy link
Member

fincs commented Jul 13, 2017

Why don't you let the end users bind the targets themselves?

@TurtleP
Copy link
Author

TurtleP commented Jul 13, 2017

The reason is because it's meant to be a game framework (i.e. Love Potion), but I'm rewriting it to use Citro rather than sf2dlib. The end users shouldn't need to know how to bind a rendertarget on the Lua side, that's just ugly and makes no sense. /shrug

@fincs
Copy link
Member

fincs commented Jul 13, 2017

It does make sense. Allowing the end user to control rendertargets allows for fun stuff to happen, such as caching a piece of drawing to a texture and using that multiple times when drawing to a screen. IIRC even sf2d did the same thing.

@TurtleP
Copy link
Author

TurtleP commented Jul 13, 2017

Right, I know. The issue, however, is that this is basically Love2D for 3DS. I'm following how it's done through the official code and tweaking where needed (such as font loading and filesystem access). The code looks like this on the Lua script:

function love.load()
	print("Press 'start' to quit")
	
	--print("Loading font!")
	timer = 1
end

function love.update(dt)
	timer = timer - dt
	if timer < 0 then
		print("FPS: " .. love.timer.getFPS())
		timer = 1
	end
end

function love.draw()	
	love.graphics.print("Adrian.. FACE REVEAL WEN?!?!?!", 120, 120)
end

function love.keypressed(key)
	if key == "start" then
		love.quit()
	end
end

The way it works is all I care about is knowing I can draw whenever. It doesn't need to be immediate. If I throw the program on the 3DS or Citra it should run without needing to draw something or having people familiar (or new) with this framework require knowledge about behind-the-scenes stuff.

@fincs
Copy link
Member

fincs commented Jul 13, 2017

You could try lazily starting the frame. Invoke love.draw(), and as soon as that method tries to use any drawing commands, start the frame. If after invoking said method a frame has been started, end it. Otherwise use C3D_FrameSync() to synchronize with the screens & keep it "60fps". You could also use the same idea to throw an error if the user script tries to use drawing commands outside love.draw().

@TurtleP
Copy link
Author

TurtleP commented Jul 13, 2017

Where would I FrameSync()? I thought Citro does that already. I'm also not sure if I can really 'detect' if the user tries to invoke a drawing command. I'll take a poke at it I guess.

This is my rendering code in C++

void Graphics::Render(gfxScreen_t screen)
{
	resetPool();
		
	renderScreen = screen;
	
	C3D_FrameBegin(C3D_FRAME_SYNCDRAW); //SYNC_DRAW

	switch(screen)
	{
		case GFX_TOP:
			this->StartTarget(topTarget);
			break;
		case GFX_BOTTOM:
			this->StartTarget(bottomTarget);
			break;
	}
}

void Graphics::StartTarget(CRenderTarget * target)
{
	if (target->GetTarget() == nullptr)
		return;

	target->Clear(graphicsGetBackgroundColor());
	
	C3D_FrameDrawOn(target->GetTarget());

	C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, projection_desc, target->GetProjection());
}

void Graphics::SwapBuffers()
{
	C3D_FrameEnd(0);
}

All of these methods do get called appropriately.

@fincs
Copy link
Member

fincs commented Jul 13, 2017

citro3d does that automatically, but only if you actually use C3D_FrameBegin/C3D_FrameEnd. Otherwise you're going to have to sync manually. Where do you invoke love.draw()?

@TurtleP
Copy link
Author

TurtleP commented Jul 13, 2017

I invoke it during the aptMainLoop:

while (aptMainLoop())
	{
		if (LOVE_QUIT)
			break;

		if (!LUA_ERROR)
		{
			....

			if(luaL_dostring(L, "if love.update then love.update(love.timer.getDelta()) end"))
				console->ThrowError(L);

			....

			love::Graphics::Instance()->Render(GFX_TOP);

			if (luaL_dostring(L, "if love.draw then love.draw() end"))
				console->ThrowError(L);
			
			/*love::Graphics::Instance()->Render(GFX_BOTTOM);
				
			if (luaL_dostring(L, "if love.draw then love.draw() end"))
				console->ThrowError(L);
			*/
			love::Graphics::Instance()->SwapBuffers();

			love::Timer::Instance()->Tick();
		}
	}

Sorry for the spacing :P

@fincs
Copy link
Member

fincs commented Jul 13, 2017

Ok so you basically need to lazily call love::Graphics::Instance()->Render(GFX_TOP); the first time love.draw issues a drawing command (per frame) - for this you'll probably need a bool flag. SwapBuffers() (which I imagine does the FrameEnd call) should detect whether the frame has actually started - if it has, then end it; if it hasn't, use FrameSync.

@TurtleP
Copy link
Author

TurtleP commented Jul 13, 2017

I think I have the right way to do it, but my issue is most likely detecting if the frame really started. The render for GFX_TOP happens before love.draw executes. This allows for Citro to render images, primitive shapes, etc. Then it ends the frame. I don't have a way to definitely check if the user put in some rendering code.

@fincs
Copy link
Member

fincs commented Jul 13, 2017

No. I meant deferring the Render() call until love.draw actually calls drawing commands. You ought to have those drawing commands implemented in C, right? That's where you need to detect if Render() needs to be called.

(Btw, if it's not clear enough: Using the renderqueue frame commands without actually rendering anything is considered API misuse.)

@TurtleP
Copy link
Author

TurtleP commented Jul 13, 2017

Yeah the draw commands are in C. If I have to flag a Boolean in each one it's kind of hacky, tbh. I'll keep trying I guess. I was hoping there'd be a better fix.

@Clownacy
Copy link

I just had this same problem with my homebrew game, and I think I have something that works.

Before noticing this issue was here, I was looking through the commit log to see if I could find the cause. Along the way, I stumbled upon this commit.

C3D_RenderTargetSetClear is still supported, however it's unofficially
deprecated (it's performed after drawing/transferring instead of before
drawing, which is pretty counter-intuitive)

So, just to keep my code clean, I removed all uses of C3D_RenderTargetSetClear, and instead did it directly as part of my render function:

void Backend_Graphics_DrawFrame(void)
{
	aptMainLoop();		// Do this once per frame so sleeping doesn't crash
	C3D_FrameBegin(C3D_FRAME_SYNCDRAW);

		C3D_FrameDrawOn(render_target_left);
		drawing_right_eye = false;

		C3D_FrameBufClear(&render_target_left->frameBuf, C3D_CLEAR_ALL, CLEAR_COLOR, 0);	// Added line

		DrawObjects();

		C3D_TexBind(0, level_texture);
		DrawLevel();

		if (osGet3DSliderState())
		{
			C3D_FrameDrawOn(render_target_right);
			drawing_right_eye = true;

			C3D_FrameBufClear(&render_target_right->frameBuf, C3D_CLEAR_ALL, CLEAR_COLOR, 0);	// Added line

			DrawObjects();

			C3D_TexBind(0, level_texture);
			DrawLevel();
		}

	C3D_FrameEnd(0);
}

To my surprise, the crashes stopped happening.

@fincs
Copy link
Member

fincs commented Jul 18, 2017

Might as well add that calling C3D_FrameSplit() is necessary before doing any kind of external operation (clear, transfer, etc) to a rendertarget's framebuffer if (and only if) you have previously drawn anything to it in the current frame. In the code posted above this call isn't necessary since the framebuffer is cleared before it is being drawn to.
An alternative way to clear a rendertarget without using C3D_RenderTargetSetClear is just to disable depthtest and draw a plain ol' colored quad over the entire buffer (which is what some games as well as new-hbmenu actually do).

@TurtleP
Copy link
Author

TurtleP commented Jul 18, 2017

So just basically clear the FrameBuffer rather than SetClear?

@endrift
Copy link

endrift commented Aug 27, 2017

I'm being bitten by this right now too, FYI. Not all code is as simple and straightforward as you assume and telling someone "well just refactor it to make it more straightforward" is not exactly a solution.

@TurtleP
Copy link
Author

TurtleP commented Aug 27, 2017

I agree with @endrift since there shouldn't be a reason it softlocks using this function if nothing is there. Regardless, just using FrameBufClear works and doesn't cause a soft lock before drawing, but the color format to clear is different than expected.

@fincs fincs added the drawing-nothing User sets up drawing state, then proceeds to draw nothing at all. label Sep 23, 2017
@Swiftloke
Copy link

Swiftloke commented Nov 17, 2017

Just experienced this issue for myself. Render a frame, draw nothing, GPU crash (no crash handler, just hang). The context was me trying to clear the framebuffer to prevent issues with APT_DoApplicationJump (In which it gets weird and renders your last frame but... without the last thing you drew while jumping). Simply drawing a black rectangle does what I need (clearing the framebuffer would work too), but just keep in mind that more people are going to run in to this.

@namkazt
Copy link

namkazt commented Jan 2, 2018

i stuck on this thing few hours don't know why my hb suddenly stuck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
drawing-nothing User sets up drawing state, then proceeds to draw nothing at all.
Projects
None yet
Development

No branches or pull requests

6 participants