3D viewport, milestone 1

This commit is contained in:
John McCardle 2026-02-04 13:33:14 -05:00
commit e277663ba0
27 changed files with 7389 additions and 8 deletions

113
src/platform/GLContext.h Normal file
View file

@ -0,0 +1,113 @@
// GLContext.h - OpenGL context abstraction for McRogueFace 3D
// Provides uniform GL access across SFML and SDL2 backends
#pragma once
#include "../3d/Math3D.h"
namespace mcrf {
namespace gl {
// =============================================================================
// Initialization
// =============================================================================
// Initialize OpenGL function pointers if needed (GLAD for desktop SFML)
// Returns true if GL is ready to use
bool initGL();
// Check if GL is initialized and ready
bool isGLReady();
// =============================================================================
// Framebuffer Object (FBO) Management
// =============================================================================
// Create a framebuffer with color texture and optional depth renderbuffer
// Returns FBO id, sets colorTex to the color attachment texture
// depthRB is optional - pass nullptr if depth buffer not needed
unsigned int createFramebuffer(int width, int height, unsigned int* colorTex, unsigned int* depthRB = nullptr);
// Bind a framebuffer for rendering
void bindFramebuffer(unsigned int fbo);
// Bind the default framebuffer (screen)
void bindDefaultFramebuffer();
// Delete a framebuffer and its attachments
void deleteFramebuffer(unsigned int fbo, unsigned int colorTex, unsigned int depthRB);
// =============================================================================
// Shader Compilation
// =============================================================================
// Compile a vertex or fragment shader from source
// type should be GL_VERTEX_SHADER or GL_FRAGMENT_SHADER
unsigned int compileShader(unsigned int type, const char* source);
// Link vertex and fragment shaders into a program
// Returns program id, or 0 on failure
unsigned int linkProgram(unsigned int vertShader, unsigned int fragShader);
// Delete a shader program
void deleteProgram(unsigned int program);
// =============================================================================
// GL State Management (for mixing with SFML rendering)
// =============================================================================
// Save current OpenGL state before custom 3D rendering
// This includes blend mode, depth test, culling, bound textures, etc.
void pushState();
// Restore OpenGL state after custom 3D rendering
void popState();
// =============================================================================
// 3D Rendering State Setup
// =============================================================================
// Set up GL state for 3D rendering (depth test, culling, etc.)
void setup3DState();
// Restore GL state for 2D rendering (disable depth, etc.)
void restore2DState();
// =============================================================================
// Depth Buffer Operations
// =============================================================================
// Enable/disable depth testing
void setDepthTest(bool enable);
// Enable/disable depth writing
void setDepthWrite(bool enable);
// Set depth test function (GL_LESS, GL_LEQUAL, etc.)
void setDepthFunc(unsigned int func);
// Clear the depth buffer
void clearDepth();
// =============================================================================
// Face Culling
// =============================================================================
// Enable/disable face culling
void setCulling(bool enable);
// Set which face to cull (GL_BACK, GL_FRONT, GL_FRONT_AND_BACK)
void setCullFace(unsigned int face);
// =============================================================================
// Utility
// =============================================================================
// Get last GL error as string (for debugging)
const char* getErrorString();
// Check for GL errors and log them
bool checkError(const char* operation);
} // namespace gl
} // namespace mcrf

View file

@ -0,0 +1,60 @@
// GLContext_Headless.cpp - Headless backend for OpenGL context abstraction
// Returns failure for all operations since there's no GPU
#ifdef MCRF_HEADLESS
#include "GLContext.h"
namespace mcrf {
namespace gl {
bool initGL() {
return false;
}
bool isGLReady() {
return false;
}
unsigned int createFramebuffer(int width, int height, unsigned int* colorTex, unsigned int* depthRB) {
if (colorTex) *colorTex = 0;
if (depthRB) *depthRB = 0;
return 0;
}
void bindFramebuffer(unsigned int fbo) {}
void bindDefaultFramebuffer() {}
void deleteFramebuffer(unsigned int fbo, unsigned int colorTex, unsigned int depthRB) {}
unsigned int compileShader(unsigned int type, const char* source) {
return 0;
}
unsigned int linkProgram(unsigned int vertShader, unsigned int fragShader) {
return 0;
}
void deleteProgram(unsigned int program) {}
void pushState() {}
void popState() {}
void setup3DState() {}
void restore2DState() {}
void setDepthTest(bool enable) {}
void setDepthWrite(bool enable) {}
void setDepthFunc(unsigned int func) {}
void clearDepth() {}
void setCulling(bool enable) {}
void setCullFace(unsigned int face) {}
const char* getErrorString() {
return "Headless mode - no GL context";
}
bool checkError(const char* operation) {
return false;
}
} // namespace gl
} // namespace mcrf
#endif // MCRF_HEADLESS

View file

