Page 1 of 1

OpenGL events in Love for RenderDoc

Posted: Tue Jan 04, 2022 9:04 am
by Froyok
[EDIT] (See latest messages for an updated code snippet. Keeping the original post as-is for posterity.)

While working on my projects I wanted to use a graphic debugger like RenderDoc to check a few things.
Löve doesn't really create any events by default, so checking the OpenGL api calls in a flat list can be a bit tedious sometimes.

So here is a code snippet that shows how to manually push events that are visible in RenderDoc:

ogl.lua

Code: Select all

local FFI = require( 'ffi' )
local OGL_LIBS = {
	OSX		= { x86 = "OpenGL.framework/OpenGL", x64 = "OpenGL.framework/OpenGL" },
	Windows	= { x86 = "OPENGL32.DLL", x64 = "OPENGL32.DLL" },
	Linux	= { x86 = "libGL.so", x64 = "libGL.so", arm = "libGL.so" },
}
local OGL_LIB = OGL_LIBS[ FFI.os ][ FFI.arch ]

FFI.cdef[[
	typedef char GLchar;
	typedef int GLsizei;
	typedef unsigned int GLuint;
	typedef unsigned int GLenum;

	void glPushDebugGroup( GLenum source, GLuint id, GLsizei length, const GLchar *message );
	void glPopDebugGroup( void );
]]

local OPENGL = {
	GL = FFI.load( OGL_LIB ),

	PushEvent = function( self, Message )
		self.GL.glPushDebugGroup(
			0,
			0,
			string.len( Message ),
			Message
		)
	end,
	
	PopEvent = function( self )
		self.GL.glPopDebugGroup()
	end
}

return OPENGL
main.lua

Code: Select all

local GL = require( "ogl.lua" )

function love.load()
end

function love.update( dt )
end

function love.draw()
	GL:PushEvent( "ParentEvent" )
	
	love.graphics.rectangle( "fill", 10, 50, 60, 120 )
	
	GL:PushEvent( "ChildEvent" )

	love.graphics.rectangle( "fill", 40, 50, 60, 120 )

	GL:PopEvent()
	GL:PopEvent()
end
An exmaple with my game, before:
event_before.png
event_before.png (37.34 KiB) Viewed 7713 times
And after:
event_after.png
event_after.png (38.66 KiB) Viewed 7713 times
---

Bonus, if you want to check/use other OpenGL functions they can be found here: https://www.khronos.org/registry/OpenGL ... lcorearb.h

Re: OpenGL events in Love for RenderDoc

Posted: Tue Jan 04, 2022 1:13 pm
by ReFreezed
This is very helpful. Thanks!

Re: OpenGL events in Love for RenderDoc

Posted: Tue Jan 04, 2022 5:18 pm
by idbrii
That's really cool!

You could even add a wrapper func to make adding markers around functions easier:

Code: Select all

Call = function( self, name, fun, ... )
	    self:PushEvent( name )
	    fn(...)
	    self:PopEvent()
	end


function love.draw()
	GL:Call( "gameplay", mygame.draw, mygame ) -- calls mygame:draw()
	GL:Call( "ui", draw_ui ) -- calls draw_ui()
end
Edit: s/GL/self/c as zorg pointed out I wasn't using self properly.

Re: OpenGL events in Love for RenderDoc

Posted: Tue Jan 04, 2022 5:51 pm
by Froyok
Ha, that's interesting !
I didn't know it was possible do this kind of stuff (I'm not yet super familiar with Lua itself). :)

Re: OpenGL events in Love for RenderDoc

Posted: Thu Jan 06, 2022 12:21 pm
by zorg
idbrii wrote: Tue Jan 04, 2022 5:18 pm That's really cool!

You could even add a wrapper func to make adding markers around functions easier:

Code: Select all

Call = function( self, name, fun, ... )
	    GL:PushEvent( name )
	    fn(...)
	    GL:PopEvent()
	end


function love.draw()
	GL:Call( "gameplay", mygame.draw, mygame ) -- calls mygame:draw()
	GL:Call( "ui", draw_ui ) -- calls draw_ui()
end
A bit of a warning, since you're missing part of the code at the very beginning... i think, anyway:
Assuming the Call function is part of the GL table, you really need to show whether you're using a dot or a colon with it, since in the latter case only, you don't need to have "self" be an explicit parameter (it'll implicitly be available as the first passed parameter from a call)
... then again, the inside of the function should also use self instead of GL too, otherwise, why make it use colon in the first place? :3

Code: Select all

GL.Call = function(self, name, fun, ...) -- since it's explicit, you don't really need to call it self, only if you want to.
  self:PushEvent(name)
  fn(...)
  self:PopEvent()
end

--[[
function GL:Call(name, fun, ...) -- this works too, since it's functionally equivalent to the above.
  self:PushEvent(name)
  fn(...)
  self:PopEvent()
end
--]]

function love.draw()
	GL:Call("gameplay", mygame.draw, mygame ) -- calls mygame:draw() -> which is functionally equivalent to mygame.draw(mygame)
	GL:Call("ui", draw_ui ) -- calls draw_ui()
end

Re: OpenGL events in Love for RenderDoc

Posted: Fri Jan 07, 2022 6:02 am
by idbrii
Oh yeah, that was meant to be another entry in the table.

Shouldn't that latter example (the commented out one) be:

Code: Select all

function GL:Call(name, fun, ...) -- this works too, since it's functionally equivalent to the above.
    self:PushEvent(name)
    fn(...)
    self:PopEvent()
end
GL:Call = function(name, fun, ...) isn't valid lua (in love's 5.1 at least!).

