OpenGL Framebuffer always black

Started by
9 comments, last by Catch_0x16 4 years, 5 months ago

Hello Folks,

I'm writing a plugin for a simulator called 'X-Plane 11'. My plugin essentially calls on a DLL (Which I've written) that makes OpenGL calls to draw graphics onto the active draw target (it does no OpenGL initialization). I want this external DLL to represent the logic behind a computer screen, but not be directly coupled with the OpenGL context that it is being drawn to. In this current instance for example, I am drawing it to the screen in X-Plane, however, I may at some point want to draw it to the screen of a different game engine, or perhaps even just a windows program.

The way I am trying to achieve this:

  1. Generate a frame buffer and texture
  2. Initialize texture and add it to the framebuffer
  3. On the drawcall...
    1. Bind to the frame buffer
    2. Call the dll's draw method
    3. Un-bind the frame buffer
    4. Bind to the host applications framebuffer
    5. Bind to the associated texture
    6. Draw a quad in the corner of the screen with appropriate texture coordinates
    7. Un-bind the texture

I have the problem though that the output is always just a black square...(See attached image)

 

The code goes a little like this (Only showing the relevant bits):

 


#include <Windows.h>
#include <string>
#include "PluginHeader.h"

#include <GL\glew.h>
#include <gl\GL.h>
#include <gl\GLU.h>


#include "MFDSim.h"

HINSTANCE g_MfdSimLibHandle = 0;
XPLMWindowID g_MfdWindowID = 0;

GLuint g_FrameBufferID = 0;
GLuint g_fbTex = 0;
GLuint g_rbo = 0;

unsigned int g_MfdScreenWidth = 0;
unsigned int g_MfdScreenHeight = 0;

/////////////////////////////////////////////////////
// Standard plugin functions ------------------------

PLUGIN_API int XPluginStart(
	char* outName,
	char* outSig,
	char* outDesc)
{
	strcpy(outName,"MFD Sim");
	strcpy(outSig, "mfd");
	strcpy(outDesc, ");

	int retVal = TRUE;

	glewInit();

	HINSTANCE g_MfdSimLibHandle = LoadLibrary("./Resources/plugins/MFD_Sim_Plugin/win_x64/MFDSim.dll");
	if (g_MfdSimLibHandle != nullptr)
	{
		try
		{
			MFD::mfdInit("./Resources/plugins/MFD_Sim_Plugin/config/layout.xml");
			MFD::mfdGetWidthHeight(g_MfdScreenWidth, g_MfdScreenHeight);
		}
		catch (const std::exception& ex)
		{
			retVal = FALSE;
		}
	}
	else
	{
		retVal = FALSE;
	}

	return retVal;
}

PLUGIN_API void XPluginStop()
{
	FreeLibrary(g_MfdSimLibHandle);
}

PLUGIN_API int XPluginEnable()
{
	// We need to revert back to the appropriate frame buffer after we're done
	GLint currentReadFB = 0;
	GLint currentDrawFB = 0;

	glGetIntegerv(GL_READ_FRAMEBUFFER, &currentReadFB);
	glGetIntegerv(GL_DRAW_FRAMEBUFFER, &currentDrawFB);

	XPLMRegisterDrawCallback(OnDrawCall, xplm_Phase_LastCockpit, TRUE, nullptr);

	// Generate OPENGL stuff we need
	glGenFramebuffers(1, &g_FrameBufferID);
	glBindFramebuffer(GL_FRAMEBUFFER, g_FrameBufferID);

	glGenTextures(1, &g_fbTex);
	glBindTexture(GL_TEXTURE_2D, g_fbTex);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, g_MfdScreenWidth, g_MfdScreenHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, 0);

	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, g_fbTex, 0);

	glGenRenderbuffers(1, &g_rbo);
	glBindRenderbuffer(GL_RENDERBUFFER, g_rbo);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, g_MfdScreenWidth, g_MfdScreenHeight);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);

	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, g_rbo);

	glBindFramebuffer(GL_READ_FRAMEBUFFER, currentReadFB);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentDrawFB);

	return TRUE;
}

