Merge branch 'master' into master

This commit is contained in:
Korbo 2025-07-07 18:03:15 -05:00 committed by GitHub
commit b995b238d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 174 additions and 224 deletions

View File

@ -455,11 +455,6 @@
<string>Unknown - Broken</string>
</property>
</item>
<item>
<property name="text">
<string>Fake Mosaic</string>
</property>
</item>
</widget>
</item>
<item>

View File

@ -48,8 +48,7 @@ class MxTransitionManager : public MxCore {
e_mosaic,
e_wipeDown,
e_windows,
e_broken, // Unknown what this is supposed to be, it locks the game up
e_fakeMosaic
e_broken // Unknown what this is supposed to be, it locks the game up
};
MxResult StartTransition(TransitionType p_animationType, MxS32 p_speed, MxBool p_doCopy, MxBool p_playMusicInAnim);
@ -69,7 +68,6 @@ class MxTransitionManager : public MxCore {
void WipeDownTransition();
void WindowsTransition();
void BrokenTransition();
void FakeMosaicTransition();
void SubmitCopyRect(LPDDSURFACEDESC p_ddsc);
void SetupCopyRect(LPDDSURFACEDESC p_ddsc);

View File

@ -84,9 +84,6 @@ MxResult MxTransitionManager::Tickle()
case e_broken:
BrokenTransition();
break;
case e_fakeMosaic:
FakeMosaicTransition();
break;
}
return SUCCESS;
}
@ -664,166 +661,3 @@ void MxTransitionManager::configureMxTransitionManager(TransitionType p_transiti
{
g_transitionManagerConfig = p_transitionManagerConfig;
}
int g_colorOffset;
int GetColorIndexWithLocality(int p_col, int p_row)
{
int islandX = p_col / 8;
int islandY = p_row / 8; // Dvide screen in 8x6 tiles
int island = islandY * 8 + islandX; // tile id
if (SDL_rand(3) > island / 8) {
return 6 + SDL_rand(2); // emulate sky
}
if (SDL_rand(16) > 2) {
island += SDL_rand(3) - 1 + (SDL_rand(3) - 1) * 8; // blure tiles
}
int hash = (island + g_colorOffset) * 2654435761u;
int scrambled = (hash >> 16) % 32;
int finalIndex = scrambled + SDL_rand(3) - 1;
return abs(finalIndex) % 32;
}
void MxTransitionManager::FakeMosaicTransition()
{
static LPDIRECTDRAWSURFACE g_fakeTranstionSurface = nullptr;
if (m_animationTimer == 16) {
m_animationTimer = 0;
if (g_fakeTranstionSurface) {
g_fakeTranstionSurface->Release();
g_fakeTranstionSurface = nullptr;
}
EndTransition(TRUE);
return;
}
else {
if (m_animationTimer == 0) {
g_colorOffset = SDL_rand(32);
// Same init/shuffle steps as the dissolve transition, except that
// we are using big blocky pixels and only need 64 columns.
MxS32 i;
for (i = 0; i < 64; i++) {
m_columnOrder[i] = i;
}
for (i = 0; i < 64; i++) {
MxS32 swap = SDL_rand(64);
MxU16 t = m_columnOrder[i];
m_columnOrder[i] = m_columnOrder[swap];
m_columnOrder[swap] = t;
}
// The same is true here. We only need 48 rows.
for (i = 0; i < 48; i++) {
m_randomShift[i] = SDL_rand(64);
}
DDSURFACEDESC mainDesc = {};
mainDesc.dwSize = sizeof(mainDesc);
if (m_ddSurface->GetSurfaceDesc(&mainDesc) != DD_OK) {
return;
}
DDSURFACEDESC tempDesc = {};
tempDesc.dwSize = sizeof(tempDesc);
tempDesc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS;
tempDesc.dwWidth = 64;
tempDesc.dwHeight = 48;
tempDesc.ddpfPixelFormat = mainDesc.ddpfPixelFormat;
tempDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
if (MVideoManager()->GetDirectDraw()->CreateSurface(&tempDesc, &g_fakeTranstionSurface, nullptr) != DD_OK) {
return;
}
DWORD fillColor = 0x00000000;
switch (mainDesc.ddpfPixelFormat.dwRGBBitCount) {
case 8:
fillColor = 0x10;
break;
case 16:
fillColor = RGB555_CREATE(0x1f, 0, 0x1f);
break;
}
DDBLTFX bltFx = {};
bltFx.dwSize = sizeof(bltFx);
bltFx.dwFillColor = fillColor;
g_fakeTranstionSurface->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &bltFx);
DDCOLORKEY key = {};
key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = fillColor;
g_fakeTranstionSurface->SetColorKey(DDCKEY_SRCBLT, &key);
}
// Run one tick of the animation
DDSURFACEDESC ddsd;
memset(&ddsd, 0, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
HRESULT res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL);
if (res == DDERR_SURFACELOST) {
g_fakeTranstionSurface->Restore();
res = g_fakeTranstionSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL);
}
if (res == DD_OK) {
SubmitCopyRect(&ddsd);
static const MxU8 g_palette[32][3] = {
{0x00, 0x00, 0x00}, {0x12, 0x1e, 0x50}, {0x00, 0x22, 0x6c}, {0x14, 0x2d, 0x9f}, {0x0e, 0x36, 0xb0},
{0x0e, 0x39, 0xd0}, {0x47, 0x96, 0xe2}, {0x79, 0xaa, 0xca}, {0xff, 0xff, 0xff}, {0xc9, 0xcd, 0xcb},
{0xad, 0xad, 0xab}, {0xa6, 0x91, 0x8e}, {0xaf, 0x59, 0x49}, {0xc0, 0x00, 0x00}, {0xab, 0x18, 0x18},
{0x61, 0x0c, 0x0c}, {0x04, 0x38, 0x12}, {0x2c, 0x67, 0x28}, {0x4a, 0xb4, 0x6b}, {0x94, 0xb7, 0x7c},
{0xb6, 0xb9, 0x87}, {0x52, 0x4a, 0x67}, {0x87, 0x8d, 0x8a}, {0xa6, 0x91, 0x8e}, {0xf8, 0xee, 0xdc},
{0xf4, 0xe2, 0xc3}, {0x87, 0x8d, 0x8a}, {0xba, 0x9f, 0x12}, {0xb5, 0x83, 0x00}, {0x6a, 0x44, 0x27},
{0x36, 0x37, 0x34}, {0x2b, 0x23, 0x0f}
};
MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8;
for (MxS32 col = 0; col < 64; col++) {
// Select 4 columns on each tick
if (m_animationTimer * 4 > m_columnOrder[col]) {
continue;
}
if (m_animationTimer * 4 + 3 < m_columnOrder[col]) {
continue;
}
for (MxS32 row = 0; row < 48; row++) {
MxS32 x = (m_randomShift[row] + col) % 64;
MxU8* dest = (MxU8*) ddsd.lpSurface + row * ddsd.lPitch + x * bytesPerPixel;
const MxU8 paletteIndex = GetColorIndexWithLocality(col, row);
const MxU8* color = g_palette[paletteIndex];
switch (bytesPerPixel) {
case 1:
*dest = paletteIndex;
break;
case 2:
*((MxU16*) dest) = RGB555_CREATE(color[2], color[1], color[0]);
break;
default:
*((MxU32*) dest) = RGB8888_CREATE(color[2], color[1], color[0], 255);
break;
}
}
}
SetupCopyRect(&ddsd);
g_fakeTranstionSurface->Unlock(ddsd.lpSurface);
RECT srcRect = {0, 0, 64, 48};
m_ddSurface->Blt(&g_fullScreenRect, g_fakeTranstionSurface, &srcRect, DDBLT_WAIT | DDBLT_KEYSRC, NULL);
m_animationTimer++;
}
}
}