Re: OpenGL events in Love for RenderDoc

Posted: Fri Jan 07, 2022 9:16 am
by zorg
idbrii wrote: Fri Jan 07, 2022 6:02 am Shouldn't that latter example (the commented out one) be:
<code>
GL:Call = function(name, fun, ...) isn't valid lua (in love's 5.1 at least!).
Yep, it should! Edited my post, thanks.

Re: OpenGL events in Love for RenderDoc

Posted: Mon Jan 31, 2022 12:41 pm
by Froyok
Little update: I noticed that Love sometimes doesn't immediately sends the draw command. So calling PopEvent() might lead to incorrect results in RenderDoc because the actual draw in the code happens after the end of the event.
This can be solved by calling love.graphics.flushBatch() before the PopEvent() call. To be used with care to avoid affecting your performances. :)

Re: OpenGL events in Love for RenderDoc

Posted: Thu Jan 12, 2023 1:26 am
by Froyok
Time has passed, and with the help of Sasha I refined my original code (which wasn't working on Windows and was bothering me). The new snippet below now works on both Linux and Windows. (No idea about Mac since I cannot test it myself.)

The main difference is that I now wrapped a bit better the code into a module and also switched away from loading the OpenGL library to instead use the SDL functionality to query the functions address and call it them.

Bonus: I also added the IsExtensionSupported() function to see if specific OpenGL extensions are supported by the host.

olg.lua

Code: Select all

local FFI = require( 'ffi' )
local OPENGL = {}
OPENGL.GL = {}
OPENGL.SDL = FFI.os == "Windows" and FFI.load("SDL2") or FFI.C

function OPENGL.Init()
	local Definitions = [[
		//---------------------
		// OpenGL
		//---------------------
		typedef char GLchar;
		typedef int GLsizei;
		typedef unsigned int GLuint;
		typedef unsigned int GLenum;

		// void glPushDebugGroup( GLenum source, GLuint id, GLsizei length, const GLchar *message );
		typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPPROC) (GLenum source, GLuint id, GLsizei length, const GLchar *message);

		// void glPopDebugGroup( void );
		typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC) (void);

		//---------------------
		// SDL
		//---------------------
		typedef bool SDL_bool;
		SDL_bool SDL_GL_ExtensionSupported( const char *extension );
		void* SDL_GL_GetProcAddress( const char *proc );
	]]

	if FFI.os == "Windows" then
		Definitions = Definitions:gsub( "APIENTRYP", "__stdcall *" )
	else
		Definitions = Definitions:gsub( "APIENTRYP", "*" )
	end

	FFI.cdef( Definitions )

	-- https://registry.khronos.org/OpenGL/api/GL/glext.h
	local Names = {
		{ "glPushDebugGroup", "PFNGLPUSHDEBUGGROUPPROC" },
		{ "glPopDebugGroup", "PFNGLPOPDEBUGGROUPPROC" }
	}
	local ProcName = ""
	local GLName = ""

	for i=1, #Names do
		GLName = Names[i][1]
		ProcName = Names[i][2]
		local Function = FFI.cast( ProcName, OPENGL.SDL.SDL_GL_GetProcAddress(GLName) )
		rawset( OPENGL.GL, GLName, Function)
	end
end

function OPENGL.IsExtensionSupported( Name )
	return OPENGL.SDL.SDL_GL_ExtensionSupported( Name )
end

function OPENGL.PushEvent( Message )
	OPENGL.GL.glPushDebugGroup(
		0,
		0,
		string.len( Message ),
		Message
	)
end

function OPENGL.PopEvent()
	OPENGL.GL.glPopDebugGroup()
end

OPENGL.Init()
return OPENGL
Usage is the same as before, simply call Push/Pop to groups you API calls.
A few tricks to be able to make sure your graphic requests get put into the group (since Löve has full control on when command are sent to the GPU by default):
  • Use love.graphics.flushBatch() before calling PopEvent() to force Löve to send out drawing command before the debug group close.
  • Use love.graphics.setCanvas() to switch between canvas and therefore force a flush in case flushBatch is not enough (for example when drawing text, which has its own batching).
These post points have been to used only when necessary (and know what you are doing) otherwise you could break optimizations and impact performance.
In my case I used those to ensure text was drawn at specific time in my rendering process before I went to do other operations.