PLUGIN_API void XPluginDisable()
{
	XPLMUnregisterDrawCallback(OnDrawCall, xplm_Phase_LastCockpit, TRUE, nullptr);
	glDeleteFramebuffers(1, &g_FrameBufferID);
	glDeleteTextures(1, &g_fbTex);
}

PLUGIN_API void XPluginReceiveMessage(
	XPLMPluginID inFrom,
	int inMsg,
	void* inParam)
{
}

// Boilerplate DLL stuff
BOOL WINAPI DLLMain(
	_In_ HINSTANCE hInstance,
	_In_ DWORD fdwReason,
	_In_ LPVOID lpvReserved)
{
	return TRUE;
}

/////////////////////////////////////////////////////
// MFD functions -----------------------------------


void HandleWindowKeyEvent(XPLMWindowID inWindowID, char inKey, XPLMKeyFlags inFlags, char inVirtualKey, void * inRefcon, int losingFocus)
{
	if (inWindowID == g_MfdWindowID)
	{
	}
}

int OnDrawCall(XPLMDrawingPhase inPhase, int inIsBefore, void * refCon)
{
	// We need to revert back to the appropriate frame buffer after we're done
	GLint currentReadFB = 0;
	GLint currentDrawFB = 0;

	glGetIntegerv(GL_READ_FRAMEBUFFER, &currentReadFB);
	glGetIntegerv(GL_DRAW_FRAMEBUFFER, &currentDrawFB);

	// Bind to our own frame buffer and draw our MFD
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, g_FrameBufferID);
	glBindFramebuffer(GL_READ_FRAMEBUFFER, g_FrameBufferID);

	glBindTexture(GL_TEXTURE_2D, g_fbTex);

	glClear(GL_COLOR_BUFFER_BIT);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glEnable(GL_TEXTURE_2D);

	if (!MFD::mfdDraw(1.0f))
	{
		// TODO: Handle failure to draw
	}

	glBegin(GL_LINES);
	glColor4f(1.0f, 0.1f, 0.1f, 1.0f);
	glVertex2f(0.0f, 0.0f);
	glVertex2f(100.0f, 100.0f);
	glEnd();

	glBindFramebuffer(GL_READ_FRAMEBUFFER, currentReadFB);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentDrawFB);
	
	glEnable(GL_TEXTURE_2D);

	glBegin(GL_QUADS);

		glVertex2f(0, 0);									glTexCoord2f(0.0f, 0.0f);
		glVertex2f(0, g_MfdScreenHeight);					glTexCoord2f(0.0f, 1.0f);
		glVertex2f(g_MfdScreenWidth, g_MfdScreenHeight);	glTexCoord2f(1.0f, 1.0f);
		glVertex2f(g_MfdScreenWidth, 0);					glTexCoord2f(1.0f, 0.0f);

	glEnd();

	glBindTexture(GL_TEXTURE_2D, 0);

	return 0;
}

 

BlackSquare.png

Advertisement

Check the framebuffer for completion after assembling all attachments with a call to glCheckFramebufferStatus.

Do you actually blit the framebuffer's contents to the "real" draw buffer with a call to and of glBlitFramebuffer ?

The mixture between opengl 3+ and legacy code in general, any disadvantages from that ?

Nice image :-)

Thanks for the reply Green Baron. Other than the attractive black box down in the lower left, I can't really take credit for the image :)

I've not blit the framebuffers contents... think that would be the problem? (I feel a bit silly now).

I'm pretty new to OpenGL, what code am I using that is legacy versus 3+? I'm keen to improve so thanks for highlighting it.

 

No worries, i feel silly several times a day ?

The immediate mode code is the legacy part, the code starting with glBegin and ending with glEnd. It is deprecated since OpenGL 3.3.

Oh cool, I see. Presumably it's just not necessary anymore? I will have to look into this in more detail later.

I'm checking the framebuffer status but it seems to be returning 0, which at face value does not concern me, however it seems that GL_FRAMEBUFFER_COMPLETE is 0x8CD5 so something is going wrong. I will keep trying to work out what is going wrong but any intuition you may have would be appreciated.