@ -0,0 +1,305 @@
// GLContext_SDL2.cpp - SDL2 backend for OpenGL context abstraction
// Leverages existing SDL2Renderer infrastructure
#ifdef MCRF_SDL2
#include "GLContext.h"
#include "SDL2Renderer.h"
#ifdef __EMSCRIPTEN__
#include <GLES2/gl2.h>
#else
#include <GL/gl.h>
#include <GL/glext.h>
#endif
#include <vector>
namespace mcrf {
namespace gl {
// =============================================================================
// State tracking structures (same as SFML version)
// =============================================================================
struct GLState {
GLboolean depthTest;
GLboolean depthWrite;
GLenum depthFunc;
GLboolean cullFace;
GLenum cullMode;
GLboolean blend;
GLenum blendSrc;
GLenum blendDst;
GLint viewport[4];
GLint boundFBO;
GLint boundProgram;
GLint boundTexture;
};
static std::vector<GLState> stateStack;
// =============================================================================
// Initialization
// =============================================================================
bool initGL() {
// SDL2Renderer handles GL initialization
auto& renderer = sf::SDL2Renderer::getInstance();
return renderer.isGLInitialized();
}
bool isGLReady() {
auto& renderer = sf::SDL2Renderer::getInstance();
return renderer.isGLInitialized();
}
// =============================================================================
// FBO Management
// =============================================================================
unsigned int createFramebuffer(int width, int height, unsigned int* colorTex, unsigned int* depthRB) {
GLuint fbo, tex, depth = 0;
// Create FBO
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// Create color texture
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
// Create depth renderbuffer if requested
// Note: GLES2 uses GL_DEPTH_COMPONENT16 instead of GL_DEPTH_COMPONENT24
if (depthRB) {
glGenRenderbuffers(1, &depth);
glBindRenderbuffer(GL_RENDERBUFFER, depth);
#ifdef __EMSCRIPTEN__
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
#else
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
#endif
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
*depthRB = depth;
}
// Check completeness
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (depth) glDeleteRenderbuffers(1, &depth);
glDeleteTextures(1, &tex);
glDeleteFramebuffers(1, &fbo);
return 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
*colorTex = tex;
return fbo;
}
void bindFramebuffer(unsigned int fbo) {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
}
void bindDefaultFramebuffer() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void deleteFramebuffer(unsigned int fbo, unsigned int colorTex, unsigned int depthRB) {
if (depthRB) {
GLuint rb = depthRB;
glDeleteRenderbuffers(1, &rb);
}
if (colorTex) {
GLuint tex = colorTex;
glDeleteTextures(1, &tex);
}
if (fbo) {
GLuint f = fbo;
glDeleteFramebuffers(1, &f);
}
}
// =============================================================================
// Shader Compilation
// =============================================================================
unsigned int compileShader(unsigned int type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
GLchar infoLog[512];
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
// TODO: Log error
glDeleteShader(shader);
return 0;
}
return shader;
}
unsigned int linkProgram(unsigned int vertShader, unsigned int fragShader) {
GLuint program = glCreateProgram();
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
glLinkProgram(program);
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
GLchar infoLog[512];
glGetProgramInfoLog(program, 512, nullptr, infoLog);
// TODO: Log error
glDeleteProgram(program);
return 0;
}
return program;
}
void deleteProgram(unsigned int program) {
glDeleteProgram(program);
}
// =============================================================================
// State Management
// =============================================================================
void pushState() {
GLState state;
state.depthTest = glIsEnabled(GL_DEPTH_TEST);
glGetBooleanv(GL_DEPTH_WRITEMASK, &state.depthWrite);
glGetIntegerv(GL_DEPTH_FUNC, (GLint*)&state.depthFunc);
state.cullFace = glIsEnabled(GL_CULL_FACE);
glGetIntegerv(GL_CULL_FACE_MODE, (GLint*)&state.cullMode);
state.blend = glIsEnabled(GL_BLEND);
glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&state.blendSrc);
glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&state.blendDst);
glGetIntegerv(GL_VIEWPORT, state.viewport);
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &state.boundFBO);
glGetIntegerv(GL_CURRENT_PROGRAM, &state.boundProgram);
glGetIntegerv(GL_TEXTURE_BINDING_2D, &state.boundTexture);
stateStack.push_back(state);
}
void popState() {
if (stateStack.empty()) return;
GLState& state = stateStack.back();
if (state.depthTest) glEnable(GL_DEPTH_TEST);
else glDisable(GL_DEPTH_TEST);
glDepthMask(state.depthWrite);
glDepthFunc(state.depthFunc);
if (state.cullFace) glEnable(GL_CULL_FACE);
else glDisable(GL_CULL_FACE);
glCullFace(state.cullMode);
if (state.blend) glEnable(GL_BLEND);
else glDisable(GL_BLEND);
glBlendFunc(state.blendSrc, state.blendDst);
glViewport(state.viewport[0], state.viewport[1], state.viewport[2], state.viewport[3]);
glBindFramebuffer(GL_FRAMEBUFFER, state.boundFBO);
glUseProgram(state.boundProgram);
glBindTexture(GL_TEXTURE_2D, state.boundTexture);
stateStack.pop_back();
}
// =============================================================================
// 3D State Setup
// =============================================================================
void setup3DState() {
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
}
void restore2DState() {
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
}
// =============================================================================
// Depth Operations
// =============================================================================
void setDepthTest(bool enable) {
if (enable) glEnable(GL_DEPTH_TEST);
else glDisable(GL_DEPTH_TEST);
}
void setDepthWrite(bool enable) {
glDepthMask(enable ? GL_TRUE : GL_FALSE);
}
void setDepthFunc(unsigned int func) {
glDepthFunc(func);
}
void clearDepth() {
glClear(GL_DEPTH_BUFFER_BIT);
}
// =============================================================================
// Culling
// =============================================================================
void setCulling(bool enable) {
if (enable) glEnable(GL_CULL_FACE);
else glDisable(GL_CULL_FACE);
}
void setCullFace(unsigned int face) {
glCullFace(face);
}
// =============================================================================
// Error Handling
// =============================================================================
const char* getErrorString() {
GLenum err = glGetError();
switch (err) {
case GL_NO_ERROR: return nullptr;
case GL_INVALID_ENUM: return "GL_INVALID_ENUM";
case GL_INVALID_VALUE: return "GL_INVALID_VALUE";
case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
default: return "Unknown GL error";
}
}
bool checkError(const char* operation) {
const char* err = getErrorString();
if (err) {
// TODO: Log error with operation name
return false;
}
return true;
}
} // namespace gl
} // namespace mcrf
#endif // MCRF_SDL2