View File

@ -237,14 +237,79 @@ GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup, bool forceUV = f
return cache;
}
bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI)
{
SDL_Surface* surf = source;
if (source->format != SDL_PIXELFORMAT_RGBA32) {
surf = SDL_ConvertSurface(source, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
return false;
}
}
glGenTextures(1, &outTexId);
glBindTexture(GL_TEXTURE_2D, outTexId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels);
if (isUI) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (strstr((const char*) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) {
GLfloat maxAniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso);
GLfloat desiredAniso = fminf(8.0f, maxAniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, desiredAniso);
}
glGenerateMipmap(GL_TEXTURE_2D);
}
if (surf != source) {
SDL_DestroySurface(surf);
}
return true;
}
OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext context, GLuint shaderProgram)
: m_context(context), m_shaderProgram(shaderProgram)
{
glGenFramebuffers(1, &m_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
m_virtualWidth = width;
m_virtualHeight = height;
ViewportTransform viewportTransform = {1.0f, 0.0f, 0.0f};
Resize(width, height, viewportTransform);
SDL_Surface* dummySurface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32);
if (!dummySurface) {
SDL_Log("Failed to create surface: %s", SDL_GetError());
return;
}
if (!SDL_LockSurface(dummySurface)) {
SDL_Log("Failed to lock surface: %s", SDL_GetError());
SDL_DestroySurface(dummySurface);
return;
}
((Uint32*) dummySurface->pixels)[0] = 0xFFFFFFFF;
SDL_UnlockSurface(dummySurface);
UploadTexture(dummySurface, m_dummyTexture, false);
if (!m_dummyTexture) {
SDL_DestroySurface(dummySurface);
SDL_Log("Failed to create surface: %s", SDL_GetError());
return;
}
SDL_DestroySurface(dummySurface);
m_uiMesh.vertices = {
{{0.0f, 0.0f, 0.0f}, {0, 0, -1}, {0.0f, 0.0f}},
{{1.0f, 0.0f, 0.0f}, {0, 0, -1}, {1.0f, 0.0f}},
@ -270,6 +335,8 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co
m_modelViewMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix");
m_normalMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_normalMatrix");
m_projectionMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_projectionMatrix");
glUseProgram(m_shaderProgram);
}
OpenGLES2Renderer::~OpenGLES2Renderer()
@ -321,47 +388,6 @@ void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture*
);
}
bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI)
{
SDL_Surface* surf = source;
if (source->format != SDL_PIXELFORMAT_RGBA32) {
surf = SDL_ConvertSurface(source, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
return false;
}
}
glGenTextures(1, &outTexId);
glBindTexture(GL_TEXTURE_2D, outTexId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels);
if (isUI) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (strstr((const char*) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) {
GLfloat maxAniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso);
GLfloat desiredAniso = fminf(8.0f, maxAniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, desiredAniso);
}
glGenerateMipmap(GL_TEXTURE_2D);
}
if (surf != source) {
SDL_DestroySurface(surf);
}
return true;
}
Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
@ -460,13 +486,13 @@ HRESULT OpenGLES2Renderer::BeginFrame()
{
m_dirty = true;
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glEnable(GL_CULL_FACE);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glUseProgram(m_shaderProgram);
SceneLightGLES2 lightData[3];
int lightCount = std::min(static_cast<int>(m_lights.size()), 3);
@ -537,6 +563,9 @@ void OpenGLES2Renderer::SubmitDraw(
}
else {
glUniform1i(m_useTextureLoc, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_dummyTexture);
glUniform1i(m_textureLoc, 0);
}
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboPositions);
@ -564,7 +593,6 @@ HRESULT OpenGLES2Renderer::FinalizeFrame()
{
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glUseProgram(0);
return DD_OK;
}
@ -578,12 +606,43 @@ void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& v
SDL_DestroySurface(m_renderedImage);
}
m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
// Create color texture
glGenTextures(1, &m_colorTarget);
glBindTexture(GL_TEXTURE_2D, m_colorTarget);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorTarget, 0);
// Create depth renderbuffer
glGenRenderbuffers(1, &m_depthTarget);
glBindRenderbuffer(GL_RENDERBUFFER, m_depthTarget);
if (SDL_GL_ExtensionSupported("GL_OES_depth24")) {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, width, height);
}
else if (SDL_GL_ExtensionSupported("GL_OES_depth32")) {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32_OES, width, height);
}
else {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
}
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthTarget);
glViewport(0, 0, m_width, m_height);
}
void OpenGLES2Renderer::Clear(float r, float g, float b)
{
m_dirty = true;
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glClearColor(r, g, b, 1.0f);
@ -592,21 +651,76 @@ void OpenGLES2Renderer::Clear(float r, float g, float b)
void OpenGLES2Renderer::Flip()
{
if (m_dirty) {
SDL_GL_SwapWindow(DDWindow);
m_dirty = false;
if (!m_dirty) {
return;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glFrontFace(GL_CCW);
glDepthMask(GL_FALSE);
glUniform4f(m_colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
glUniform1f(m_shinLoc, 0.0f);
float ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};
float blank[] = {0.0f, 0.0f, 0.0f, 0.0f};
glUniform4fv(u_lightLocs[0][0], 1, ambient);
glUniform4fv(u_lightLocs[0][1], 1, blank);
glUniform4fv(u_lightLocs[0][2], 1, blank);
glUniform1i(m_lightCountLoc, 1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_colorTarget);
glUniform1i(m_textureLoc, 0);
glUniform1i(m_useTextureLoc, 1);
D3DRMMATRIX4D projection;
D3DRMMATRIX4D modelViewMatrix = {
{(float) m_width, 0.0f, 0.0f, 0.0f},
{0.0f, (float) -m_height, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f, 0.0f},
{0.0f, (float) m_height, 0.0f, 1.0f}
};
glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelViewMatrix[0][0]);
Matrix3x3 identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}};
glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &identity[0][0]);
CreateOrthographicProjection((float) m_width, (float) m_height, projection);
glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &projection[0][0]);
glDisable(GL_SCISSOR_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions);
glEnableVertexAttribArray(m_posLoc);
glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords);
glEnableVertexAttribArray(m_texLoc);
glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr);
glDisableVertexAttribArray(m_texLoc);
SDL_GL_SwapWindow(DDWindow);
glFrontFace(GL_CW);
m_dirty = false;
}
void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color)
{
m_dirty = true;
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glUseProgram(m_shaderProgram);
float ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};
float blank[] = {0.0f, 0.0f, 0.0f, 0.0f};
glUniform4fv(u_lightLocs[0][0], 1, ambient);
@ -629,14 +743,17 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c
static_cast<int>(std::round(texture.height * scaleY))
};
glActiveTexture(GL_TEXTURE0);
glUniform1i(m_useTextureLoc, 1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture.glTextureId);
glUniform1i(m_textureLoc, 0);
}
else {
expandedDstRect = dstRect;
glUniform1i(m_useTextureLoc, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_dummyTexture);
glUniform1i(m_textureLoc, 0);
}
D3DRMMATRIX4D modelView, projection;
@ -685,6 +802,8 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c
void OpenGLES2Renderer::Download(SDL_Surface* target)
{
glFinish();
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_renderedImage->pixels);
SDL_Rect srcRect = {

View File

@ -72,7 +72,11 @@ class OpenGLES2Renderer : public Direct3DRMRenderer {
bool m_dirty = false;
std::vector<SceneLight> m_lights;
SDL_GLContext m_context;
GLuint m_fbo;
GLuint m_colorTarget;
GLuint m_depthTarget;
GLuint m_shaderProgram;
GLuint m_dummyTexture;
GLint m_posLoc;
GLint m_normLoc;
GLint m_texLoc;