I feel like feeling silly frequently is a right of passage as a programmer. The days where I feel like I know what I am doing always make me concerned! :)

Checking could also be performed like this (you'd probably use the "unnamed" version of the call):


if( GL_FRAMEBUFFER_COMPLETE != glCheckNamedFramebufferStatus( m_framebuffer, GL_FRAMEBUFFER ) )
	...

The "modern" style would be to create a vertex array object with the vertex data (two triangles) and assemble a small pipeline with a pass-through vertex and fragment shader. learnopengl.com triangle example explains that perfectly.

To participate in the philosophical periphrasing: "modern" is a fuzzy expression. Master painters of the late 19th century were "modern", too ... ?

Thanks,

I've been doing some digging since my last post and I've found some interesting things.

Looks like when I generate my framebuffer, I am assigned a value of '1'. Later on during my draw call when I call 'glCheckFramebufferStatus' it returns 0. Further reading suggests that it only returns 0 when an error occurs.

I'm now checking for errors before and after the call to check the framebuffer status and it looks like the call is generating the 1280 error 'GL_INVALID_ENUM'.

According to the documentation GL_INVALID_ENUM should only occur if:

Quote

GL_INVALID_ENUM is generated if target is not GL_FRAMEBUFFER.

Which leads me to suggest that my framebuffer value of 1 is perhaps no longer valid... Can you think of any reason this might occur?

I am plugging into drawcalls of a much larger game engine that is making OpenGL calls left right and centre. I wonder if ID '1' is being hardcoded/assumed as something else? (Unlikely).

Ok small update, looks like when I call 'glCheckNAMEDFramebufferStatus' I get the return code of 36053, or 'GL_FRAMEBUFFER_COMPLETE'

 

Quick question -> Do I need to re-define/re-compile shaders for the new framebuffer?

I have never done modding, so if write nonsense, i hope that o more savant colleague jumps in and debunks me.

Possible solutions: don't use framebuffers. Just render to a scissored quad or a viewport of the appropriate size without switching framebuffers. This is theoretical from my side, i haven't done that yet.

If you want to stay with the framebuffer approach, maybe in order to do some extra processing, i'd first check the opengl context's version with glgetintegerv. I guess you don't create the context yourself and have to live with the one the application provides ? It should be 3.0 or higher in order to use framebuffers. If you have a version of 4.3 and above, use an opengl debug context to receive messages about opengl's feelings.

Also, after the steps you perform, you can check if they succeed, via glIsFramebuffer after generating the framebuffer, glistexture and glisrenderbuffer. For rendering, you bind your complete framebuffer to the draw buffer target, issue your draw call(s), then bind it to read buffer and the "default" one to write buffer and blit to it, then you bind the default one to draw buffer again for subsequent drawing and presentation.

If you work with named framebuffers (opengl 4.5), it may be a good idea to use the family of functions for named objects, glCreate*, and save a lot of binding and unbinding. But then you transfer a lot of logic to the shader pipelines. Maybe, at this point, let me recommend the OpenGL Programming Guide 9th ed. and OpenGL Superbible 7th ed.

@Green_Baron Thanks for that info, sorry for the very late reply, I was out of the country for work.

Rendering to a scissored quad seems to make sense, I will google it, but to get some human input -> how would you recommend doing it?

I'm not wedded to the idea of Framebuffers, they just seemed like a sensible method of achieving this outcome; I'm a bit of an OpenGL idiot.

One of the issues I have, is that the game draws everything in screen coordinates, i.e. [0,0,1920,1080], whereas I want to make my draw calls to [-1,-1,1,1] coordinates. Is there a way of switching the coordinate system, letting my plugin draw in standard coordinates [-1-1,1,1] and then switching back to the main game coordinates?

My reason for wanting to do it this way is that the window should be able to/most likely will move and thus it's screen coordinates will change. However I want to abstract the plugin from needing to worry about screen coordinates. Hope this makes sense :)

This topic is closed to new replies.

Advertisement