View file

@ -0,0 +1,339 @@
// GLContext_SFML.cpp - SFML backend for OpenGL context abstraction
// Uses GLAD for GL function loading
#ifndef MCRF_SDL2
#ifndef MCRF_HEADLESS
#include "GLContext.h"
#include <glad/glad.h>
#include <SFML/OpenGL.hpp>
#include <vector>
#include <cstdio>
namespace mcrf {
namespace gl {
// =============================================================================
// State tracking
// =============================================================================
static bool s_gladInitialized = false;
struct GLState {
GLboolean depthTest;
GLboolean depthWrite;
GLenum depthFunc;
GLboolean cullFace;
GLenum cullMode;
GLboolean blend;
GLenum blendSrc;
GLenum blendDst;
GLint viewport[4];
GLint boundFBO;
GLint boundProgram;
GLint boundTexture;
};
static std::vector<GLState> stateStack;
// =============================================================================
// Initialization
// =============================================================================
bool initGL() {
if (s_gladInitialized) {
return true;
}
// Load GL function pointers via GLAD
// Note: SFML must have created an OpenGL context before this is called
if (!gladLoadGL()) {
fprintf(stderr, "GLContext_SFML: Failed to initialize GLAD\n");
return false;
}
s_gladInitialized = true;
printf("GLContext_SFML: GLAD initialized - OpenGL %d.%d\n", GLVersion.major, GLVersion.minor);
return true;
}
bool isGLReady() {
return s_gladInitialized;
}
// =============================================================================
// FBO Management
// =============================================================================
unsigned int createFramebuffer(int width, int height, unsigned int* colorTex, unsigned int* depthRB) {
if (!s_gladInitialized) return 0;
GLuint fbo, tex, depth = 0;
// Create FBO
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// Create color texture
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
// Create depth renderbuffer if requested
if (depthRB) {
glGenRenderbuffers(1, &depth);
glBindRenderbuffer(GL_RENDERBUFFER, depth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
*depthRB = depth;
}
// Check completeness
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "GLContext_SFML: Framebuffer incomplete: 0x%x\n", status);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (depth) glDeleteRenderbuffers(1, &depth);
glDeleteTextures(1, &tex);
glDeleteFramebuffers(1, &fbo);
return 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
*colorTex = tex;
return fbo;
}
void bindFramebuffer(unsigned int fbo) {
if (s_gladInitialized) {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
}
}
void bindDefaultFramebuffer() {
if (s_gladInitialized) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
void deleteFramebuffer(unsigned int fbo, unsigned int colorTex, unsigned int depthRB) {
if (!s_gladInitialized) return;
if (depthRB) {
GLuint rb = depthRB;
glDeleteRenderbuffers(1, &rb);
}
if (colorTex) {
GLuint tex = colorTex;
glDeleteTextures(1, &tex);
}
if (fbo) {
GLuint f = fbo;
glDeleteFramebuffers(1, &f);
}
}
// =============================================================================
// Shader Compilation
// =============================================================================
unsigned int compileShader(unsigned int type, const char* source) {
if (!s_gladInitialized) return 0;
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
GLchar infoLog[512];
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
fprintf(stderr, "GLContext_SFML: Shader compilation failed:\n%s\n", infoLog);
glDeleteShader(shader);
return 0;
}
return shader;
}
unsigned int linkProgram(unsigned int vertShader, unsigned int fragShader) {
if (!s_gladInitialized) return 0;
GLuint program = glCreateProgram();
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
glLinkProgram(program);
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
GLchar infoLog[512];
glGetProgramInfoLog(program, 512, nullptr, infoLog);
fprintf(stderr, "GLContext_SFML: Program linking failed:\n%s\n", infoLog);
glDeleteProgram(program);
return 0;
}
return program;
}
void deleteProgram(unsigned int program) {
if (s_gladInitialized && program) {
glDeleteProgram(program);
}
}
// =============================================================================
// State Management
// =============================================================================
void pushState() {
if (!s_gladInitialized) return;
GLState state;
state.depthTest = glIsEnabled(GL_DEPTH_TEST);
glGetBooleanv(GL_DEPTH_WRITEMASK, &state.depthWrite);
glGetIntegerv(GL_DEPTH_FUNC, (GLint*)&state.depthFunc);
state.cullFace = glIsEnabled(GL_CULL_FACE);
glGetIntegerv(GL_CULL_FACE_MODE, (GLint*)&state.cullMode);
state.blend = glIsEnabled(GL_BLEND);
glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&state.blendSrc);
glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&state.blendDst);
glGetIntegerv(GL_VIEWPORT, state.viewport);
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &state.boundFBO);
glGetIntegerv(GL_CURRENT_PROGRAM, &state.boundProgram);
glGetIntegerv(GL_TEXTURE_BINDING_2D, &state.boundTexture);
stateStack.push_back(state);
}
void popState() {
if (!s_gladInitialized || stateStack.empty()) return;
GLState& state = stateStack.back();
if (state.depthTest) glEnable(GL_DEPTH_TEST);
else glDisable(GL_DEPTH_TEST);
glDepthMask(state.depthWrite);
glDepthFunc(state.depthFunc);
if (state.cullFace) glEnable(GL_CULL_FACE);
else glDisable(GL_CULL_FACE);
glCullFace(state.cullMode);
if (state.blend) glEnable(GL_BLEND);
else glDisable(GL_BLEND);
glBlendFunc(state.blendSrc, state.blendDst);
glViewport(state.viewport[0], state.viewport[1], state.viewport[2], state.viewport[3]);
glBindFramebuffer(GL_FRAMEBUFFER, state.boundFBO);
glUseProgram(state.boundProgram);
glBindTexture(GL_TEXTURE_2D, state.boundTexture);
stateStack.pop_back();
}
// =============================================================================
// 3D State Setup
// =============================================================================
void setup3DState() {
if (!s_gladInitialized) return;
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
}
void restore2DState() {
if (!s_gladInitialized) return;
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
}
// =============================================================================
// Depth Operations
// =============================================================================
void setDepthTest(bool enable) {
if (!s_gladInitialized) return;
if (enable) glEnable(GL_DEPTH_TEST);
else glDisable(GL_DEPTH_TEST);
}
void setDepthWrite(bool enable) {
if (!s_gladInitialized) return;
glDepthMask(enable ? GL_TRUE : GL_FALSE);
}
void setDepthFunc(unsigned int func) {
if (!s_gladInitialized) return;
glDepthFunc(func);
}
void clearDepth() {
if (!s_gladInitialized) return;
glClear(GL_DEPTH_BUFFER_BIT);
}
// =============================================================================
// Culling
// =============================================================================
void setCulling(bool enable) {
if (!s_gladInitialized) return;
if (enable) glEnable(GL_CULL_FACE);
else glDisable(GL_CULL_FACE);
}
void setCullFace(unsigned int face) {
if (!s_gladInitialized) return;
glCullFace(face);
}
// =============================================================================
// Error Handling
// =============================================================================
const char* getErrorString() {
if (!s_gladInitialized) return "GLAD not initialized";
GLenum err = glGetError();
switch (err) {
case GL_NO_ERROR: return nullptr;
case GL_INVALID_ENUM: return "GL_INVALID_ENUM";
case GL_INVALID_VALUE: return "GL_INVALID_VALUE";
case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION";
default: return "Unknown GL error";
}
}
bool checkError(const char* operation) {
const char* err = getErrorString();
if (err) {
fprintf(stderr, "GLContext_SFML: GL error after %s: %s\n", operation, err);
return false;
}
return true;
}
} // namespace gl
} // namespace mcrf
#endif // MCRF_HEADLESS
#endif // MCRF_SDL2