diff --git a/src/api/types/mesh.c b/src/api/types/mesh.c index 2710ace2..3d1c16cd 100644 --- a/src/api/types/mesh.c +++ b/src/api/types/mesh.c @@ -57,7 +57,7 @@ int l_lovrMeshDrawInstanced(lua_State* L) { int instances = luaL_checkinteger(L, 2); float transform[16]; luax_readtransform(L, 3, transform, 1); - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .transform = transform, .mesh = mesh, .material = lovrMeshGetMaterial(mesh), diff --git a/src/graphics/font.c b/src/graphics/font.c index 69c2dcc2..cb606960 100644 --- a/src/graphics/font.c +++ b/src/graphics/font.c @@ -11,7 +11,7 @@ #include #include -#include "graphics/opengl/opengl.h" +#include "graphics/opengl.h" static float* lovrFontAlignLine(float* x, float* lineEnd, float width, HorizontalAlign halign) { while(x < lineEnd) { diff --git a/src/graphics/gpu.h b/src/graphics/gpu.h index b7e1c23a..53c14f54 100644 --- a/src/graphics/gpu.h +++ b/src/graphics/gpu.h @@ -11,22 +11,12 @@ #pragma once -typedef struct { - Layer layer; - mat4 transform; - Shader* shader; - Material* material; - Mesh* mesh; - Pipeline pipeline; - int instances; -} GpuDrawCommand; - typedef void (*gpuProc)(void); void gpuInit(bool srgb, gpuProc (*getProcAddress)(const char*)); void gpuDestroy(); void gpuClear(Canvas** canvas, int canvasCount, Color* color, float* depth, int* stencil); -void gpuDraw(GpuDrawCommand* command); +void gpuDraw(DrawCommand* command); void gpuPresent(); // Ephemeral diff --git a/src/graphics/graphics.c b/src/graphics/graphics.c index 296a3d20..69ba870e 100644 --- a/src/graphics/graphics.c +++ b/src/graphics/graphics.c @@ -5,6 +5,7 @@ #include "math/mat4.h" #include "math/vec3.h" #include "util.h" +#include "lib/glfw.h" #include "lib/stb/stb_image.h" #define _USE_MATH_DEFINES #include @@ -15,17 +16,14 @@ static GraphicsState state; static void onCloseWindow(GLFWwindow* window) { if (window == state.window) { - EventType type = EVENT_QUIT; - EventData data = { .quit = { false, 0 } }; - Event event = { .type = type, .data = data }; - lovrEventPush(event); + lovrEventPush((Event) { .type = EVENT_QUIT, .data = { .quit = { false, 0 } } }); } } // Base void lovrGraphicsInit() { - // + // This page intentionally left blank } void lovrGraphicsDestroy() { @@ -33,12 +31,12 @@ void lovrGraphicsDestroy() { lovrGraphicsSetShader(NULL); lovrGraphicsSetFont(NULL); lovrGraphicsSetCanvas(NULL, 0); - for (int i = 0; i < DEFAULT_SHADER_COUNT; i++) { + for (int i = 0; i < MAX_DEFAULT_SHADERS; i++) { lovrRelease(state.defaultShaders[i]); } lovrRelease(state.defaultMaterial); lovrRelease(state.defaultFont); - lovrRelease(state.mesh); + lovrRelease(state.defaultMesh); gpuDestroy(); memset(&state, 0, sizeof(GraphicsState)); } @@ -103,11 +101,32 @@ void lovrGraphicsCreateWindow(int w, int h, bool fullscreen, int msaa, const cha vertexFormatAppend(&format, "lovrPosition", ATTR_FLOAT, 3); vertexFormatAppend(&format, "lovrNormal", ATTR_FLOAT, 3); vertexFormatAppend(&format, "lovrTexCoord", ATTR_FLOAT, 2); - state.mesh = lovrMeshCreate(64, format, MESH_TRIANGLES, MESH_STREAM); + state.defaultMesh = lovrMeshCreate(64, format, MESH_TRIANGLES, MESH_STREAM); lovrGraphicsReset(); state.initialized = true; } +void lovrGraphicsSetCamera(Camera* camera, bool clear) { + if (!camera) { + int width, height; + lovrGraphicsGetDimensions(&width, &height); + state.camera.canvas = NULL; + state.camera.viewport[0] = 0; + state.camera.viewport[1] = 0; + state.camera.viewport[2] = width; + state.camera.viewport[3] = height; + mat4_identity(state.camera.viewMatrix); + mat4_perspective(state.camera.projection, .01f, 100.f, 67 * M_PI / 180., (float) width / height); + } else { + state.camera = *camera; + } + + if (clear) { + Color backgroundColor = lovrGraphicsGetBackgroundColor(); + gpuClear(&state.camera.canvas, 1, &backgroundColor, &(float) { 1. }, &(int) { 0 }); + } +} + void lovrGraphicsGetDimensions(int* width, int* height) { glfwGetFramebufferSize(state.window, width, height); } @@ -115,17 +134,13 @@ void lovrGraphicsGetDimensions(int* width, int* height) { // State void lovrGraphicsReset() { - int width, height; - lovrGraphicsGetDimensions(&width, &height); state.transform = 0; - state.layer = 0; - memcpy(state.layers[state.layer].viewport, (int[]) { 0, 0, width, height }, 4 * sizeof(uint32_t)); - mat4_perspective(state.layers[state.layer].projection, .01f, 100.f, 67 * M_PI / 180., (float) width / height); - mat4_identity(state.layers[state.layer].view); - lovrGraphicsSetBackgroundColor((Color) { 0, 0, 0, 1. }); + state.pipeline = 0; + lovrGraphicsSetCamera(NULL, false); + lovrGraphicsSetBackgroundColor((Color) { 0, 0, 0, 1 }); lovrGraphicsSetBlendMode(BLEND_ALPHA, BLEND_ALPHA_MULTIPLY); lovrGraphicsSetCanvas(NULL, 0); - lovrGraphicsSetColor((Color) { 1., 1., 1., 1. }); + lovrGraphicsSetColor((Color) { 1, 1, 1, 1 }); lovrGraphicsSetCullingEnabled(false); lovrGraphicsSetDefaultFilter((TextureFilter) { .mode = FILTER_TRILINEAR }); lovrGraphicsSetDepthTest(COMPARE_LEQUAL, true); @@ -139,6 +154,15 @@ void lovrGraphicsReset() { lovrGraphicsOrigin(); } +void lovrGraphicsPushPipeline() { + lovrAssert(++state.pipeline < MAX_PIPELINES, "Unbalanced pipeline stack (more pushes than pops?)"); + memcpy(&state.pipelines[state.pipeline], &state.pipelines[state.pipeline - 1], sizeof(Pipeline)); +} + +void lovrGraphicsPopPipeline() { + lovrAssert(--state.pipeline >= 0, "Unbalanced pipeline stack (more pops than pushes?)"); +} + Color lovrGraphicsGetBackgroundColor() { return state.pipelines[state.pipeline].backgroundColor; } @@ -158,21 +182,21 @@ void lovrGraphicsSetBlendMode(BlendMode mode, BlendAlphaMode alphaMode) { } void lovrGraphicsGetCanvas(Canvas** canvas, int* count) { - Layer layer = state.layers[state.layer]; - if (layer.user) { - *count = layer.canvasCount; - memcpy(canvas, layer.canvas, layer.canvasCount * sizeof(Canvas*)); - } else { - *count = 0; - } + *count = state.pipelines[state.pipeline].canvasCount; + memcpy(canvas, state.pipelines[state.pipeline].canvas, *count); } void lovrGraphicsSetCanvas(Canvas** canvas, int count) { - if (state.layers[state.layer].user) { - lovrGraphicsPopLayer(); + for (int i = 0; i < count; i++) { + lovrRetain(canvas[i]); } - lovrGraphicsPushLayer(canvas, count, true); + for (int i = 0; i < state.pipelines[state.pipeline].canvasCount; i++) { + lovrRelease(state.pipelines[state.pipeline].canvas[i]); + } + + memcpy(state.pipelines[state.pipeline].canvas, canvas, count * sizeof(Canvas*)); + state.pipelines[state.pipeline].canvasCount = count; } Color lovrGraphicsGetColor() { @@ -258,11 +282,9 @@ Shader* lovrGraphicsGetShader() { } void lovrGraphicsSetShader(Shader* shader) { - if (shader != state.pipelines[state.pipeline].shader) { - lovrRetain(shader); - lovrRelease(state.pipelines[state.pipeline].shader); - state.pipelines[state.pipeline].shader = shader; - } + lovrRetain(shader); + lovrRelease(state.pipelines[state.pipeline].shader); + state.pipelines[state.pipeline].shader = shader; } void lovrGraphicsGetStencilTest(CompareMode* mode, int* value) { @@ -296,17 +318,12 @@ void lovrGraphicsSetWireframe(bool wireframe) { // Transforms void lovrGraphicsPush() { - if (++state.transform >= MAX_TRANSFORMS) { - lovrThrow("Unbalanced matrix stack (more pushes than pops?)"); - } - - memcpy(state.transforms[state.transform], state.transforms[state.transform - 1], 2 * 16 * sizeof(float)); + lovrAssert(++state.transform < MAX_TRANSFORMS, "Unbalanced matrix stack (more pushes than pops?)"); + mat4_init(state.transforms[state.transform], state.transforms[state.transform - 1]); } void lovrGraphicsPop() { - if (--state.transform < 0) { - lovrThrow("Unbalanced matrix stack (more pops than pushes?)"); - } + lovrAssert(--state.transform >= 0, "Unbalanced matrix stack (more pops than pushes?)"); } void lovrGraphicsOrigin() { @@ -332,28 +349,27 @@ void lovrGraphicsMatrixTransform(mat4 transform) { // Drawing VertexPointer lovrGraphicsGetVertexPointer(uint32_t count) { - lovrMeshResize(state.mesh, count); - return lovrMeshMapVertices(state.mesh, 0, count, false, true); + lovrMeshResize(state.defaultMesh, count); + return lovrMeshMapVertices(state.defaultMesh, 0, count, false, true); } void lovrGraphicsClear(Color* color, float* depth, int* stencil) { - Layer layer = state.layers[state.layer]; - gpuClear(layer.canvas, layer.canvasCount, color, depth, stencil); + Pipeline* pipeline = &state.pipelines[state.pipeline]; + if (pipeline->canvasCount > 0) { + gpuClear(pipeline->canvas, pipeline->canvasCount, color, depth, stencil); + } else { + gpuClear(&state.camera.canvas, 1, color, depth, stencil); + } } -void lovrGraphicsDraw(DrawCommand* draw) { - if (draw->transform) { - lovrGraphicsPush(); - lovrGraphicsMatrixTransform(draw->transform); - } - +void lovrGraphicsDraw(DrawOptions* draw) { Shader* shader = state.pipelines[state.pipeline].shader ? state.pipelines[state.pipeline].shader : state.defaultShaders[draw->shader]; if (!shader) shader = state.defaultShaders[draw->shader] = lovrShaderCreateDefault(draw->shader); Mesh* mesh = draw->mesh; if (!mesh) { int drawCount = draw->range.count ? draw->range.count : (draw->index.count ? draw->index.count : draw->vertex.count); - mesh = state.mesh; + mesh = state.defaultMesh; lovrMeshSetDrawMode(mesh, draw->mode); lovrMeshSetDrawRange(mesh, draw->range.start, drawCount); if (draw->vertex.count) { @@ -370,41 +386,43 @@ void lovrGraphicsDraw(DrawCommand* draw) { Material* material = draw->material; if (!material) { - material = state.defaultMaterial; - - if (!material) { - material = state.defaultMaterial = lovrMaterialCreate(true); + if (!state.defaultMaterial) { + state.defaultMaterial = lovrMaterialCreate(true); } + material = state.defaultMaterial; + for (int i = 0; i < MAX_MATERIAL_TEXTURES; i++) { lovrMaterialSetTexture(material, i, draw->textures[i]); } } - gpuDraw(&(GpuDrawCommand) { - .layer = state.layers[state.layer], + DrawCommand command = { + .mesh = mesh, .shader = shader, .material = material, - .transform = state.transforms[state.transform], - .mesh = mesh, + .camera = state.camera, .pipeline = state.pipelines[state.pipeline], .instances = draw->instances - }); + }; + mat4_init(command.transform, state.transforms[state.transform]); if (draw->transform) { - lovrGraphicsPop(); + mat4_multiply(command.transform, draw->transform); } + + gpuDraw(&command); } void lovrGraphicsPoints(uint32_t count) { - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .mode = MESH_POINTS, .range = { 0, count } }); } void lovrGraphicsLine(uint32_t count) { - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .mode = MESH_LINE_STRIP, .range = { 0, count } }); @@ -412,7 +430,7 @@ void lovrGraphicsLine(uint32_t count) { void lovrGraphicsTriangle(DrawMode mode, Material* material, float points[9]) { if (mode == DRAW_MODE_LINE) { - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .material = material, .mode = MESH_LINE_LOOP, .vertex.count = 3, @@ -425,7 +443,7 @@ void lovrGraphicsTriangle(DrawMode mode, Material* material, float points[9]) { } else { float normal[3]; vec3_cross(vec3_init(normal, &points[0]), &points[3]); - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .material = material, .mode = MESH_TRIANGLES, .vertex.count = 3, @@ -440,7 +458,7 @@ void lovrGraphicsTriangle(DrawMode mode, Material* material, float points[9]) { void lovrGraphicsPlane(DrawMode mode, Material* material, mat4 transform) { if (mode == DRAW_MODE_LINE) { - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .transform = transform, .material = material, .mode = MESH_LINE_LOOP, @@ -453,7 +471,7 @@ void lovrGraphicsPlane(DrawMode mode, Material* material, mat4 transform) { } }); } else if (mode == DRAW_MODE_FILL) { - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .transform = transform, .material = material, .mode = MESH_TRIANGLE_STRIP, @@ -470,7 +488,7 @@ void lovrGraphicsPlane(DrawMode mode, Material* material, mat4 transform) { void lovrGraphicsBox(DrawMode mode, Material* material, mat4 transform) { if (mode == DRAW_MODE_LINE) { - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .transform = transform, .material = material, .mode = MESH_LINES, @@ -496,7 +514,7 @@ void lovrGraphicsBox(DrawMode mode, Material* material, mat4 transform) { } }); } else { - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .transform = transform, .material = material, .mode = MESH_TRIANGLE_STRIP, @@ -555,7 +573,7 @@ void lovrGraphicsArc(DrawMode mode, ArcMode arcMode, Material* material, mat4 tr bool hasCenterPoint = arcMode == ARC_MODE_PIE && fabsf(theta1 - theta2) < 2 * M_PI; uint32_t count = segments + 1 + hasCenterPoint; VertexPointer vertices = lovrGraphicsGetVertexPointer(count); - lovrMeshWriteIndices(state.mesh, 0, 0); + lovrMeshWriteIndices(state.defaultMesh, 0, 0); if (hasCenterPoint) { memcpy(vertices.floats, (float[]) { 0, 0, 0, 0, 0, 1, .5, .5 }, 8 * sizeof(float)); @@ -573,7 +591,7 @@ void lovrGraphicsArc(DrawMode mode, ArcMode arcMode, Material* material, mat4 tr theta += angleShift; } - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .transform = transform, .material = material, .mode = mode == DRAW_MODE_LINE ? (arcMode == ARC_MODE_OPEN ? MESH_LINE_STRIP : MESH_LINE_LOOP) : MESH_TRIANGLE_FAN, @@ -595,7 +613,7 @@ void lovrGraphicsCylinder(Material* material, float x1, float y1, float z1, floa uint32_t indexCount = 3 * segments * ((capped && r1) + (capped && r2) + 2); VertexPointer vertices = lovrGraphicsGetVertexPointer(vertexCount); - IndexPointer indices = lovrMeshWriteIndices(state.mesh, indexCount, sizeof(uint32_t)); + IndexPointer indices = lovrMeshWriteIndices(state.defaultMesh, indexCount, sizeof(uint32_t)); float* baseVertex = vertices.floats; vec3_init(p, n); @@ -675,7 +693,7 @@ void lovrGraphicsCylinder(Material* material, float x1, float y1, float z1, floa #undef PUSH_CYLINDER_VERTEX #undef PUSH_CYLINDER_TRIANGLE - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .material = material, .mode = MESH_TRIANGLES, .range = { 0, indexCount } @@ -684,7 +702,7 @@ void lovrGraphicsCylinder(Material* material, float x1, float y1, float z1, floa void lovrGraphicsSphere(Material* material, mat4 transform, int segments) { VertexPointer vertices = lovrGraphicsGetVertexPointer((segments + 1) * (segments + 1)); - IndexPointer indices = lovrMeshWriteIndices(state.mesh, segments * segments * 6, sizeof(uint32_t)); + IndexPointer indices = lovrMeshWriteIndices(state.defaultMesh, segments * segments * 6, sizeof(uint32_t)); for (int i = 0; i <= segments; i++) { float v = i / (float) segments; @@ -710,7 +728,7 @@ void lovrGraphicsSphere(Material* material, mat4 transform, int segments) { } } - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .transform = transform, .material = material, .mode = MESH_TRIANGLES, @@ -723,7 +741,7 @@ void lovrGraphicsSkybox(Texture* texture, float angle, float ax, float ay, float lovrAssert(type == TEXTURE_CUBE || type == TEXTURE_2D, "Only 2D and cube textures can be used as skyboxes"); lovrGraphicsPushPipeline(); lovrGraphicsSetWinding(WINDING_COUNTERCLOCKWISE); - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .shader = type == TEXTURE_CUBE ? SHADER_CUBE : SHADER_PANO, .textures[TEXTURE_DIFFUSE] = texture, .textures[TEXTURE_ENVIRONMENT_MAP] = texture, @@ -747,7 +765,7 @@ void lovrGraphicsPrint(const char* str, mat4 transform, float wrap, HorizontalAl uint32_t maxVertices = strlen(str) * 6; VertexPointer vertexPointer = lovrGraphicsGetVertexPointer(maxVertices); lovrFontRender(font, str, wrap, halign, valign, vertexPointer, &offsety, &vertexCount); - lovrMeshWriteIndices(state.mesh, 0, 0); + lovrMeshWriteIndices(state.defaultMesh, 0, 0); lovrGraphicsPush(); lovrGraphicsMatrixTransform(transform); @@ -755,7 +773,7 @@ void lovrGraphicsPrint(const char* str, mat4 transform, float wrap, HorizontalAl lovrGraphicsTranslate(0, offsety, 0); lovrGraphicsPushPipeline(); state.pipelines[state.pipeline].depthWrite = false; - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .shader = SHADER_FONT, .textures[TEXTURE_DIFFUSE] = font->texture, .mode = MESH_TRIANGLES, @@ -768,7 +786,7 @@ void lovrGraphicsPrint(const char* str, mat4 transform, float wrap, HorizontalAl void lovrGraphicsFill(Texture* texture) { lovrGraphicsPushPipeline(); lovrGraphicsSetDepthTest(COMPARE_NONE, false); - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .shader = SHADER_FILL, .textures[TEXTURE_DIFFUSE] = texture, .mode = MESH_TRIANGLE_STRIP, @@ -782,62 +800,3 @@ void lovrGraphicsFill(Texture* texture) { }); lovrGraphicsPopPipeline(); } - -// Internal -void lovrGraphicsPushLayer(Canvas** canvas, int count, bool user) { - lovrAssert(count <= MAX_CANVASES, "Attempt to set %d canvases (the maximum is %d)", count, MAX_CANVASES); - lovrAssert(++state.layer < MAX_LAYERS, "Layer overflow"); - - for (int i = 0; i < count; i++) { - lovrRetain(canvas[i]); - } - - Layer* prevLayer = &state.layers[state.layer - 1]; - for (int i = 0; i < prevLayer->canvasCount; i++) { - lovrRelease(prevLayer->canvas[i]); - } - - Layer* layer = &state.layers[state.layer]; - memcpy(layer, prevLayer, sizeof(Layer)); - layer->canvasCount = count; - layer->user = user; - - if (count > 0) { - memcpy(layer->canvas, canvas, count * sizeof(Canvas*)); - } -} - -void lovrGraphicsPopLayer() { - Layer* layer = &state.layers[state.layer]; - if (layer->canvasCount > 0) { - lovrCanvasResolve(layer->canvas[0]); - } - - lovrAssert(--state.layer >= 0, "Layer underflow"); -} - -void lovrGraphicsPushPipeline() { - if (++state.pipeline >= MAX_PIPELINES) { - lovrThrow("Unbalanced pipeline stack (more pushes than pops?)"); - } - - memcpy(&state.pipelines[state.pipeline], &state.pipelines[state.pipeline - 1], sizeof(Pipeline)); -} - -void lovrGraphicsPopPipeline() { - if (--state.pipeline < 0) { - lovrThrow("Unbalanced pipeline stack (more pops than pushes?)"); - } -} - -void lovrGraphicsSetCamera(mat4 projection, mat4 view) { - mat4_set(state.layers[state.layer].projection, projection); - mat4_set(state.layers[state.layer].view, view); -} - -void lovrGraphicsSetViewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - state.layers[state.layer].viewport[0] = x; - state.layers[state.layer].viewport[1] = y; - state.layers[state.layer].viewport[2] = width; - state.layers[state.layer].viewport[3] = height; -} diff --git a/src/graphics/graphics.h b/src/graphics/graphics.h index a0bd1422..f7ccf97d 100644 --- a/src/graphics/graphics.h +++ b/src/graphics/graphics.h @@ -6,25 +6,21 @@ #include "graphics/texture.h" #include "math/math.h" #include "util.h" -#include "lib/glfw.h" #include +#include #pragma once -#define MAX_TRANSFORMS 60 -#define INTERNAL_TRANSFORMS 4 -#define MAX_LAYERS 4 -#define MAX_PIPELINES 14 -#define INTERNAL_PIPELINES 2 -#define DEFAULT_SHADER_COUNT 5 -#define MAX_TEXTURES 16 +#define MAX_TRANSFORMS 64 +#define MAX_PIPELINES 16 typedef void (*StencilCallback)(void* userdata); -typedef struct { - int shaderSwitches; - int drawCalls; -} GraphicsStats; +typedef enum { + ARC_MODE_PIE, + ARC_MODE_OPEN, + ARC_MODE_CLOSED +} ArcMode; typedef enum { BLEND_ALPHA, @@ -42,22 +38,6 @@ typedef enum { BLEND_PREMULTIPLIED } BlendAlphaMode; -typedef enum { - DRAW_MODE_FILL, - DRAW_MODE_LINE -} DrawMode; - -typedef enum { - ARC_MODE_PIE, - ARC_MODE_OPEN, - ARC_MODE_CLOSED -} ArcMode; - -typedef enum { - WINDING_CLOCKWISE, - WINDING_COUNTERCLOCKWISE -} Winding; - typedef enum { COMPARE_NONE, COMPARE_EQUAL, @@ -68,6 +48,11 @@ typedef enum { COMPARE_GEQUAL } CompareMode; +typedef enum { + DRAW_MODE_FILL, + DRAW_MODE_LINE +} DrawMode; + typedef enum { STENCIL_REPLACE, STENCIL_INCREMENT, @@ -77,6 +62,11 @@ typedef enum { STENCIL_INVERT } StencilAction; +typedef enum { + WINDING_CLOCKWISE, + WINDING_COUNTERCLOCKWISE +} Winding; + typedef struct { bool initialized; float pointSizes[2]; @@ -85,10 +75,24 @@ typedef struct { float textureAnisotropy; } GraphicsLimits; +typedef struct { + int shaderSwitches; + int drawCalls; +} GraphicsStats; + +typedef struct { + Canvas* canvas; + uint32_t viewport[4]; + mat4 viewMatrix; + mat4 projection; +} Camera; + typedef struct { Color backgroundColor; BlendMode blendMode; BlendAlphaMode blendAlphaMode; + Canvas* canvas[MAX_CANVASES]; + int canvasCount; Color color; bool culling; CompareMode depthTest; @@ -104,19 +108,6 @@ typedef struct { } Pipeline; typedef struct { - float projection[16]; - float view[16]; - uint32_t viewport[4]; - Canvas* canvas[MAX_CANVASES]; - int canvasCount; - bool user; -} Layer; - -typedef struct { - mat4 transform; - DefaultShader shader; - Material* material; - Texture* textures[MAX_MATERIAL_TEXTURES]; Mesh* mesh; MeshDrawMode mode; struct { @@ -131,23 +122,36 @@ typedef struct { int start; int count; } range; + DefaultShader shader; + Material* material; + Texture* textures[MAX_MATERIAL_TEXTURES]; + mat4 transform; + int instances; +} DrawOptions; + +typedef struct { + Mesh* mesh; + Shader* shader; + Material* material; + Camera camera; + float transform[16]; + Pipeline pipeline; int instances; } DrawCommand; typedef struct { bool initialized; - GLFWwindow* window; - Shader* defaultShaders[DEFAULT_SHADER_COUNT]; + bool gammaCorrect; + void* window; + Camera camera; + Shader* defaultShaders[MAX_DEFAULT_SHADERS]; Material* defaultMaterial; Font* defaultFont; + Mesh* defaultMesh; TextureFilter defaultFilter; - bool gammaCorrect; - Mesh* mesh; - float transforms[MAX_TRANSFORMS + INTERNAL_TRANSFORMS][16]; + float transforms[MAX_TRANSFORMS][16]; int transform; - Layer layers[MAX_LAYERS]; - int layer; - Pipeline pipelines[MAX_PIPELINES + INTERNAL_PIPELINES]; + Pipeline pipelines[MAX_PIPELINES]; int pipeline; } GraphicsState; @@ -157,11 +161,14 @@ void lovrGraphicsDestroy(); void lovrGraphicsPresent(); void lovrGraphicsCreateWindow(int w, int h, bool fullscreen, int msaa, const char* title, const char* icon); void lovrGraphicsGetDimensions(int* width, int* height); +void lovrGraphicsSetCamera(Camera* camera, bool clear); GraphicsLimits lovrGraphicsGetLimits(); GraphicsStats lovrGraphicsGetStats(); // State void lovrGraphicsReset(); +void lovrGraphicsPushPipeline(); +void lovrGraphicsPopPipeline(); Color lovrGraphicsGetBackgroundColor(); void lovrGraphicsSetBackgroundColor(Color color); void lovrGraphicsGetBlendMode(BlendMode* mode, BlendAlphaMode* alphaMode); @@ -205,7 +212,7 @@ void lovrGraphicsMatrixTransform(mat4 transform); // Drawing VertexPointer lovrGraphicsGetVertexPointer(uint32_t capacity); void lovrGraphicsClear(Color* color, float* depth, int* stencil); -void lovrGraphicsDraw(DrawCommand* draw); +void lovrGraphicsDraw(DrawOptions* draw); void lovrGraphicsPoints(uint32_t count); void lovrGraphicsLine(uint32_t count); void lovrGraphicsTriangle(DrawMode mode, Material* material, float points[9]); @@ -219,11 +226,3 @@ void lovrGraphicsSkybox(Texture* texture, float angle, float ax, float ay, float void lovrGraphicsPrint(const char* str, mat4 transform, float wrap, HorizontalAlign halign, VerticalAlign valign); void lovrGraphicsStencil(StencilAction action, int replaceValue, StencilCallback callback, void* userdata); void lovrGraphicsFill(Texture* texture); - -// Internal -void lovrGraphicsPushLayer(Canvas** canvas, int count, bool user); -void lovrGraphicsPopLayer(); -void lovrGraphicsPushPipeline(); -void lovrGraphicsPopPipeline(); -void lovrGraphicsSetCamera(mat4 projection, mat4 view); -void lovrGraphicsSetViewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height); diff --git a/src/graphics/model.c b/src/graphics/model.c index 9a34229d..d7522370 100644 --- a/src/graphics/model.c +++ b/src/graphics/model.c @@ -40,7 +40,7 @@ static void renderNode(Model* model, int nodeIndex, int instances) { lovrMeshSetDrawRange(model->mesh, primitive->drawStart, primitive->drawCount); lovrMeshSetPose(model->mesh, (float*) model->pose); - lovrGraphicsDraw(&(DrawCommand) { + lovrGraphicsDraw(&(DrawOptions) { .transform = model->nodeTransforms[nodeIndex], .mesh = model->mesh, .material = lovrMeshGetMaterial(model->mesh), diff --git a/src/graphics/model.h b/src/graphics/model.h index f7868d9e..878f2388 100644 --- a/src/graphics/model.h +++ b/src/graphics/model.h @@ -17,8 +17,8 @@ typedef struct { Material* material; Animator* animator; Mesh* mesh; - float pose[MAX_BONES][16]; - float (*nodeTransforms)[16]; + mat4 pose[MAX_BONES]; + mat4* nodeTransforms; float aabb[6]; bool aabbDirty; } Model; diff --git a/src/graphics/opengl.c b/src/graphics/opengl.c new file mode 100644 index 00000000..1adae699 --- /dev/null +++ b/src/graphics/opengl.c @@ -0,0 +1,1708 @@ +#include "graphics/gpu.h" +#include "graphics/opengl.h" +#include "resources/shaders.h" +#include "data/modelData.h" +#include "math/mat4.h" +#include "lib/vec/vec.h" +#include +#include +#include +#include +#include + +#define MAX_TEXTURES 16 + +#define LOVR_SHADER_POSITION 0 +#define LOVR_SHADER_NORMAL 1 +#define LOVR_SHADER_TEX_COORD 2 +#define LOVR_SHADER_VERTEX_COLOR 3 +#define LOVR_SHADER_TANGENT 4 +#define LOVR_SHADER_BONES 5 +#define LOVR_SHADER_BONE_WEIGHTS 6 +#define LOVR_MAX_UNIFORM_LENGTH 256 +#define LOVR_MAX_ATTRIBUTE_LENGTH 256 + +/////////////////////////////////////////////////// Types + +typedef struct { + GLchar name[LOVR_MAX_UNIFORM_LENGTH]; + GLenum glType; + int index; + int location; + int count; + int components; + size_t size; + UniformType type; + union { + void* data; + int* ints; + float* floats; + Texture** textures; + } value; + int baseTextureSlot; + bool dirty; +} Uniform; + +typedef map_t(Uniform) map_uniform_t; + +struct Shader { + Ref ref; + uint32_t program; + map_uniform_t uniforms; + map_int_t attributes; +}; + +struct Texture { + Ref ref; + TextureType type; + GLenum glType; + TextureData** slices; + int width; + int height; + int depth; + GLuint id; + TextureFilter filter; + TextureWrap wrap; + bool srgb; + bool mipmaps; + bool allocated; +}; + +struct Canvas { + Texture texture; + GLuint framebuffer; + GLuint resolveFramebuffer; + GLuint depthStencilBuffer; + GLuint msaaTexture; + CanvasFlags flags; + Canvas** attachments[MAX_CANVASES]; +}; + +typedef struct { + Mesh* mesh; + int attributeIndex; + int divisor; + bool enabled; +} MeshAttachment; + +typedef map_t(MeshAttachment) map_attachment_t; + +struct Mesh { + Ref ref; + uint32_t count; + VertexFormat format; + MeshDrawMode drawMode; + GLenum usage; + VertexPointer data; + IndexPointer indices; + uint32_t indexCount; + size_t indexSize; + size_t indexCapacity; + bool mappedIndices; + uint32_t dirtyStart; + uint32_t dirtyEnd; + uint32_t rangeStart; + uint32_t rangeCount; + GLuint vao; + GLuint vbo; + GLuint ibo; + Material* material; + float* pose; + map_attachment_t attachments; + MeshAttachment layout[MAX_ATTACHMENTS]; + bool isAttachment; +}; + +static struct { + Texture* defaultTexture; + BlendMode blendMode; + BlendAlphaMode blendAlphaMode; + bool culling; + bool depthEnabled; + CompareMode depthTest; + bool depthWrite; + float lineWidth; + bool stencilEnabled; + CompareMode stencilMode; + int stencilValue; + bool stencilWriting; + Winding winding; + bool wireframe; + uint32_t framebuffer; + uint32_t indexBuffer; + uint32_t program; + Texture* textures[MAX_TEXTURES]; + uint32_t vertexArray; + uint32_t vertexBuffer; + uint32_t viewport[4]; + bool srgb; + GraphicsLimits limits; + GraphicsStats stats; +} state; + +static void gammaCorrectColor(Color* color) { + if (state.srgb) { + color->r = lovrMathGammaToLinear(color->r); + color->g = lovrMathGammaToLinear(color->g); + color->b = lovrMathGammaToLinear(color->b); + } +} + +static GLenum convertCompareMode(CompareMode mode) { + switch (mode) { + case COMPARE_NONE: return GL_ALWAYS; + case COMPARE_EQUAL: return GL_EQUAL; + case COMPARE_NEQUAL: return GL_NOTEQUAL; + case COMPARE_LESS: return GL_LESS; + case COMPARE_LEQUAL: return GL_LEQUAL; + case COMPARE_GREATER: return GL_GREATER; + case COMPARE_GEQUAL: return GL_GEQUAL; + } +} + +GLenum lovrConvertWrapMode(WrapMode mode); +GLenum lovrConvertTextureFormat(TextureFormat format); +GLenum lovrConvertTextureFormatInternal(TextureFormat format, bool srgb); +GLenum lovrConvertMeshUsage(MeshUsage usage); +GLenum lovrConvertMeshDrawMode(MeshDrawMode mode); +bool lovrIsTextureFormatCompressed(TextureFormat format); + +//////////////////////////////////////////////////////////////////// Texture + +GLenum lovrConvertWrapMode(WrapMode mode) { + switch (mode) { + case WRAP_CLAMP: return GL_CLAMP_TO_EDGE; + case WRAP_REPEAT: return GL_REPEAT; + case WRAP_MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; + } +} + +GLenum lovrConvertTextureFormat(TextureFormat format) { + switch (format) { + case FORMAT_RGB: return GL_RGB; + case FORMAT_RGBA: return GL_RGBA; + case FORMAT_RGBA16F: return GL_RGBA; + case FORMAT_RGBA32F: return GL_RGBA; + case FORMAT_RG11B10F: return GL_RGB; + case FORMAT_DXT1: return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + case FORMAT_DXT3: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + case FORMAT_DXT5: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } +} + +GLenum lovrConvertTextureFormatInternal(TextureFormat format, bool srgb) { + switch (format) { + case FORMAT_RGB: return srgb ? GL_SRGB8 : GL_RGB8; + case FORMAT_RGBA: return srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8; + case FORMAT_RGBA16F: return GL_RGBA16F; + case FORMAT_RGBA32F: return GL_RGBA32F; + case FORMAT_RG11B10F: return GL_R11F_G11F_B10F; + case FORMAT_DXT1: return srgb ? GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + case FORMAT_DXT3: return srgb ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT : GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + case FORMAT_DXT5: return srgb ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } +} + +bool lovrIsTextureFormatCompressed(TextureFormat format) { + switch (format) { + case FORMAT_DXT1: + case FORMAT_DXT3: + case FORMAT_DXT5: + return true; + default: + return false; + } +} + +static void lovrTextureAllocate(Texture* texture, TextureData* textureData) { + texture->allocated = true; + texture->width = textureData->width; + texture->height = textureData->height; + + if (lovrIsTextureFormatCompressed(textureData->format)) { + return; + } + + int w = textureData->width; + int h = textureData->height; + int mipmapCount = log2(MAX(w, h)) + 1; + bool srgb = lovrGraphicsIsGammaCorrect() && texture->srgb; + GLenum glFormat = lovrConvertTextureFormat(textureData->format); + GLenum internalFormat = lovrConvertTextureFormatInternal(textureData->format, srgb); +#ifndef EMSCRIPTEN + if (GLAD_GL_ARB_texture_storage) { +#endif + if (texture->type == TEXTURE_ARRAY) { + glTexStorage3D(texture->glType, mipmapCount, internalFormat, w, h, texture->depth); + } else { + glTexStorage2D(texture->glType, mipmapCount, internalFormat, w, h); + } +#ifndef EMSCRIPTEN + } else { + for (int i = 0; i < mipmapCount; i++) { + switch (texture->type) { + case TEXTURE_2D: + glTexImage2D(texture->glType, i, internalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, NULL); + break; + + case TEXTURE_CUBE: + for (int face = 0; face < 6; face++) { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, i, internalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, NULL); + } + break; + + case TEXTURE_ARRAY: + case TEXTURE_VOLUME: + glTexImage3D(texture->glType, i, internalFormat, w, h, texture->depth, 0, glFormat, GL_UNSIGNED_BYTE, NULL); + break; + } + w = MAX(w >> 1, 1); + h = MAX(h >> 1, 1); + } + } +#endif +} + +Texture* lovrTextureCreate(TextureType type, TextureData** slices, int depth, bool srgb, bool mipmaps) { + Texture* texture = lovrAlloc(sizeof(Texture), lovrTextureDestroy); + if (!texture) return NULL; + + texture->type = type; + switch (type) { + case TEXTURE_2D: texture->glType = GL_TEXTURE_2D; break; + case TEXTURE_ARRAY: texture->glType = GL_TEXTURE_2D_ARRAY; break; + case TEXTURE_CUBE: texture->glType = GL_TEXTURE_CUBE_MAP; break; + case TEXTURE_VOLUME: texture->glType = GL_TEXTURE_3D; break; + } + + texture->slices = calloc(depth, sizeof(TextureData**)); + texture->depth = depth; + texture->srgb = srgb; + texture->mipmaps = mipmaps; + + WrapMode wrap = type == TEXTURE_CUBE ? WRAP_CLAMP : WRAP_REPEAT; + glGenTextures(1, &texture->id); + gpuBindTexture(texture, 0); + lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter()); + lovrTextureSetWrap(texture, (TextureWrap) { .s = wrap, .t = wrap, .r = wrap }); + + lovrAssert(type != TEXTURE_CUBE || depth == 6, "6 images are required for a cube texture\n"); + lovrAssert(type != TEXTURE_2D || depth == 1, "2D textures can only contain a single image"); + + if (slices) { + for (int i = 0; i < depth; i++) { + lovrTextureReplacePixels(texture, slices[i], i); + } + } + + return texture; +} + +void lovrTextureDestroy(void* ref) { + Texture* texture = ref; + for (int i = 0; i < texture->depth; i++) { + lovrRelease(texture->slices[i]); + } + glDeleteTextures(1, &texture->id); + free(texture->slices); + free(texture); +} + +GLuint lovrTextureGetId(Texture* texture) { + return texture->id; +} + +int lovrTextureGetWidth(Texture* texture) { + return texture->width; +} + +int lovrTextureGetHeight(Texture* texture) { + return texture->height; +} + +int lovrTextureGetDepth(Texture* texture) { + return texture->depth; +} + +TextureType lovrTextureGetType(Texture* texture) { + return texture->type; +} + +void lovrTextureReplacePixels(Texture* texture, TextureData* textureData, int slice) { + lovrRetain(textureData); + lovrRelease(texture->slices[slice]); + texture->slices[slice] = textureData; + + if (!texture->allocated) { + lovrAssert(texture->type != TEXTURE_CUBE || textureData->width == textureData->height, "Cubemap images must be square"); + lovrTextureAllocate(texture, textureData); + } else { + lovrAssert(textureData->width == texture->width && textureData->height == texture->height, "All texture slices must have the same dimensions"); + } + + if (!textureData->blob.data) { + return; + } + + GLenum glFormat = lovrConvertTextureFormat(textureData->format); + GLenum glInternalFormat = lovrConvertTextureFormatInternal(textureData->format, texture->srgb); + GLenum binding = (texture->type == TEXTURE_CUBE) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice : texture->glType; + + if (lovrIsTextureFormatCompressed(textureData->format)) { + Mipmap m; int i; + vec_foreach(&textureData->mipmaps, m, i) { + switch (texture->type) { + case TEXTURE_2D: + case TEXTURE_CUBE: + glCompressedTexImage2D(binding, i, glInternalFormat, m.width, m.height, 0, m.size, m.data); + break; + case TEXTURE_ARRAY: + case TEXTURE_VOLUME: + glCompressedTexSubImage3D(binding, i, 0, 0, slice, m.width, m.height, 1, glInternalFormat, m.size, m.data); + break; + } + } + } else { + switch (texture->type) { + case TEXTURE_2D: + case TEXTURE_CUBE: + glTexSubImage2D(binding, 0, 0, 0, textureData->width, textureData->height, glFormat, GL_UNSIGNED_BYTE, textureData->blob.data); + break; + case TEXTURE_ARRAY: + case TEXTURE_VOLUME: + glTexSubImage3D(binding, 0, 0, 0, slice, textureData->width, textureData->height, 1, glFormat, GL_UNSIGNED_BYTE, textureData->blob.data); + break; + } + + if (texture->mipmaps) { + glGenerateMipmap(texture->glType); + } + } +} + +TextureFilter lovrTextureGetFilter(Texture* texture) { + return texture->filter; +} + +void lovrTextureSetFilter(Texture* texture, TextureFilter filter) { + float anisotropy = filter.mode == FILTER_ANISOTROPIC ? MAX(filter.anisotropy, 1.) : 1.; + gpuBindTexture(texture, 0); + texture->filter = filter; + + switch (filter.mode) { + case FILTER_NEAREST: + glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + break; + + case FILTER_BILINEAR: + if (texture->mipmaps) { + glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); + glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + break; + + case FILTER_TRILINEAR: + case FILTER_ANISOTROPIC: + if (texture->mipmaps) { + glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + break; + } + + glTexParameteri(texture->glType, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy); +} + +TextureWrap lovrTextureGetWrap(Texture* texture) { + return texture->wrap; +} + +void lovrTextureSetWrap(Texture* texture, TextureWrap wrap) { + texture->wrap = wrap; + gpuBindTexture(texture, 0); + glTexParameteri(texture->glType, GL_TEXTURE_WRAP_S, lovrConvertWrapMode(wrap.s)); + glTexParameteri(texture->glType, GL_TEXTURE_WRAP_T, lovrConvertWrapMode(wrap.t)); + if (texture->type == TEXTURE_CUBE || texture->type == TEXTURE_VOLUME) { + glTexParameteri(texture->glType, GL_TEXTURE_WRAP_R, lovrConvertWrapMode(wrap.r)); + } +} + +///////////////////////////////////// Canvas + +bool lovrCanvasSupportsFormat(TextureFormat format) { + switch (format) { + case FORMAT_RGB: + case FORMAT_RGBA: + case FORMAT_RGBA16F: + case FORMAT_RGBA32F: + case FORMAT_RG11B10F: + return true; + case FORMAT_DXT1: + case FORMAT_DXT3: + case FORMAT_DXT5: + return false; + } +} + +Canvas* lovrCanvasCreate(int width, int height, TextureFormat format, CanvasFlags flags) { + TextureData* textureData = lovrTextureDataGetEmpty(width, height, format); + Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, flags.mipmaps); + if (!texture) return NULL; + + Canvas* canvas = lovrAlloc(sizeof(Canvas), lovrCanvasDestroy); + canvas->texture = *texture; + canvas->flags = flags; + + // Framebuffer + glGenFramebuffers(1, &canvas->framebuffer); + gpuBindFramebuffer(canvas->framebuffer); + + // Color attachment + if (flags.msaa > 0) { + GLenum internalFormat = lovrConvertTextureFormatInternal(format, lovrGraphicsIsGammaCorrect()); + glGenRenderbuffers(1, &canvas->msaaTexture); + glBindRenderbuffer(GL_RENDERBUFFER, canvas->msaaTexture); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, flags.msaa, internalFormat, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, canvas->msaaTexture); + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas->texture.id, 0); + } + + // Depth/Stencil + if (flags.depth || flags.stencil) { + GLenum depthStencilFormat = flags.stencil ? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT24; + glGenRenderbuffers(1, &canvas->depthStencilBuffer); + glBindRenderbuffer(GL_RENDERBUFFER, canvas->depthStencilBuffer); + if (flags.msaa > 0) { + glRenderbufferStorageMultisample(GL_RENDERBUFFER, flags.msaa, depthStencilFormat, width, height); + } else { + glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, width, height); + } + + if (flags.depth) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, canvas->depthStencilBuffer); + } + + if (flags.stencil) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, canvas->depthStencilBuffer); + } + } + + // Resolve framebuffer + if (flags.msaa > 0) { + glGenFramebuffers(1, &canvas->resolveFramebuffer); + gpuBindFramebuffer(canvas->resolveFramebuffer); + glBindTexture(GL_TEXTURE_2D, canvas->texture.id); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas->texture.id, 0); + gpuBindFramebuffer(canvas->framebuffer); + } + + lovrAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Error creating Canvas"); + lovrGraphicsClear(&(Color) { 0, 0, 0, 0 }, &(float) { 1. }, &(int) { 0 }); + gpuBindFramebuffer(0); + + return canvas; +} + +void lovrCanvasDestroy(void* ref) { + Canvas* canvas = ref; + glDeleteFramebuffers(1, &canvas->framebuffer); + if (canvas->resolveFramebuffer) { + glDeleteFramebuffers(1, &canvas->resolveFramebuffer); + } + if (canvas->depthStencilBuffer) { + glDeleteRenderbuffers(1, &canvas->depthStencilBuffer); + } + if (canvas->msaaTexture) { + glDeleteTextures(1, &canvas->msaaTexture); + } + lovrTextureDestroy(ref); +} + +uint32_t lovrCanvasGetId(Canvas* canvas) { + return canvas->framebuffer; +} + +void lovrCanvasBind(Canvas** canvases, int canvasCount) { + if (canvasCount == 0) { + gpuBindFramebuffer(0); + return; + } + + gpuBindFramebuffer(canvases[0]->texture.id); + if (memcmp(canvases, canvases[0]->attachments, MAX_CANVASES * sizeof(Canvas*))) { + memcpy(canvases[0]->attachments, canvases, MAX_CANVASES * sizeof(Canvas*)); + + GLenum buffers[MAX_CANVASES]; + for (int i = 0; i < canvasCount; i++) { + buffers[i] = GL_COLOR_ATTACHMENT0 + i; + glFramebufferTexture2D(GL_FRAMEBUFFER, buffers[i], GL_TEXTURE_2D, lovrTextureGetId((Texture*) canvases[i]), 0); + } + glDrawBuffers(canvasCount, buffers); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + lovrAssert(status != GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS, "All multicanvas canvases must have the same dimensions"); + lovrAssert(status == GL_FRAMEBUFFER_COMPLETE, "Unable to bind framebuffer"); + } +} + +void lovrCanvasResolve(Canvas* canvas) { + if (canvas->flags.msaa > 0) { + int width = canvas->texture.width; + int height = canvas->texture.height; + glBindFramebuffer(GL_READ_FRAMEBUFFER, canvas->framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, canvas->resolveFramebuffer); + glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } + + if (canvas->flags.mipmaps) { + gpuBindTexture(&canvas->texture, 0); + glGenerateMipmap(canvas->texture.glType); + } +} + +TextureFormat lovrCanvasGetFormat(Canvas* canvas) { + return canvas->texture.slices[0]->format; +} + +int lovrCanvasGetMSAA(Canvas* canvas) { + return canvas->flags.msaa; +} + +TextureData* lovrCanvasNewTextureData(Canvas* canvas) { + TextureData* textureData = lovrTextureDataGetBlank(canvas->texture.width, canvas->texture.height, 0, FORMAT_RGBA); + if (!textureData) return NULL; + + gpuBindFramebuffer(canvas->framebuffer); + glReadPixels(0, 0, canvas->texture.width, canvas->texture.height, GL_RGBA, GL_UNSIGNED_BYTE, textureData->blob.data); + + return textureData; +} + +///////////////////////////////////////////////////////////////////// Shader + +static UniformType getUniformType(GLenum type, const char* debug) { + switch (type) { + case GL_FLOAT: + case GL_FLOAT_VEC2: + case GL_FLOAT_VEC3: + case GL_FLOAT_VEC4: + return UNIFORM_FLOAT; + + case GL_INT: + case GL_INT_VEC2: + case GL_INT_VEC3: + case GL_INT_VEC4: + return UNIFORM_INT; + + case GL_FLOAT_MAT2: + case GL_FLOAT_MAT3: + case GL_FLOAT_MAT4: + return UNIFORM_MATRIX; + + case GL_SAMPLER_2D: + case GL_SAMPLER_3D: + case GL_SAMPLER_CUBE: + case GL_SAMPLER_2D_ARRAY: + return UNIFORM_SAMPLER; + + default: + lovrThrow("Unsupported type for uniform '%s'", debug); + return UNIFORM_FLOAT; + } +} + +static int getUniformComponents(GLenum type) { + switch (type) { + case GL_FLOAT: + case GL_INT: + case GL_SAMPLER_2D: + case GL_SAMPLER_3D: + case GL_SAMPLER_CUBE: + case GL_SAMPLER_2D_ARRAY: + return 1; + + case GL_FLOAT_VEC2: + case GL_INT_VEC2: + case GL_FLOAT_MAT2: + return 2; + + case GL_FLOAT_VEC3: + case GL_INT_VEC3: + case GL_FLOAT_MAT3: + return 3; + + case GL_FLOAT_VEC4: + case GL_INT_VEC4: + case GL_FLOAT_MAT4: + return 4; + + default: + return 1; + } +} + +static GLuint compileShader(GLenum type, const char* source) { + GLuint shader = glCreateShader(type); + + glShaderSource(shader, 1, (const GLchar**) &source, NULL); + glCompileShader(shader); + + int isShaderCompiled; + glGetShaderiv(shader, GL_COMPILE_STATUS, &isShaderCompiled); + if (!isShaderCompiled) { + int logLength; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + + char* log = malloc(logLength); + glGetShaderInfoLog(shader, logLength, &logLength, log); + lovrThrow("Could not compile shader %s", log); + } + + return shader; +} + +static GLuint linkShaders(GLuint vertexShader, GLuint fragmentShader) { + GLuint program = glCreateProgram(); + + if (vertexShader) { + glAttachShader(program, vertexShader); + } + + if (fragmentShader) { + glAttachShader(program, fragmentShader); + } + + glBindAttribLocation(program, LOVR_SHADER_POSITION, "lovrPosition"); + glBindAttribLocation(program, LOVR_SHADER_NORMAL, "lovrNormal"); + glBindAttribLocation(program, LOVR_SHADER_TEX_COORD, "lovrTexCoord"); + glBindAttribLocation(program, LOVR_SHADER_VERTEX_COLOR, "lovrVertexColor"); + glBindAttribLocation(program, LOVR_SHADER_TANGENT, "lovrTangent"); + glBindAttribLocation(program, LOVR_SHADER_BONES, "lovrBones"); + glBindAttribLocation(program, LOVR_SHADER_BONE_WEIGHTS, "lovrBoneWeights"); + glLinkProgram(program); + + int isLinked; + glGetProgramiv(program, GL_LINK_STATUS, &isLinked); + if (!isLinked) { + int logLength; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength); + + char* log = malloc(logLength); + glGetProgramInfoLog(program, logLength, &logLength, log); + lovrThrow("Could not link shader %s", log); + } + + glDetachShader(program, vertexShader); + glDeleteShader(vertexShader); + glDetachShader(program, fragmentShader); + glDeleteShader(fragmentShader); + + return program; +} + +Shader* lovrShaderCreate(const char* vertexSource, const char* fragmentSource) { + Shader* shader = lovrAlloc(sizeof(Shader), lovrShaderDestroy); + if (!shader) return NULL; + + char source[8192]; + + // Vertex + vertexSource = vertexSource == NULL ? lovrDefaultVertexShader : vertexSource; + snprintf(source, sizeof(source), "%s%s\n%s", lovrShaderVertexPrefix, vertexSource, lovrShaderVertexSuffix); + GLuint vertexShader = compileShader(GL_VERTEX_SHADER, source); + + // Fragment + fragmentSource = fragmentSource == NULL ? lovrDefaultFragmentShader : fragmentSource; + snprintf(source, sizeof(source), "%s%s\n%s", lovrShaderFragmentPrefix, fragmentSource, lovrShaderFragmentSuffix); + GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, source); + + // Link + uint32_t program = linkShaders(vertexShader, fragmentShader); + shader->program = program; + + gpuUseProgram(program); + glVertexAttrib4fv(LOVR_SHADER_VERTEX_COLOR, (float[4]) { 1., 1., 1., 1. }); + glVertexAttribI4iv(LOVR_SHADER_BONES, (int[4]) { 0., 0., 0., 0. }); + glVertexAttrib4fv(LOVR_SHADER_BONE_WEIGHTS, (float[4]) { 1., 0., 0., 0. }); + + // Uniform introspection + int32_t uniformCount; + int textureSlot = 0; + map_init(&shader->uniforms); + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniformCount); + for (int i = 0; i < uniformCount; i++) { + Uniform uniform; + glGetActiveUniform(program, i, LOVR_MAX_UNIFORM_LENGTH, NULL, &uniform.count, &uniform.glType, uniform.name); + + char* subscript = strchr(uniform.name, '['); + if (subscript) { + *subscript = '\0'; + } + + uniform.index = i; + uniform.location = glGetUniformLocation(program, uniform.name); + uniform.type = getUniformType(uniform.glType, uniform.name); + uniform.components = getUniformComponents(uniform.glType); + uniform.baseTextureSlot = (uniform.type == UNIFORM_SAMPLER) ? textureSlot : -1; + + if (uniform.location == -1) { + continue; + } + + switch (uniform.type) { + case UNIFORM_FLOAT: + uniform.size = uniform.components * uniform.count * sizeof(float); + uniform.value.data = calloc(1, uniform.size); + break; + + case UNIFORM_INT: + uniform.size = uniform.components * uniform.count * sizeof(int); + uniform.value.data = calloc(1, uniform.size); + break; + + case UNIFORM_MATRIX: + uniform.size = uniform.components * uniform.components * uniform.count * sizeof(int); + uniform.value.data = calloc(1, uniform.size); + break; + + case UNIFORM_SAMPLER: + uniform.size = uniform.components * uniform.count * MAX(sizeof(Texture*), sizeof(int)); + uniform.value.data = calloc(1, uniform.size); + + // Use the value for ints to bind texture slots, but use the value for textures afterwards. + for (int i = 0; i < uniform.count; i++) { + uniform.value.ints[i] = uniform.baseTextureSlot + i; + } + glUniform1iv(uniform.location, uniform.count, uniform.value.ints); + break; + } + + size_t offset = 0; + for (int j = 0; j < uniform.count; j++) { + int location = uniform.location; + + if (uniform.count > 1) { + char name[LOVR_MAX_UNIFORM_LENGTH]; + snprintf(name, LOVR_MAX_UNIFORM_LENGTH, "%s[%d]", uniform.name, j); + location = glGetUniformLocation(program, name); + } + + switch (uniform.type) { + case UNIFORM_FLOAT: + glGetUniformfv(program, location, &uniform.value.floats[offset]); + offset += uniform.components; + break; + + case UNIFORM_INT: + glGetUniformiv(program, location, &uniform.value.ints[offset]); + offset += uniform.components; + break; + + case UNIFORM_MATRIX: + glGetUniformfv(program, location, &uniform.value.floats[offset]); + offset += uniform.components * uniform.components; + break; + + default: + break; + } + } + + map_set(&shader->uniforms, uniform.name, uniform); + textureSlot += (uniform.type == UNIFORM_SAMPLER) ? uniform.count : 0; + } + + // Attribute cache + int32_t attributeCount; + glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &attributeCount); + map_init(&shader->attributes); + for (int i = 0; i < attributeCount; i++) { + char name[LOVR_MAX_ATTRIBUTE_LENGTH]; + GLint size; + GLenum type; + glGetActiveAttrib(program, i, LOVR_MAX_ATTRIBUTE_LENGTH, NULL, &size, &type, name); + map_set(&shader->attributes, name, glGetAttribLocation(program, name)); + } + + return shader; +} + +Shader* lovrShaderCreateDefault(DefaultShader type) { + switch (type) { + case SHADER_DEFAULT: return lovrShaderCreate(NULL, NULL); + case SHADER_CUBE: return lovrShaderCreate(lovrCubeVertexShader, lovrCubeFragmentShader); break; + case SHADER_PANO: return lovrShaderCreate(lovrCubeVertexShader, lovrPanoFragmentShader); break; + case SHADER_FONT: return lovrShaderCreate(NULL, lovrFontFragmentShader); + case SHADER_FILL: return lovrShaderCreate(lovrFillVertexShader, NULL); + default: lovrThrow("Unknown default shader type"); return NULL; + } +} + +void lovrShaderDestroy(void* ref) { + Shader* shader = ref; + glDeleteProgram(shader->program); + map_deinit(&shader->uniforms); + map_deinit(&shader->attributes); + free(shader); +} + +uint32_t lovrShaderGetProgram(Shader* shader) { + return shader->program; +} + +void lovrShaderBind(Shader* shader) { + map_iter_t iter = map_iter(&shader->uniforms); + const char* key; + while ((key = map_next(&shader->uniforms, &iter)) != NULL) { + Uniform* uniform = map_get(&shader->uniforms, key); + + if (uniform->type != UNIFORM_SAMPLER && !uniform->dirty) { + continue; + } + + uniform->dirty = false; + int count = uniform->count; + void* data = uniform->value.data; + + switch (uniform->type) { + case UNIFORM_FLOAT: + switch (uniform->components) { + case 1: glUniform1fv(uniform->location, count, data); break; + case 2: glUniform2fv(uniform->location, count, data); break; + case 3: glUniform3fv(uniform->location, count, data); break; + case 4: glUniform4fv(uniform->location, count, data); break; + } + break; + + case UNIFORM_INT: + switch (uniform->components) { + case 1: glUniform1iv(uniform->location, count, data); break; + case 2: glUniform2iv(uniform->location, count, data); break; + case 3: glUniform3iv(uniform->location, count, data); break; + case 4: glUniform4iv(uniform->location, count, data); break; + } + break; + + case UNIFORM_MATRIX: + switch (uniform->components) { + case 2: glUniformMatrix2fv(uniform->location, count, GL_FALSE, data); break; + case 3: glUniformMatrix3fv(uniform->location, count, GL_FALSE, data); break; + case 4: glUniformMatrix4fv(uniform->location, count, GL_FALSE, data); break; + } + break; + + case UNIFORM_SAMPLER: + for (int i = 0; i < count; i++) { + GLenum uniformTextureType; + switch (uniform->glType) { + case GL_SAMPLER_2D: uniformTextureType = GL_TEXTURE_2D; break; + case GL_SAMPLER_3D: uniformTextureType = GL_TEXTURE_3D; break; + case GL_SAMPLER_CUBE: uniformTextureType = GL_TEXTURE_CUBE_MAP; break; + case GL_SAMPLER_2D_ARRAY: uniformTextureType = GL_TEXTURE_2D_ARRAY; break; + } + gpuBindTexture(uniform->value.textures[i], uniform->baseTextureSlot + i); + } + break; + } + } +} + +int lovrShaderGetAttributeId(Shader* shader, const char* name) { + int* id = map_get(&shader->attributes, name); + return id ? *id : -1; +} + +bool lovrShaderHasUniform(Shader* shader, const char* name) { + return map_get(&shader->uniforms, name) != NULL; +} + +bool lovrShaderGetUniform(Shader* shader, const char* name, int* count, int* components, size_t* size, UniformType* type) { + Uniform* uniform = map_get(&shader->uniforms, name); + if (!uniform) { + return false; + } + + *count = uniform->count; + *components = uniform->components; + *size = uniform->size; + *type = uniform->type; + return true; +} + +static void lovrShaderSetUniform(Shader* shader, const char* name, UniformType type, void* data, int count, size_t size, const char* debug) { + Uniform* uniform = map_get(&shader->uniforms, name); + if (!uniform) { + return; + } + + const char* plural = (uniform->size / size) > 1 ? "s" : ""; + lovrAssert(uniform->type == type, "Unable to send %ss to uniform %s", debug, uniform->name); + lovrAssert(count * size <= uniform->size, "Expected at most %d %s%s for uniform %s, got %d", uniform->size / size, debug, plural, uniform->name, count); + + if (!uniform->dirty && !memcmp(uniform->value.data, data, count * size)) { + return; + } + + memcpy(uniform->value.data, data, count * size); + uniform->dirty = true; +} + +void lovrShaderSetFloat(Shader* shader, const char* name, float* data, int count) { + lovrShaderSetUniform(shader, name, UNIFORM_FLOAT, data, count, sizeof(float), "float"); +} + +void lovrShaderSetInt(Shader* shader, const char* name, int* data, int count) { + lovrShaderSetUniform(shader, name, UNIFORM_INT, data, count, sizeof(int), "int"); +} + +void lovrShaderSetMatrix(Shader* shader, const char* name, float* data, int count) { + lovrShaderSetUniform(shader, name, UNIFORM_MATRIX, data, count, sizeof(float), "float"); +} + +void lovrShaderSetTexture(Shader* shader, const char* name, Texture** data, int count) { + lovrShaderSetUniform(shader, name, UNIFORM_SAMPLER, data, count, sizeof(Texture*), "texture"); +} + +///////////////////////////////////////////////////////////////// Mesh + +GLenum lovrConvertMeshUsage(MeshUsage usage) { + switch (usage) { + case MESH_STATIC: return GL_STATIC_DRAW; + case MESH_DYNAMIC: return GL_DYNAMIC_DRAW; + case MESH_STREAM: return GL_STREAM_DRAW; + } +} + +GLenum lovrConvertMeshDrawMode(MeshDrawMode mode) { + switch (mode) { + case MESH_POINTS: return GL_POINTS; + case MESH_LINES: return GL_LINES; + case MESH_LINE_STRIP: return GL_LINE_STRIP; + case MESH_LINE_LOOP: return GL_LINE_LOOP; + case MESH_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP; + case MESH_TRIANGLES: return GL_TRIANGLES; + case MESH_TRIANGLE_FAN: return GL_TRIANGLE_FAN; + } +} + +Mesh* lovrMeshCreate(uint32_t count, VertexFormat format, MeshDrawMode drawMode, MeshUsage usage) { + Mesh* mesh = lovrAlloc(sizeof(Mesh), lovrMeshDestroy); + if (!mesh) return NULL; + + mesh->count = count; + mesh->format = format; + mesh->drawMode = drawMode; + mesh->usage = lovrConvertMeshUsage(usage); + + glGenBuffers(1, &mesh->vbo); + glGenBuffers(1, &mesh->ibo); + gpuBindVertexBuffer(mesh->vbo); + glBufferData(GL_ARRAY_BUFFER, count * format.stride, NULL, mesh->usage); + glGenVertexArrays(1, &mesh->vao); + + map_init(&mesh->attachments); + for (int i = 0; i < format.count; i++) { + map_set(&mesh->attachments, format.attributes[i].name, ((MeshAttachment) { mesh, i, 0, true })); + } + + mesh->data.raw = calloc(count, format.stride); + + return mesh; +} + +void lovrMeshDestroy(void* ref) { + Mesh* mesh = ref; + lovrRelease(mesh->material); + free(mesh->data.raw); + free(mesh->indices.raw); + glDeleteBuffers(1, &mesh->vbo); + glDeleteBuffers(1, &mesh->ibo); + glDeleteVertexArrays(1, &mesh->vao); + const char* key; + map_iter_t iter = map_iter(&mesh->attachments); + while ((key = map_next(&mesh->attachments, &iter)) != NULL) { + MeshAttachment* attachment = map_get(&mesh->attachments, key); + if (attachment->mesh != mesh) { + lovrRelease(attachment->mesh); + } + } + map_deinit(&mesh->attachments); + free(mesh); +} + +void lovrMeshAttachAttribute(Mesh* mesh, Mesh* other, const char* name, int divisor) { + MeshAttachment* otherAttachment = map_get(&other->attachments, name); + lovrAssert(!mesh->isAttachment, "Attempted to attach to a mesh which is an attachment itself"); + lovrAssert(otherAttachment, "No attribute named '%s' exists", name); + lovrAssert(!map_get(&mesh->attachments, name), "Mesh already has an attribute named '%s'", name); + lovrAssert(divisor >= 0, "Divisor can't be negative"); + + MeshAttachment attachment = { other, otherAttachment->attributeIndex, divisor, true }; + map_set(&mesh->attachments, name, attachment); + other->isAttachment = true; + lovrRetain(other); +} + +void lovrMeshDetachAttribute(Mesh* mesh, const char* name) { + MeshAttachment* attachment = map_get(&mesh->attachments, name); + lovrAssert(attachment, "No attached attribute '%s' was found", name); + lovrAssert(attachment->mesh != mesh, "Attribute '%s' was not attached from another Mesh", name); + lovrRelease(attachment->mesh); + map_remove(&mesh->attachments, name); +} + +void lovrMeshBind(Mesh* mesh, Shader* shader) { + const char* key; + map_iter_t iter = map_iter(&mesh->attachments); + + MeshAttachment layout[MAX_ATTACHMENTS]; + memset(layout, 0, MAX_ATTACHMENTS * sizeof(MeshAttachment)); + + gpuBindVertexArray(mesh->vao); + lovrMeshUnmapVertices(mesh); + lovrMeshUnmapIndices(mesh); + if (mesh->indexCount > 0) { + gpuBindIndexBuffer(mesh->ibo); + } + + while ((key = map_next(&mesh->attachments, &iter)) != NULL) { + int location = lovrShaderGetAttributeId(shader, key); + + if (location >= 0) { + MeshAttachment* attachment = map_get(&mesh->attachments, key); + layout[location] = *attachment; + lovrMeshUnmapVertices(attachment->mesh); + lovrMeshUnmapIndices(attachment->mesh); + } + } + + for (int i = 0; i < MAX_ATTACHMENTS; i++) { + MeshAttachment previous = mesh->layout[i]; + MeshAttachment current = layout[i]; + + if (!memcmp(&previous, ¤t, sizeof(MeshAttachment))) { + continue; + } + + if (previous.enabled != current.enabled) { + if (current.enabled) { + glEnableVertexAttribArray(i); + } else { + glDisableVertexAttribArray(i); + mesh->layout[i] = current; + continue; + } + } + + if (previous.divisor != current.divisor) { + glVertexAttribDivisor(i, current.divisor); + } + + if (previous.mesh != current.mesh || previous.attributeIndex != current.attributeIndex) { + gpuBindVertexBuffer(current.mesh->vbo); + VertexFormat* format = ¤t.mesh->format; + Attribute attribute = format->attributes[current.attributeIndex]; + switch (attribute.type) { + case ATTR_FLOAT: + glVertexAttribPointer(i, attribute.count, GL_FLOAT, GL_TRUE, format->stride, (void*) attribute.offset); + break; + + case ATTR_BYTE: + glVertexAttribPointer(i, attribute.count, GL_UNSIGNED_BYTE, GL_TRUE, format->stride, (void*) attribute.offset); + break; + + case ATTR_INT: + glVertexAttribIPointer(i, attribute.count, GL_UNSIGNED_INT, format->stride, (void*) attribute.offset); + break; + } + } + + mesh->layout[i] = current; + } +} + +VertexFormat* lovrMeshGetVertexFormat(Mesh* mesh) { + return &mesh->format; +} + +MeshDrawMode lovrMeshGetDrawMode(Mesh* mesh) { + return mesh->drawMode; +} + +void lovrMeshSetDrawMode(Mesh* mesh, MeshDrawMode drawMode) { + mesh->drawMode = drawMode; +} + +int lovrMeshGetVertexCount(Mesh* mesh) { + return mesh->count; +} + +bool lovrMeshIsAttributeEnabled(Mesh* mesh, const char* name) { + MeshAttachment* attachment = map_get(&mesh->attachments, name); + lovrAssert(attachment, "Mesh does not have an attribute named '%s'", name); + return attachment->enabled; +} + +void lovrMeshSetAttributeEnabled(Mesh* mesh, const char* name, bool enable) { + MeshAttachment* attachment = map_get(&mesh->attachments, name); + lovrAssert(attachment, "Mesh does not have an attribute named '%s'", name); + attachment->enabled = enable; +} + +void lovrMeshGetDrawRange(Mesh* mesh, uint32_t* start, uint32_t* count) { + *start = mesh->rangeStart; + *count = mesh->rangeCount; +} + +void lovrMeshSetDrawRange(Mesh* mesh, uint32_t start, uint32_t count) { + uint32_t limit = mesh->indexCount > 0 ? mesh->indexCount : mesh->count; + lovrAssert(start + count <= limit, "Invalid mesh draw range [%d, %d]", start + 1, start + count + 1); + mesh->rangeStart = start; + mesh->rangeCount = count; +} + +Material* lovrMeshGetMaterial(Mesh* mesh) { + return mesh->material; +} + +void lovrMeshSetMaterial(Mesh* mesh, Material* material) { + if (mesh->material != material) { + lovrRetain(material); + lovrRelease(mesh->material); + mesh->material = material; + } +} + +float* lovrMeshGetPose(Mesh* mesh) { + return mesh->pose; +} + +void lovrMeshSetPose(Mesh* mesh, float* pose) { + mesh->pose = pose; +} + +VertexPointer lovrMeshMapVertices(Mesh* mesh, uint32_t start, uint32_t count, bool read, bool write) { + if (write) { + mesh->dirtyStart = MIN(mesh->dirtyStart, start); + mesh->dirtyEnd = MAX(mesh->dirtyEnd, start + count); + } + + return (VertexPointer) { .bytes = mesh->data.bytes + start * mesh->format.stride }; +} + +void lovrMeshUnmapVertices(Mesh* mesh) { + if (mesh->dirtyEnd == 0) { + return; + } + + size_t stride = mesh->format.stride; + gpuBindVertexBuffer(mesh->vbo); + if (mesh->usage == MESH_STREAM) { + glBufferData(GL_ARRAY_BUFFER, mesh->count * stride, mesh->data.bytes, mesh->usage); + } else { + size_t offset = mesh->dirtyStart * stride; + size_t count = (mesh->dirtyEnd - mesh->dirtyStart) * stride; + glBufferSubData(GL_ARRAY_BUFFER, offset, count, mesh->data.bytes + offset); + } + + mesh->dirtyStart = INT_MAX; + mesh->dirtyEnd = 0; +} + +IndexPointer lovrMeshReadIndices(Mesh* mesh, uint32_t* count, size_t* size) { + *size = mesh->indexSize; + *count = mesh->indexCount; + + if (mesh->indexCount == 0) { + return (IndexPointer) { .raw = NULL }; + } else if (mesh->mappedIndices) { + lovrMeshUnmapIndices(mesh); + } + + return mesh->indices; +} + +IndexPointer lovrMeshWriteIndices(Mesh* mesh, uint32_t count, size_t size) { + if (mesh->mappedIndices) { + lovrMeshUnmapIndices(mesh); + } + + mesh->indexSize = size; + mesh->indexCount = count; + + if (count == 0) { + return (IndexPointer) { .raw = NULL }; + } + + gpuBindVertexArray(mesh->vao); + gpuBindIndexBuffer(mesh->ibo); + mesh->mappedIndices = true; + + if (mesh->indexCapacity < size * count) { + mesh->indexCapacity = nextPo2(size * count); + mesh->indices.raw = realloc(mesh->indices.raw, mesh->indexCapacity); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh->indexCapacity, NULL, mesh->usage); + } + + return mesh->indices; +} + +void lovrMeshUnmapIndices(Mesh* mesh) { + if (!mesh->mappedIndices) { + return; + } + + mesh->mappedIndices = false; + gpuBindIndexBuffer(mesh->ibo); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, mesh->indexCount * mesh->indexSize, mesh->indices.raw); +} + +void lovrMeshResize(Mesh* mesh, uint32_t count) { + if (mesh->count < count) { + mesh->count = nextPo2(count); + gpuBindVertexBuffer(mesh->vbo); + mesh->data.raw = realloc(mesh->data.raw, count * mesh->format.stride); + glBufferData(GL_ARRAY_BUFFER, count * mesh->format.stride, mesh->data.raw, mesh->usage); + } +} + +///////////////////////////////////////////////// GPU + +void gpuInit(bool srgb, gpuProc (*getProcAddress)(const char*)) { +#ifndef EMSCRIPTEN + gladLoadGLLoader((GLADloadproc) getProcAddress); + glEnable(GL_LINE_SMOOTH); + glEnable(GL_PROGRAM_POINT_SIZE); + if (srgb) { + glEnable(GL_FRAMEBUFFER_SRGB); + } else { + glDisable(GL_FRAMEBUFFER_SRGB); + } +#endif + glEnable(GL_BLEND); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + state.srgb = srgb; + state.blendMode = -1; + state.blendAlphaMode = -1; + state.culling = -1; + state.depthEnabled = -1; + state.depthTest = -1; + state.depthWrite = -1; + state.lineWidth = -1; + state.stencilEnabled = -1; + state.stencilMode = -1; + state.stencilValue = -1; + state.stencilWriting = false; + state.winding = -1; + state.wireframe = -1; +} + +void gpuDestroy() { + lovrRelease(state.defaultTexture); + for (int i = 0; i < MAX_TEXTURES; i++) { + lovrRelease(state.textures[i]); + } +} + +void gpuClear(Canvas** canvas, int canvasCount, Color* color, float* depth, int* stencil) { + gpuBindFramebuffer(canvasCount > 0 ? lovrCanvasGetId(canvas[0]) : 0); + + if (color) { + gammaCorrectColor(color); + float c[4] = { color->r, color->g, color->b, color->a }; + glClearBufferfv(GL_COLOR, 0, c); + for (int i = 1; i < canvasCount; i++) { + glClearBufferfv(GL_COLOR, i, c); + } + } + + if (depth) { + glClearBufferfv(GL_DEPTH, 0, depth); + } + + if (stencil) { + glClearBufferiv(GL_STENCIL, 0, stencil); + } +} + +void lovrGraphicsStencil(StencilAction action, int replaceValue, StencilCallback callback, void* userdata) { + state.depthWrite = false; + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + if (!state.stencilEnabled) { + state.stencilEnabled = true; + glEnable(GL_STENCIL_TEST); + } + + GLenum glAction; + switch (action) { + case STENCIL_REPLACE: glAction = GL_REPLACE; break; + case STENCIL_INCREMENT: glAction = GL_INCR; break; + case STENCIL_DECREMENT: glAction = GL_DECR; break; + case STENCIL_INCREMENT_WRAP: glAction = GL_INCR_WRAP; break; + case STENCIL_DECREMENT_WRAP: glAction = GL_DECR_WRAP; break; + case STENCIL_INVERT: glAction = GL_INVERT; break; + } + + glStencilFunc(GL_ALWAYS, replaceValue, 0xff); + glStencilOp(GL_KEEP, GL_KEEP, glAction); + + state.stencilWriting = true; + callback(userdata); + state.stencilWriting = false; + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + state.stencilMode = ~0; // Dirty +} + +void gpuDraw(DrawCommand* command) { + Mesh* mesh = command->mesh; + Material* material = command->material; + Shader* shader = command->shader; + Pipeline* pipeline = &command->pipeline; + int instances = command->instances; + + // Blend mode + if (state.blendMode != pipeline->blendMode || state.blendAlphaMode != pipeline->blendAlphaMode) { + state.blendMode = pipeline->blendMode; + state.blendAlphaMode = pipeline->blendAlphaMode; + + GLenum srcRGB = state.blendMode == BLEND_MULTIPLY ? GL_DST_COLOR : GL_ONE; + if (srcRGB == GL_ONE && state.blendAlphaMode == BLEND_ALPHA_MULTIPLY) { + srcRGB = GL_SRC_ALPHA; + } + + switch (state.blendMode) { + case BLEND_ALPHA: + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(srcRGB, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + break; + + case BLEND_ADD: + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(srcRGB, GL_ONE, GL_ZERO, GL_ONE); + break; + + case BLEND_SUBTRACT: + glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); + glBlendFuncSeparate(srcRGB, GL_ONE, GL_ZERO, GL_ONE); + break; + + case BLEND_MULTIPLY: + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(srcRGB, GL_ZERO, GL_DST_COLOR, GL_ZERO); + break; + + case BLEND_LIGHTEN: + glBlendEquation(GL_MAX); + glBlendFuncSeparate(srcRGB, GL_ZERO, GL_ONE, GL_ZERO); + break; + + case BLEND_DARKEN: + glBlendEquation(GL_MIN); + glBlendFuncSeparate(srcRGB, GL_ZERO, GL_ONE, GL_ZERO); + break; + + case BLEND_SCREEN: + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(srcRGB, GL_ONE_MINUS_SRC_COLOR, GL_ONE, GL_ONE_MINUS_SRC_COLOR); + break; + + case BLEND_REPLACE: + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(srcRGB, GL_ZERO, GL_ONE, GL_ZERO); + break; + } + } + + // Culling + if (state.culling != pipeline->culling) { + state.culling = pipeline->culling; + if (state.culling) { + glEnable(GL_CULL_FACE); + } else { + glDisable(GL_CULL_FACE); + } + } + + // Depth test + if (state.depthTest != pipeline->depthTest) { + state.depthTest = pipeline->depthTest; + if (state.depthTest != COMPARE_NONE) { + if (!state.depthEnabled) { + state.depthEnabled = true; + glEnable(GL_DEPTH_TEST); + } + glDepthFunc(convertCompareMode(state.depthTest)); + } else if (state.depthEnabled) { + state.depthEnabled = false; + glDisable(GL_DEPTH_TEST); + } + } + + // Depth write + if (state.depthWrite != pipeline->depthWrite) { + state.depthWrite = pipeline->depthWrite; + glDepthMask(state.depthWrite); + } + + // Line width + if (state.lineWidth != pipeline->lineWidth) { + state.lineWidth = state.lineWidth; + glLineWidth(state.lineWidth); + } + + // Stencil mode + if (!state.stencilWriting && (state.stencilMode != pipeline->stencilMode || state.stencilValue != pipeline->stencilValue)) { + state.stencilMode = pipeline->stencilMode; + state.stencilValue = pipeline->stencilValue; + if (state.stencilMode != COMPARE_NONE) { + if (!state.stencilEnabled) { + state.stencilEnabled = true; + glEnable(GL_STENCIL_TEST); + } + + GLenum glMode = GL_ALWAYS; + switch (state.stencilMode) { + case COMPARE_EQUAL: glMode = GL_EQUAL; break; + case COMPARE_NEQUAL: glMode = GL_NOTEQUAL; break; + case COMPARE_LESS: glMode = GL_GREATER; break; + case COMPARE_LEQUAL: glMode = GL_GEQUAL; break; + case COMPARE_GREATER: glMode = GL_LESS; break; + case COMPARE_GEQUAL: glMode = GL_LEQUAL; break; + default: break; + } + + glStencilFunc(glMode, state.stencilValue, 0xff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + } else if (state.stencilEnabled) { + state.stencilEnabled = false; + glDisable(GL_STENCIL_TEST); + } + } + + // Winding + if (state.winding != pipeline->winding) { + state.winding = pipeline->winding; + glFrontFace(state.winding == WINDING_CLOCKWISE ? GL_CW : GL_CCW); + } + + // Wireframe + if (state.wireframe != pipeline->wireframe) { + state.wireframe = pipeline->wireframe; +#ifndef EMSCRIPTEN + glPolygonMode(GL_FRONT_AND_BACK, state.wireframe ? GL_LINE : GL_FILL); +#endif + } + + // Transform + lovrShaderSetMatrix(shader, "lovrProjection", command->camera.projection, 16); + lovrShaderSetMatrix(shader, "lovrView", command->camera.viewMatrix, 16); + lovrShaderSetMatrix(shader, "lovrModel", command->transform, 16); + + float modelView[16]; + mat4_multiply(mat4_set(modelView, command->camera.viewMatrix), command->transform); + lovrShaderSetMatrix(shader, "lovrTransform", modelView, 16); + + if (lovrShaderHasUniform(shader, "lovrNormalMatrix")) { + if (mat4_invert(modelView)) { + mat4_transpose(modelView); + } else { + mat4_identity(modelView); + } + + float normalMatrix[9] = { + modelView[0], modelView[1], modelView[2], + modelView[4], modelView[5], modelView[6], + modelView[8], modelView[9], modelView[10], + }; + + lovrShaderSetMatrix(shader, "lovrNormalMatrix", normalMatrix, 9); + } + + // Pose + float* pose = lovrMeshGetPose(mesh); + if (pose) { + lovrShaderSetMatrix(shader, "lovrPose", pose, MAX_BONES * 16); + } else { + float identity[16]; + mat4_identity(identity); + lovrShaderSetMatrix(shader, "lovrPose", identity, 16); + } + + // Point size + lovrShaderSetFloat(shader, "lovrPointSize", &pipeline->pointSize, 1); + + // Color + Color color = pipeline->color; + gammaCorrectColor(&color); + float data[4] = { color.r, color.g, color.b, color.a }; + lovrShaderSetFloat(shader, "lovrColor", data, 4); + + // Material + for (int i = 0; i < MAX_MATERIAL_SCALARS; i++) { + float value = lovrMaterialGetScalar(material, i); + lovrShaderSetFloat(shader, lovrShaderScalarUniforms[i], &value, 1); + } + + for (int i = 0; i < MAX_MATERIAL_COLORS; i++) { + Color color = lovrMaterialGetColor(material, i); + gammaCorrectColor(&color); + float data[4] = { color.r, color.g, color.b, color.a }; + lovrShaderSetFloat(shader, lovrShaderColorUniforms[i], data, 4); + } + + for (int i = 0; i < MAX_MATERIAL_TEXTURES; i++) { + Texture* texture = lovrMaterialGetTexture(material, i); + lovrShaderSetTexture(shader, lovrShaderTextureUniforms[i], &texture, 1); + } + + // Canvas + if (pipeline->canvasCount > 0) { + lovrCanvasBind(pipeline->canvas, pipeline->canvasCount); + int width = lovrTextureGetWidth((Texture*) pipeline->canvas); + int height = lovrTextureGetHeight((Texture*) pipeline->canvas); + gpuSetViewport((uint32_t[4]) { 0, 0, width, height }); + } else { + lovrCanvasBind(&command->camera.canvas, 1); + gpuSetViewport(command->camera.viewport); + } + + // Shader + gpuUseProgram(lovrShaderGetProgram(shader)); + lovrShaderBind(shader); + + // Attributes + lovrMeshBind(mesh, shader); + + // Draw (TODEW) + uint32_t rangeStart, rangeCount; + lovrMeshGetDrawRange(mesh, &rangeStart, &rangeCount); + uint32_t indexCount; + size_t indexSize; + lovrMeshReadIndices(mesh, &indexCount, &indexSize); + GLenum glDrawMode = lovrConvertMeshDrawMode(lovrMeshGetDrawMode(mesh)); + if (indexCount > 0) { + size_t count = rangeCount ? rangeCount : indexCount; + GLenum indexType = indexSize == sizeof(uint16_t) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; + size_t offset = rangeStart * indexSize; + if (instances > 1) { + glDrawElementsInstanced(glDrawMode, count, indexType, (GLvoid*) offset, instances); + } else { + glDrawElements(glDrawMode, count, indexType, (GLvoid*) offset); + } + } else { + size_t count = rangeCount ? rangeCount : lovrMeshGetVertexCount(mesh); + if (instances > 1) { + glDrawArraysInstanced(glDrawMode, rangeStart, count, instances); + } else { + glDrawArrays(glDrawMode, rangeStart, count); + } + } + + state.stats.drawCalls++; +} + +void gpuPresent() { + memset(&state.stats, 0, sizeof(state.stats)); +} + +GraphicsLimits lovrGraphicsGetLimits() { + if (!state.limits.initialized) { +#ifdef EMSCRIPTEN + glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, state.limits.pointSizes); +#else + glGetFloatv(GL_POINT_SIZE_RANGE, state.limits.pointSizes); +#endif + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &state.limits.textureSize); + glGetIntegerv(GL_MAX_SAMPLES, &state.limits.textureMSAA); + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &state.limits.textureAnisotropy); + state.limits.initialized = 1; + } + + return state.limits; +} + +GraphicsStats lovrGraphicsGetStats() { + return state.stats; +} + +// Ephemeral + +void gpuBindFramebuffer(uint32_t framebuffer) { + if (state.framebuffer != framebuffer) { + state.framebuffer = framebuffer; + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + } +} + +void gpuBindIndexBuffer(uint32_t indexBuffer) { + if (state.indexBuffer != indexBuffer) { + state.indexBuffer = indexBuffer; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); + } +} + +void gpuBindTexture(Texture* texture, int slot) { + lovrAssert(slot >= 0 && slot < MAX_TEXTURES, "Invalid texture slot %d", slot); + + if (!texture) { + if (!state.defaultTexture) { + TextureData* textureData = lovrTextureDataGetBlank(1, 1, 0xff, FORMAT_RGBA); + state.defaultTexture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, false); + lovrRelease(textureData); + } + + texture = state.defaultTexture; + } + + if (texture != state.textures[slot]) { + lovrRetain(texture); + lovrRelease(state.textures[slot]); + state.textures[slot] = texture; + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(texture->glType, lovrTextureGetId(texture)); + } +} + +void gpuBindVertexArray(uint32_t vertexArray) { + if (state.vertexArray != vertexArray) { + state.vertexArray = vertexArray; + glBindVertexArray(vertexArray); + } +} + +void gpuBindVertexBuffer(uint32_t vertexBuffer) { + if (state.vertexBuffer != vertexBuffer) { + state.vertexBuffer = vertexBuffer; + glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); + } +} + +Texture* gpuGetTexture(int slot) { + lovrAssert(slot >= 0 && slot < MAX_TEXTURES, "Invalid texture slot %d", slot); + return state.textures[slot]; +} + +void gpuSetViewport(uint32_t viewport[4]) { + if (memcmp(state.viewport, viewport, 4 * sizeof(uint32_t))) { + memcpy(state.viewport, viewport, 4 * sizeof(uint32_t)); + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + } +} + +void gpuUseProgram(uint32_t program) { + if (state.program != program) { + state.program = program; + glUseProgram(program); + state.stats.shaderSwitches++; + } +} diff --git a/src/graphics/opengl.h b/src/graphics/opengl.h new file mode 100644 index 00000000..221e3eb0 --- /dev/null +++ b/src/graphics/opengl.h @@ -0,0 +1,8 @@ +#pragma once + +#if EMSCRIPTEN +#include +#include +#else +#include "lib/glad/glad.h" +#endif diff --git a/src/graphics/opengl/canvas.c b/src/graphics/opengl/canvas.c deleted file mode 100644 index a07de96b..00000000 --- a/src/graphics/opengl/canvas.c +++ /dev/null @@ -1,153 +0,0 @@ -#include "graphics/opengl/opengl.h" -#include "graphics/graphics.h" -#include "graphics/gpu.h" - -bool lovrCanvasSupportsFormat(TextureFormat format) { - switch (format) { - case FORMAT_RGB: - case FORMAT_RGBA: - case FORMAT_RGBA16F: - case FORMAT_RGBA32F: - case FORMAT_RG11B10F: - return true; - case FORMAT_DXT1: - case FORMAT_DXT3: - case FORMAT_DXT5: - return false; - } -} - -Canvas* lovrCanvasCreate(int width, int height, TextureFormat format, CanvasFlags flags) { - TextureData* textureData = lovrTextureDataGetEmpty(width, height, format); - Texture* texture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, flags.mipmaps); - if (!texture) return NULL; - - Canvas* canvas = lovrAlloc(sizeof(Canvas), lovrCanvasDestroy); - canvas->texture = *texture; - canvas->flags = flags; - - // Framebuffer - glGenFramebuffers(1, &canvas->framebuffer); - gpuBindFramebuffer(canvas->framebuffer); - - // Color attachment - if (flags.msaa > 0) { - GLenum internalFormat = lovrConvertTextureFormatInternal(format, lovrGraphicsIsGammaCorrect()); - glGenRenderbuffers(1, &canvas->msaaTexture); - glBindRenderbuffer(GL_RENDERBUFFER, canvas->msaaTexture); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, flags.msaa, internalFormat, width, height); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, canvas->msaaTexture); - } else { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas->texture.id, 0); - } - - // Depth/Stencil - if (flags.depth || flags.stencil) { - GLenum depthStencilFormat = flags.stencil ? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT24; - glGenRenderbuffers(1, &canvas->depthStencilBuffer); - glBindRenderbuffer(GL_RENDERBUFFER, canvas->depthStencilBuffer); - if (flags.msaa > 0) { - glRenderbufferStorageMultisample(GL_RENDERBUFFER, flags.msaa, depthStencilFormat, width, height); - } else { - glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, width, height); - } - - if (flags.depth) { - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, canvas->depthStencilBuffer); - } - - if (flags.stencil) { - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, canvas->depthStencilBuffer); - } - } - - // Resolve framebuffer - if (flags.msaa > 0) { - glGenFramebuffers(1, &canvas->resolveFramebuffer); - gpuBindFramebuffer(canvas->resolveFramebuffer); - glBindTexture(GL_TEXTURE_2D, canvas->texture.id); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas->texture.id, 0); - gpuBindFramebuffer(canvas->framebuffer); - } - - lovrAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Error creating Canvas"); - lovrGraphicsClear(&(Color) { 0, 0, 0, 0 }, &(float) { 1. }, &(int) { 0 }); - gpuBindFramebuffer(0); - - return canvas; -} - -void lovrCanvasDestroy(void* ref) { - Canvas* canvas = ref; - glDeleteFramebuffers(1, &canvas->framebuffer); - if (canvas->resolveFramebuffer) { - glDeleteFramebuffers(1, &canvas->resolveFramebuffer); - } - if (canvas->depthStencilBuffer) { - glDeleteRenderbuffers(1, &canvas->depthStencilBuffer); - } - if (canvas->msaaTexture) { - glDeleteTextures(1, &canvas->msaaTexture); - } - lovrTextureDestroy(ref); -} - -uint32_t lovrCanvasGetId(Canvas* canvas) { - return canvas->framebuffer; -} - -void lovrCanvasBind(Canvas** canvases, int canvasCount) { - if (canvasCount == 0) { - gpuBindFramebuffer(0); - return; - } - - gpuBindFramebuffer(canvases[0]->texture.id); - if (memcmp(canvases, canvases[0]->attachments, MAX_CANVASES * sizeof(Canvas*))) { - memcpy(canvases[0]->attachments, canvases, MAX_CANVASES * sizeof(Canvas*)); - - GLenum buffers[MAX_CANVASES]; - for (int i = 0; i < canvasCount; i++) { - buffers[i] = GL_COLOR_ATTACHMENT0 + i; - glFramebufferTexture2D(GL_FRAMEBUFFER, buffers[i], GL_TEXTURE_2D, lovrTextureGetId((Texture*) canvases[i]), 0); - } - glDrawBuffers(canvasCount, buffers); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - lovrAssert(status != GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS, "All multicanvas canvases must have the same dimensions"); - lovrAssert(status == GL_FRAMEBUFFER_COMPLETE, "Unable to bind framebuffer"); - } -} - -void lovrCanvasResolve(Canvas* canvas) { - if (canvas->flags.msaa > 0) { - int width = canvas->texture.width; - int height = canvas->texture.height; - glBindFramebuffer(GL_READ_FRAMEBUFFER, canvas->framebuffer); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, canvas->resolveFramebuffer); - glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR); - } - - if (canvas->flags.mipmaps) { - gpuBindTexture(&canvas->texture, 0); - glGenerateMipmap(canvas->texture.glType); - } -} - -TextureFormat lovrCanvasGetFormat(Canvas* canvas) { - return canvas->texture.slices[0]->format; -} - -int lovrCanvasGetMSAA(Canvas* canvas) { - return canvas->flags.msaa; -} - -TextureData* lovrCanvasNewTextureData(Canvas* canvas) { - TextureData* textureData = lovrTextureDataGetBlank(canvas->texture.width, canvas->texture.height, 0, FORMAT_RGBA); - if (!textureData) return NULL; - - gpuBindFramebuffer(canvas->framebuffer); - glReadPixels(0, 0, canvas->texture.width, canvas->texture.height, GL_RGBA, GL_UNSIGNED_BYTE, textureData->blob.data); - - return textureData; -} diff --git a/src/graphics/opengl/gpu.c b/src/graphics/opengl/gpu.c deleted file mode 100644 index 9147b9bc..00000000 --- a/src/graphics/opengl/gpu.c +++ /dev/null @@ -1,479 +0,0 @@ -#include "graphics/gpu.h" -#include "graphics/opengl/opengl.h" -#include "resources/shaders.h" -#include "data/modelData.h" -#include "math/mat4.h" -#include - -#define MAX_TEXTURES 16 - -static struct { - Texture* defaultTexture; - BlendMode blendMode; - BlendAlphaMode blendAlphaMode; - bool culling; - bool depthEnabled; - CompareMode depthTest; - bool depthWrite; - float lineWidth; - bool stencilEnabled; - CompareMode stencilMode; - int stencilValue; - bool stencilWriting; - Winding winding; - bool wireframe; - uint32_t framebuffer; - uint32_t indexBuffer; - uint32_t program; - Texture* textures[MAX_TEXTURES]; - uint32_t vertexArray; - uint32_t vertexBuffer; - uint32_t viewport[4]; - bool srgb; - GraphicsLimits limits; - GraphicsStats stats; -} state; - -static void gammaCorrectColor(Color* color) { - if (state.srgb) { - color->r = lovrMathGammaToLinear(color->r); - color->g = lovrMathGammaToLinear(color->g); - color->b = lovrMathGammaToLinear(color->b); - } -} - -static GLenum convertCompareMode(CompareMode mode) { - switch (mode) { - case COMPARE_NONE: return GL_ALWAYS; - case COMPARE_EQUAL: return GL_EQUAL; - case COMPARE_NEQUAL: return GL_NOTEQUAL; - case COMPARE_LESS: return GL_LESS; - case COMPARE_LEQUAL: return GL_LEQUAL; - case COMPARE_GREATER: return GL_GREATER; - case COMPARE_GEQUAL: return GL_GEQUAL; - } -} - -void gpuInit(bool srgb, gpuProc (*getProcAddress)(const char*)) { -#ifndef EMSCRIPTEN - gladLoadGLLoader((GLADloadproc) getProcAddress); - glEnable(GL_LINE_SMOOTH); - glEnable(GL_PROGRAM_POINT_SIZE); - if (srgb) { - glEnable(GL_FRAMEBUFFER_SRGB); - } else { - glDisable(GL_FRAMEBUFFER_SRGB); - } -#endif - glEnable(GL_BLEND); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - state.srgb = srgb; - state.blendMode = -1; - state.blendAlphaMode = -1; - state.culling = -1; - state.depthEnabled = -1; - state.depthTest = -1; - state.depthWrite = -1; - state.lineWidth = -1; - state.stencilEnabled = -1; - state.stencilMode = -1; - state.stencilValue = -1; - state.stencilWriting = false; - state.winding = -1; - state.wireframe = -1; -} - -void gpuDestroy() { - lovrRelease(state.defaultTexture); - for (int i = 0; i < MAX_TEXTURES; i++) { - lovrRelease(state.textures[i]); - } -} - -void gpuClear(Canvas** canvas, int canvasCount, Color* color, float* depth, int* stencil) { - gpuBindFramebuffer(canvasCount > 0 ? lovrCanvasGetId(canvas[0]) : 0); - - if (color) { - gammaCorrectColor(color); - float c[4] = { color->r, color->g, color->b, color->a }; - glClearBufferfv(GL_COLOR, 0, c); - for (int i = 1; i < canvasCount; i++) { - glClearBufferfv(GL_COLOR, i, c); - } - } - - if (depth) { - glClearBufferfv(GL_DEPTH, 0, depth); - } - - if (stencil) { - glClearBufferiv(GL_STENCIL, 0, stencil); - } -} - -void lovrGraphicsStencil(StencilAction action, int replaceValue, StencilCallback callback, void* userdata) { - state.depthWrite = false; - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - - if (!state.stencilEnabled) { - state.stencilEnabled = true; - glEnable(GL_STENCIL_TEST); - } - - GLenum glAction; - switch (action) { - case STENCIL_REPLACE: glAction = GL_REPLACE; break; - case STENCIL_INCREMENT: glAction = GL_INCR; break; - case STENCIL_DECREMENT: glAction = GL_DECR; break; - case STENCIL_INCREMENT_WRAP: glAction = GL_INCR_WRAP; break; - case STENCIL_DECREMENT_WRAP: glAction = GL_DECR_WRAP; break; - case STENCIL_INVERT: glAction = GL_INVERT; break; - } - - glStencilFunc(GL_ALWAYS, replaceValue, 0xff); - glStencilOp(GL_KEEP, GL_KEEP, glAction); - - state.stencilWriting = true; - callback(userdata); - state.stencilWriting = false; - - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - state.stencilMode = ~0; // Dirty -} - -void gpuDraw(GpuDrawCommand* command) { - Mesh* mesh = command->mesh; - Material* material = command->material; - Shader* shader = command->shader; - Pipeline* pipeline = &command->pipeline; - int instances = command->instances; - - // Blend mode - if (state.blendMode != pipeline->blendMode || state.blendAlphaMode != pipeline->blendAlphaMode) { - state.blendMode = pipeline->blendMode; - state.blendAlphaMode = pipeline->blendAlphaMode; - - GLenum srcRGB = state.blendMode == BLEND_MULTIPLY ? GL_DST_COLOR : GL_ONE; - if (srcRGB == GL_ONE && state.blendAlphaMode == BLEND_ALPHA_MULTIPLY) { - srcRGB = GL_SRC_ALPHA; - } - - switch (state.blendMode) { - case BLEND_ALPHA: - glBlendEquation(GL_FUNC_ADD); - glBlendFuncSeparate(srcRGB, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - break; - - case BLEND_ADD: - glBlendEquation(GL_FUNC_ADD); - glBlendFuncSeparate(srcRGB, GL_ONE, GL_ZERO, GL_ONE); - break; - - case BLEND_SUBTRACT: - glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); - glBlendFuncSeparate(srcRGB, GL_ONE, GL_ZERO, GL_ONE); - break; - - case BLEND_MULTIPLY: - glBlendEquation(GL_FUNC_ADD); - glBlendFuncSeparate(srcRGB, GL_ZERO, GL_DST_COLOR, GL_ZERO); - break; - - case BLEND_LIGHTEN: - glBlendEquation(GL_MAX); - glBlendFuncSeparate(srcRGB, GL_ZERO, GL_ONE, GL_ZERO); - break; - - case BLEND_DARKEN: - glBlendEquation(GL_MIN); - glBlendFuncSeparate(srcRGB, GL_ZERO, GL_ONE, GL_ZERO); - break; - - case BLEND_SCREEN: - glBlendEquation(GL_FUNC_ADD); - glBlendFuncSeparate(srcRGB, GL_ONE_MINUS_SRC_COLOR, GL_ONE, GL_ONE_MINUS_SRC_COLOR); - break; - - case BLEND_REPLACE: - glBlendEquation(GL_FUNC_ADD); - glBlendFuncSeparate(srcRGB, GL_ZERO, GL_ONE, GL_ZERO); - break; - } - } - - // Culling - if (state.culling != pipeline->culling) { - state.culling = pipeline->culling; - if (state.culling) { - glEnable(GL_CULL_FACE); - } else { - glDisable(GL_CULL_FACE); - } - } - - // Depth test - if (state.depthTest != pipeline->depthTest) { - state.depthTest = pipeline->depthTest; - if (state.depthTest != COMPARE_NONE) { - if (!state.depthEnabled) { - state.depthEnabled = true; - glEnable(GL_DEPTH_TEST); - } - glDepthFunc(convertCompareMode(state.depthTest)); - } else if (state.depthEnabled) { - state.depthEnabled = false; - glDisable(GL_DEPTH_TEST); - } - } - - // Depth write - if (state.depthWrite != pipeline->depthWrite) { - state.depthWrite = pipeline->depthWrite; - glDepthMask(state.depthWrite); - } - - // Line width - if (state.lineWidth != pipeline->lineWidth) { - state.lineWidth = state.lineWidth; - glLineWidth(state.lineWidth); - } - - // Stencil mode - if (!state.stencilWriting && (state.stencilMode != pipeline->stencilMode || state.stencilValue != pipeline->stencilValue)) { - state.stencilMode = pipeline->stencilMode; - state.stencilValue = pipeline->stencilValue; - if (state.stencilMode != COMPARE_NONE) { - if (!state.stencilEnabled) { - state.stencilEnabled = true; - glEnable(GL_STENCIL_TEST); - } - - GLenum glMode = GL_ALWAYS; - switch (state.stencilMode) { - case COMPARE_EQUAL: glMode = GL_EQUAL; break; - case COMPARE_NEQUAL: glMode = GL_NOTEQUAL; break; - case COMPARE_LESS: glMode = GL_GREATER; break; - case COMPARE_LEQUAL: glMode = GL_GEQUAL; break; - case COMPARE_GREATER: glMode = GL_LESS; break; - case COMPARE_GEQUAL: glMode = GL_LEQUAL; break; - default: break; - } - - glStencilFunc(glMode, state.stencilValue, 0xff); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - } else if (state.stencilEnabled) { - state.stencilEnabled = false; - glDisable(GL_STENCIL_TEST); - } - } - - // Winding - if (state.winding != pipeline->winding) { - state.winding = pipeline->winding; - glFrontFace(state.winding == WINDING_CLOCKWISE ? GL_CW : GL_CCW); - } - - // Wireframe - if (state.wireframe != pipeline->wireframe) { - state.wireframe = pipeline->wireframe; -#ifndef EMSCRIPTEN - glPolygonMode(GL_FRONT_AND_BACK, state.wireframe ? GL_LINE : GL_FILL); -#endif - } - - // Transform - lovrShaderSetMatrix(shader, "lovrProjection", command->layer.projection, 16); - lovrShaderSetMatrix(shader, "lovrView", command->layer.view, 16); - lovrShaderSetMatrix(shader, "lovrModel", command->transform, 16); - - float modelView[16]; - mat4_multiply(mat4_set(modelView, command->layer.view), command->transform); - lovrShaderSetMatrix(shader, "lovrTransform", modelView, 16); - - if (lovrShaderHasUniform(shader, "lovrNormalMatrix")) { - if (mat4_invert(modelView)) { - mat4_transpose(modelView); - } else { - mat4_identity(modelView); - } - - float normalMatrix[9] = { - modelView[0], modelView[1], modelView[2], - modelView[4], modelView[5], modelView[6], - modelView[8], modelView[9], modelView[10], - }; - - lovrShaderSetMatrix(shader, "lovrNormalMatrix", normalMatrix, 9); - } - - // Pose - float* pose = lovrMeshGetPose(mesh); - if (pose) { - lovrShaderSetMatrix(shader, "lovrPose", pose, MAX_BONES * 16); - } else { - float identity[16]; - mat4_identity(identity); - lovrShaderSetMatrix(shader, "lovrPose", identity, 16); - } - - // Point size - lovrShaderSetFloat(shader, "lovrPointSize", &pipeline->pointSize, 1); - - // Color - Color color = pipeline->color; - gammaCorrectColor(&color); - float data[4] = { color.r, color.g, color.b, color.a }; - lovrShaderSetFloat(shader, "lovrColor", data, 4); - - // Material - for (int i = 0; i < MAX_MATERIAL_SCALARS; i++) { - float value = lovrMaterialGetScalar(material, i); - lovrShaderSetFloat(shader, lovrShaderScalarUniforms[i], &value, 1); - } - - for (int i = 0; i < MAX_MATERIAL_COLORS; i++) { - Color color = lovrMaterialGetColor(material, i); - gammaCorrectColor(&color); - float data[4] = { color.r, color.g, color.b, color.a }; - lovrShaderSetFloat(shader, lovrShaderColorUniforms[i], data, 4); - } - - for (int i = 0; i < MAX_MATERIAL_TEXTURES; i++) { - Texture* texture = lovrMaterialGetTexture(material, i); - lovrShaderSetTexture(shader, lovrShaderTextureUniforms[i], &texture, 1); - } - - // Layer - lovrCanvasBind(command->layer.canvas, command->layer.canvasCount); - gpuSetViewport(command->layer.viewport); - - // Shader - gpuUseProgram(lovrShaderGetProgram(shader)); - lovrShaderBind(shader); - - // Attributes - lovrMeshBind(mesh, shader); - - // Draw (TODEW) - uint32_t rangeStart, rangeCount; - lovrMeshGetDrawRange(mesh, &rangeStart, &rangeCount); - uint32_t indexCount; - size_t indexSize; - lovrMeshReadIndices(mesh, &indexCount, &indexSize); - GLenum glDrawMode = lovrConvertMeshDrawMode(lovrMeshGetDrawMode(mesh)); - if (indexCount > 0) { - size_t count = rangeCount ? rangeCount : indexCount; - GLenum indexType = indexSize == sizeof(uint16_t) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; - size_t offset = rangeStart * indexSize; - if (instances > 1) { - glDrawElementsInstanced(glDrawMode, count, indexType, (GLvoid*) offset, instances); - } else { - glDrawElements(glDrawMode, count, indexType, (GLvoid*) offset); - } - } else { - size_t count = rangeCount ? rangeCount : lovrMeshGetVertexCount(mesh); - if (instances > 1) { - glDrawArraysInstanced(glDrawMode, rangeStart, count, instances); - } else { - glDrawArrays(glDrawMode, rangeStart, count); - } - } - - state.stats.drawCalls++; -} - -void gpuPresent() { - memset(&state.stats, 0, sizeof(state.stats)); -} - -GraphicsLimits lovrGraphicsGetLimits() { - if (!state.limits.initialized) { -#ifdef EMSCRIPTEN - glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, state.limits.pointSizes); -#else - glGetFloatv(GL_POINT_SIZE_RANGE, state.limits.pointSizes); -#endif - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &state.limits.textureSize); - glGetIntegerv(GL_MAX_SAMPLES, &state.limits.textureMSAA); - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &state.limits.textureAnisotropy); - state.limits.initialized = 1; - } - - return state.limits; -} - -GraphicsStats lovrGraphicsGetStats() { - return state.stats; -} - -// Ephemeral - -void gpuBindFramebuffer(uint32_t framebuffer) { - if (state.framebuffer != framebuffer) { - state.framebuffer = framebuffer; - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - } -} - -void gpuBindIndexBuffer(uint32_t indexBuffer) { - if (state.indexBuffer != indexBuffer) { - state.indexBuffer = indexBuffer; - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); - } -} - -void gpuBindTexture(Texture* texture, int slot) { - lovrAssert(slot >= 0 && slot < MAX_TEXTURES, "Invalid texture slot %d", slot); - - if (!texture) { - if (!state.defaultTexture) { - TextureData* textureData = lovrTextureDataGetBlank(1, 1, 0xff, FORMAT_RGBA); - state.defaultTexture = lovrTextureCreate(TEXTURE_2D, &textureData, 1, true, false); - lovrRelease(textureData); - } - - texture = state.defaultTexture; - } - - if (texture != state.textures[slot]) { - lovrRetain(texture); - lovrRelease(state.textures[slot]); - state.textures[slot] = texture; - glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(texture->glType, lovrTextureGetId(texture)); - } -} - -void gpuBindVertexArray(uint32_t vertexArray) { - if (state.vertexArray != vertexArray) { - state.vertexArray = vertexArray; - glBindVertexArray(vertexArray); - } -} - -void gpuBindVertexBuffer(uint32_t vertexBuffer) { - if (state.vertexBuffer != vertexBuffer) { - state.vertexBuffer = vertexBuffer; - glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); - } -} - -Texture* gpuGetTexture(int slot) { - lovrAssert(slot >= 0 && slot < MAX_TEXTURES, "Invalid texture slot %d", slot); - return state.textures[slot]; -} - -void gpuSetViewport(uint32_t viewport[4]) { - if (memcmp(state.viewport, viewport, 4 * sizeof(uint32_t))) { - memcpy(state.viewport, viewport, 4 * sizeof(uint32_t)); - glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - } -} - -void gpuUseProgram(uint32_t program) { - if (state.program != program) { - state.program = program; - glUseProgram(program); - state.stats.shaderSwitches++; - } -} diff --git a/src/graphics/opengl/mesh.c b/src/graphics/opengl/mesh.c deleted file mode 100644 index 6530c9d3..00000000 --- a/src/graphics/opengl/mesh.c +++ /dev/null @@ -1,305 +0,0 @@ -#include "graphics/opengl/opengl.h" -#include "graphics/gpu.h" -#include "math/math.h" -#include - -GLenum lovrConvertMeshUsage(MeshUsage usage) { - switch (usage) { - case MESH_STATIC: return GL_STATIC_DRAW; - case MESH_DYNAMIC: return GL_DYNAMIC_DRAW; - case MESH_STREAM: return GL_STREAM_DRAW; - } -} - -GLenum lovrConvertMeshDrawMode(MeshDrawMode mode) { - switch (mode) { - case MESH_POINTS: return GL_POINTS; - case MESH_LINES: return GL_LINES; - case MESH_LINE_STRIP: return GL_LINE_STRIP; - case MESH_LINE_LOOP: return GL_LINE_LOOP; - case MESH_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP; - case MESH_TRIANGLES: return GL_TRIANGLES; - case MESH_TRIANGLE_FAN: return GL_TRIANGLE_FAN; - } -} - -Mesh* lovrMeshCreate(uint32_t count, VertexFormat format, MeshDrawMode drawMode, MeshUsage usage) { - Mesh* mesh = lovrAlloc(sizeof(Mesh), lovrMeshDestroy); - if (!mesh) return NULL; - - mesh->count = count; - mesh->format = format; - mesh->drawMode = drawMode; - mesh->usage = lovrConvertMeshUsage(usage); - - glGenBuffers(1, &mesh->vbo); - glGenBuffers(1, &mesh->ibo); - gpuBindVertexBuffer(mesh->vbo); - glBufferData(GL_ARRAY_BUFFER, count * format.stride, NULL, mesh->usage); - glGenVertexArrays(1, &mesh->vao); - - map_init(&mesh->attachments); - for (int i = 0; i < format.count; i++) { - map_set(&mesh->attachments, format.attributes[i].name, ((MeshAttachment) { mesh, i, 0, true })); - } - - mesh->data.raw = calloc(count, format.stride); - - return mesh; -} - -void lovrMeshDestroy(void* ref) { - Mesh* mesh = ref; - lovrRelease(mesh->material); - free(mesh->data.raw); - free(mesh->indices.raw); - glDeleteBuffers(1, &mesh->vbo); - glDeleteBuffers(1, &mesh->ibo); - glDeleteVertexArrays(1, &mesh->vao); - const char* key; - map_iter_t iter = map_iter(&mesh->attachments); - while ((key = map_next(&mesh->attachments, &iter)) != NULL) { - MeshAttachment* attachment = map_get(&mesh->attachments, key); - if (attachment->mesh != mesh) { - lovrRelease(attachment->mesh); - } - } - map_deinit(&mesh->attachments); - free(mesh); -} - -void lovrMeshAttachAttribute(Mesh* mesh, Mesh* other, const char* name, int divisor) { - MeshAttachment* otherAttachment = map_get(&other->attachments, name); - lovrAssert(!mesh->isAttachment, "Attempted to attach to a mesh which is an attachment itself"); - lovrAssert(otherAttachment, "No attribute named '%s' exists", name); - lovrAssert(!map_get(&mesh->attachments, name), "Mesh already has an attribute named '%s'", name); - lovrAssert(divisor >= 0, "Divisor can't be negative"); - - MeshAttachment attachment = { other, otherAttachment->attributeIndex, divisor, true }; - map_set(&mesh->attachments, name, attachment); - other->isAttachment = true; - lovrRetain(other); -} - -void lovrMeshDetachAttribute(Mesh* mesh, const char* name) { - MeshAttachment* attachment = map_get(&mesh->attachments, name); - lovrAssert(attachment, "No attached attribute '%s' was found", name); - lovrAssert(attachment->mesh != mesh, "Attribute '%s' was not attached from another Mesh", name); - lovrRelease(attachment->mesh); - map_remove(&mesh->attachments, name); -} - -void lovrMeshBind(Mesh* mesh, Shader* shader) { - const char* key; - map_iter_t iter = map_iter(&mesh->attachments); - - MeshAttachment layout[MAX_ATTACHMENTS]; - memset(layout, 0, MAX_ATTACHMENTS * sizeof(MeshAttachment)); - - gpuBindVertexArray(mesh->vao); - lovrMeshUnmapVertices(mesh); - lovrMeshUnmapIndices(mesh); - if (mesh->indexCount > 0) { - gpuBindIndexBuffer(mesh->ibo); - } - - while ((key = map_next(&mesh->attachments, &iter)) != NULL) { - int location = lovrShaderGetAttributeId(shader, key); - - if (location >= 0) { - MeshAttachment* attachment = map_get(&mesh->attachments, key); - layout[location] = *attachment; - lovrMeshUnmapVertices(attachment->mesh); - lovrMeshUnmapIndices(attachment->mesh); - } - } - - for (int i = 0; i < MAX_ATTACHMENTS; i++) { - MeshAttachment previous = mesh->layout[i]; - MeshAttachment current = layout[i]; - - if (!memcmp(&previous, ¤t, sizeof(MeshAttachment))) { - continue; - } - - if (previous.enabled != current.enabled) { - if (current.enabled) { - glEnableVertexAttribArray(i); - } else { - glDisableVertexAttribArray(i); - mesh->layout[i] = current; - continue; - } - } - - if (previous.divisor != current.divisor) { - glVertexAttribDivisor(i, current.divisor); - } - - if (previous.mesh != current.mesh || previous.attributeIndex != current.attributeIndex) { - gpuBindVertexBuffer(current.mesh->vbo); - VertexFormat* format = ¤t.mesh->format; - Attribute attribute = format->attributes[current.attributeIndex]; - switch (attribute.type) { - case ATTR_FLOAT: - glVertexAttribPointer(i, attribute.count, GL_FLOAT, GL_TRUE, format->stride, (void*) attribute.offset); - break; - - case ATTR_BYTE: - glVertexAttribPointer(i, attribute.count, GL_UNSIGNED_BYTE, GL_TRUE, format->stride, (void*) attribute.offset); - break; - - case ATTR_INT: - glVertexAttribIPointer(i, attribute.count, GL_UNSIGNED_INT, format->stride, (void*) attribute.offset); - break; - } - } - - mesh->layout[i] = current; - } -} - -VertexFormat* lovrMeshGetVertexFormat(Mesh* mesh) { - return &mesh->format; -} - -MeshDrawMode lovrMeshGetDrawMode(Mesh* mesh) { - return mesh->drawMode; -} - -void lovrMeshSetDrawMode(Mesh* mesh, MeshDrawMode drawMode) { - mesh->drawMode = drawMode; -} - -int lovrMeshGetVertexCount(Mesh* mesh) { - return mesh->count; -} - -bool lovrMeshIsAttributeEnabled(Mesh* mesh, const char* name) { - MeshAttachment* attachment = map_get(&mesh->attachments, name); - lovrAssert(attachment, "Mesh does not have an attribute named '%s'", name); - return attachment->enabled; -} - -void lovrMeshSetAttributeEnabled(Mesh* mesh, const char* name, bool enable) { - MeshAttachment* attachment = map_get(&mesh->attachments, name); - lovrAssert(attachment, "Mesh does not have an attribute named '%s'", name); - attachment->enabled = enable; -} - -void lovrMeshGetDrawRange(Mesh* mesh, uint32_t* start, uint32_t* count) { - *start = mesh->rangeStart; - *count = mesh->rangeCount; -} - -void lovrMeshSetDrawRange(Mesh* mesh, uint32_t start, uint32_t count) { - uint32_t limit = mesh->indexCount > 0 ? mesh->indexCount : mesh->count; - lovrAssert(start + count <= limit, "Invalid mesh draw range [%d, %d]", start + 1, start + count + 1); - mesh->rangeStart = start; - mesh->rangeCount = count; -} - -Material* lovrMeshGetMaterial(Mesh* mesh) { - return mesh->material; -} - -void lovrMeshSetMaterial(Mesh* mesh, Material* material) { - if (mesh->material != material) { - lovrRetain(material); - lovrRelease(mesh->material); - mesh->material = material; - } -} - -float* lovrMeshGetPose(Mesh* mesh) { - return mesh->pose; -} - -void lovrMeshSetPose(Mesh* mesh, float* pose) { - mesh->pose = pose; -} - -VertexPointer lovrMeshMapVertices(Mesh* mesh, uint32_t start, uint32_t count, bool read, bool write) { - if (write) { - mesh->dirtyStart = MIN(mesh->dirtyStart, start); - mesh->dirtyEnd = MAX(mesh->dirtyEnd, start + count); - } - - return (VertexPointer) { .bytes = mesh->data.bytes + start * mesh->format.stride }; -} - -void lovrMeshUnmapVertices(Mesh* mesh) { - if (mesh->dirtyEnd == 0) { - return; - } - - size_t stride = mesh->format.stride; - gpuBindVertexBuffer(mesh->vbo); - if (mesh->usage == MESH_STREAM) { - glBufferData(GL_ARRAY_BUFFER, mesh->count * stride, mesh->data.bytes, mesh->usage); - } else { - size_t offset = mesh->dirtyStart * stride; - size_t count = (mesh->dirtyEnd - mesh->dirtyStart) * stride; - glBufferSubData(GL_ARRAY_BUFFER, offset, count, mesh->data.bytes + offset); - } - - mesh->dirtyStart = INT_MAX; - mesh->dirtyEnd = 0; -} - -IndexPointer lovrMeshReadIndices(Mesh* mesh, uint32_t* count, size_t* size) { - *size = mesh->indexSize; - *count = mesh->indexCount; - - if (mesh->indexCount == 0) { - return (IndexPointer) { .raw = NULL }; - } else if (mesh->mappedIndices) { - lovrMeshUnmapIndices(mesh); - } - - return mesh->indices; -} - -IndexPointer lovrMeshWriteIndices(Mesh* mesh, uint32_t count, size_t size) { - if (mesh->mappedIndices) { - lovrMeshUnmapIndices(mesh); - } - - mesh->indexSize = size; - mesh->indexCount = count; - - if (count == 0) { - return (IndexPointer) { .raw = NULL }; - } - - gpuBindVertexArray(mesh->vao); - gpuBindIndexBuffer(mesh->ibo); - mesh->mappedIndices = true; - - if (mesh->indexCapacity < size * count) { - mesh->indexCapacity = nextPo2(size * count); - mesh->indices.raw = realloc(mesh->indices.raw, mesh->indexCapacity); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh->indexCapacity, NULL, mesh->usage); - } - - return mesh->indices; -} - -void lovrMeshUnmapIndices(Mesh* mesh) { - if (!mesh->mappedIndices) { - return; - } - - mesh->mappedIndices = false; - gpuBindIndexBuffer(mesh->ibo); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, mesh->indexCount * mesh->indexSize, mesh->indices.raw); -} - -void lovrMeshResize(Mesh* mesh, uint32_t count) { - if (mesh->count < count) { - mesh->count = nextPo2(count); - gpuBindVertexBuffer(mesh->vbo); - mesh->data.raw = realloc(mesh->data.raw, count * mesh->format.stride); - glBufferData(GL_ARRAY_BUFFER, count * mesh->format.stride, mesh->data.raw, mesh->usage); - } -} diff --git a/src/graphics/opengl/opengl.h b/src/graphics/opengl/opengl.h deleted file mode 100644 index d60f54d1..00000000 --- a/src/graphics/opengl/opengl.h +++ /dev/null @@ -1,122 +0,0 @@ -#include "graphics/shader.h" -#include "graphics/texture.h" -#include "graphics/canvas.h" -#include "graphics/mesh.h" -#include "util.h" -#include "lib/map/map.h" -#include - -#pragma once - -#if EMSCRIPTEN -#include -#include -#else -#include "lib/glad/glad.h" -#endif - -#define LOVR_SHADER_POSITION 0 -#define LOVR_SHADER_NORMAL 1 -#define LOVR_SHADER_TEX_COORD 2 -#define LOVR_SHADER_VERTEX_COLOR 3 -#define LOVR_SHADER_TANGENT 4 -#define LOVR_SHADER_BONES 5 -#define LOVR_SHADER_BONE_WEIGHTS 6 -#define LOVR_MAX_UNIFORM_LENGTH 256 -#define LOVR_MAX_ATTRIBUTE_LENGTH 256 - -typedef struct { - GLchar name[LOVR_MAX_UNIFORM_LENGTH]; - GLenum glType; - int index; - int location; - int count; - int components; - size_t size; - UniformType type; - union { - void* data; - int* ints; - float* floats; - Texture** textures; - } value; - int baseTextureSlot; - bool dirty; -} Uniform; - -typedef map_t(Uniform) map_uniform_t; - -struct Shader { - Ref ref; - uint32_t program; - map_uniform_t uniforms; - map_int_t attributes; -}; - -struct Texture { - Ref ref; - TextureType type; - GLenum glType; - TextureData** slices; - int width; - int height; - int depth; - GLuint id; - TextureFilter filter; - TextureWrap wrap; - bool srgb; - bool mipmaps; - bool allocated; -}; - -struct Canvas { - Texture texture; - GLuint framebuffer; - GLuint resolveFramebuffer; - GLuint depthStencilBuffer; - GLuint msaaTexture; - CanvasFlags flags; - Canvas** attachments[MAX_CANVASES]; -}; - -typedef struct { - Mesh* mesh; - int attributeIndex; - int divisor; - bool enabled; -} MeshAttachment; - -typedef map_t(MeshAttachment) map_attachment_t; - -struct Mesh { - Ref ref; - uint32_t count; - VertexFormat format; - MeshDrawMode drawMode; - GLenum usage; - VertexPointer data; - IndexPointer indices; - uint32_t indexCount; - size_t indexSize; - size_t indexCapacity; - bool mappedIndices; - uint32_t dirtyStart; - uint32_t dirtyEnd; - uint32_t rangeStart; - uint32_t rangeCount; - GLuint vao; - GLuint vbo; - GLuint ibo; - Material* material; - float* pose; - map_attachment_t attachments; - MeshAttachment layout[MAX_ATTACHMENTS]; - bool isAttachment; -}; - -GLenum lovrConvertWrapMode(WrapMode mode); -GLenum lovrConvertTextureFormat(TextureFormat format); -GLenum lovrConvertTextureFormatInternal(TextureFormat format, bool srgb); -GLenum lovrConvertMeshUsage(MeshUsage usage); -GLenum lovrConvertMeshDrawMode(MeshDrawMode mode); -bool lovrIsTextureFormatCompressed(TextureFormat format); diff --git a/src/graphics/opengl/shader.c b/src/graphics/opengl/shader.c deleted file mode 100644 index 44e94017..00000000 --- a/src/graphics/opengl/shader.c +++ /dev/null @@ -1,391 +0,0 @@ -#include "graphics/opengl/opengl.h" -#include "graphics/graphics.h" -#include "graphics/gpu.h" -#include "resources/shaders.h" -#include "math/math.h" -#include -#include -#include - -static UniformType getUniformType(GLenum type, const char* debug) { - switch (type) { - case GL_FLOAT: - case GL_FLOAT_VEC2: - case GL_FLOAT_VEC3: - case GL_FLOAT_VEC4: - return UNIFORM_FLOAT; - - case GL_INT: - case GL_INT_VEC2: - case GL_INT_VEC3: - case GL_INT_VEC4: - return UNIFORM_INT; - - case GL_FLOAT_MAT2: - case GL_FLOAT_MAT3: - case GL_FLOAT_MAT4: - return UNIFORM_MATRIX; - - case GL_SAMPLER_2D: - case GL_SAMPLER_3D: - case GL_SAMPLER_CUBE: - case GL_SAMPLER_2D_ARRAY: - return UNIFORM_SAMPLER; - - default: - lovrThrow("Unsupported type for uniform '%s'", debug); - return UNIFORM_FLOAT; - } -} - -static int getUniformComponents(GLenum type) { - switch (type) { - case GL_FLOAT: - case GL_INT: - case GL_SAMPLER_2D: - case GL_SAMPLER_3D: - case GL_SAMPLER_CUBE: - case GL_SAMPLER_2D_ARRAY: - return 1; - - case GL_FLOAT_VEC2: - case GL_INT_VEC2: - case GL_FLOAT_MAT2: - return 2; - - case GL_FLOAT_VEC3: - case GL_INT_VEC3: - case GL_FLOAT_MAT3: - return 3; - - case GL_FLOAT_VEC4: - case GL_INT_VEC4: - case GL_FLOAT_MAT4: - return 4; - - default: - return 1; - } -} - -static GLuint compileShader(GLenum type, const char* source) { - GLuint shader = glCreateShader(type); - - glShaderSource(shader, 1, (const GLchar**) &source, NULL); - glCompileShader(shader); - - int isShaderCompiled; - glGetShaderiv(shader, GL_COMPILE_STATUS, &isShaderCompiled); - if (!isShaderCompiled) { - int logLength; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); - - char* log = malloc(logLength); - glGetShaderInfoLog(shader, logLength, &logLength, log); - lovrThrow("Could not compile shader %s", log); - } - - return shader; -} - -static GLuint linkShaders(GLuint vertexShader, GLuint fragmentShader) { - GLuint program = glCreateProgram(); - - if (vertexShader) { - glAttachShader(program, vertexShader); - } - - if (fragmentShader) { - glAttachShader(program, fragmentShader); - } - - glBindAttribLocation(program, LOVR_SHADER_POSITION, "lovrPosition"); - glBindAttribLocation(program, LOVR_SHADER_NORMAL, "lovrNormal"); - glBindAttribLocation(program, LOVR_SHADER_TEX_COORD, "lovrTexCoord"); - glBindAttribLocation(program, LOVR_SHADER_VERTEX_COLOR, "lovrVertexColor"); - glBindAttribLocation(program, LOVR_SHADER_TANGENT, "lovrTangent"); - glBindAttribLocation(program, LOVR_SHADER_BONES, "lovrBones"); - glBindAttribLocation(program, LOVR_SHADER_BONE_WEIGHTS, "lovrBoneWeights"); - glLinkProgram(program); - - int isLinked; - glGetProgramiv(program, GL_LINK_STATUS, &isLinked); - if (!isLinked) { - int logLength; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength); - - char* log = malloc(logLength); - glGetProgramInfoLog(program, logLength, &logLength, log); - lovrThrow("Could not link shader %s", log); - } - - glDetachShader(program, vertexShader); - glDeleteShader(vertexShader); - glDetachShader(program, fragmentShader); - glDeleteShader(fragmentShader); - - return program; -} - -Shader* lovrShaderCreate(const char* vertexSource, const char* fragmentSource) { - Shader* shader = lovrAlloc(sizeof(Shader), lovrShaderDestroy); - if (!shader) return NULL; - - char source[8192]; - - // Vertex - vertexSource = vertexSource == NULL ? lovrDefaultVertexShader : vertexSource; - snprintf(source, sizeof(source), "%s%s\n%s", lovrShaderVertexPrefix, vertexSource, lovrShaderVertexSuffix); - GLuint vertexShader = compileShader(GL_VERTEX_SHADER, source); - - // Fragment - fragmentSource = fragmentSource == NULL ? lovrDefaultFragmentShader : fragmentSource; - snprintf(source, sizeof(source), "%s%s\n%s", lovrShaderFragmentPrefix, fragmentSource, lovrShaderFragmentSuffix); - GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, source); - - // Link - uint32_t program = linkShaders(vertexShader, fragmentShader); - shader->program = program; - - gpuUseProgram(program); - glVertexAttrib4fv(LOVR_SHADER_VERTEX_COLOR, (float[4]) { 1., 1., 1., 1. }); - glVertexAttribI4iv(LOVR_SHADER_BONES, (int[4]) { 0., 0., 0., 0. }); - glVertexAttrib4fv(LOVR_SHADER_BONE_WEIGHTS, (float[4]) { 1., 0., 0., 0. }); - - // Uniform introspection - int32_t uniformCount; - int textureSlot = 0; - map_init(&shader->uniforms); - glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniformCount); - for (int i = 0; i < uniformCount; i++) { - Uniform uniform; - glGetActiveUniform(program, i, LOVR_MAX_UNIFORM_LENGTH, NULL, &uniform.count, &uniform.glType, uniform.name); - - char* subscript = strchr(uniform.name, '['); - if (subscript) { - *subscript = '\0'; - } - - uniform.index = i; - uniform.location = glGetUniformLocation(program, uniform.name); - uniform.type = getUniformType(uniform.glType, uniform.name); - uniform.components = getUniformComponents(uniform.glType); - uniform.baseTextureSlot = (uniform.type == UNIFORM_SAMPLER) ? textureSlot : -1; - - if (uniform.location == -1) { - continue; - } - - switch (uniform.type) { - case UNIFORM_FLOAT: - uniform.size = uniform.components * uniform.count * sizeof(float); - uniform.value.data = calloc(1, uniform.size); - break; - - case UNIFORM_INT: - uniform.size = uniform.components * uniform.count * sizeof(int); - uniform.value.data = calloc(1, uniform.size); - break; - - case UNIFORM_MATRIX: - uniform.size = uniform.components * uniform.components * uniform.count * sizeof(int); - uniform.value.data = calloc(1, uniform.size); - break; - - case UNIFORM_SAMPLER: - uniform.size = uniform.components * uniform.count * MAX(sizeof(Texture*), sizeof(int)); - uniform.value.data = calloc(1, uniform.size); - - // Use the value for ints to bind texture slots, but use the value for textures afterwards. - for (int i = 0; i < uniform.count; i++) { - uniform.value.ints[i] = uniform.baseTextureSlot + i; - } - glUniform1iv(uniform.location, uniform.count, uniform.value.ints); - break; - } - - size_t offset = 0; - for (int j = 0; j < uniform.count; j++) { - int location = uniform.location; - - if (uniform.count > 1) { - char name[LOVR_MAX_UNIFORM_LENGTH]; - snprintf(name, LOVR_MAX_UNIFORM_LENGTH, "%s[%d]", uniform.name, j); - location = glGetUniformLocation(program, name); - } - - switch (uniform.type) { - case UNIFORM_FLOAT: - glGetUniformfv(program, location, &uniform.value.floats[offset]); - offset += uniform.components; - break; - - case UNIFORM_INT: - glGetUniformiv(program, location, &uniform.value.ints[offset]); - offset += uniform.components; - break; - - case UNIFORM_MATRIX: - glGetUniformfv(program, location, &uniform.value.floats[offset]); - offset += uniform.components * uniform.components; - break; - - default: - break; - } - } - - map_set(&shader->uniforms, uniform.name, uniform); - textureSlot += (uniform.type == UNIFORM_SAMPLER) ? uniform.count : 0; - } - - // Attribute cache - int32_t attributeCount; - glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &attributeCount); - map_init(&shader->attributes); - for (int i = 0; i < attributeCount; i++) { - char name[LOVR_MAX_ATTRIBUTE_LENGTH]; - GLint size; - GLenum type; - glGetActiveAttrib(program, i, LOVR_MAX_ATTRIBUTE_LENGTH, NULL, &size, &type, name); - map_set(&shader->attributes, name, glGetAttribLocation(program, name)); - } - - return shader; -} - -Shader* lovrShaderCreateDefault(DefaultShader type) { - switch (type) { - case SHADER_DEFAULT: return lovrShaderCreate(NULL, NULL); - case SHADER_CUBE: return lovrShaderCreate(lovrCubeVertexShader, lovrCubeFragmentShader); break; - case SHADER_PANO: return lovrShaderCreate(lovrCubeVertexShader, lovrPanoFragmentShader); break; - case SHADER_FONT: return lovrShaderCreate(NULL, lovrFontFragmentShader); - case SHADER_FILL: return lovrShaderCreate(lovrFillVertexShader, NULL); - default: lovrThrow("Unknown default shader type"); - } -} - -void lovrShaderDestroy(void* ref) { - Shader* shader = ref; - glDeleteProgram(shader->program); - map_deinit(&shader->uniforms); - map_deinit(&shader->attributes); - free(shader); -} - -uint32_t lovrShaderGetProgram(Shader* shader) { - return shader->program; -} - -void lovrShaderBind(Shader* shader) { - map_iter_t iter = map_iter(&shader->uniforms); - const char* key; - while ((key = map_next(&shader->uniforms, &iter)) != NULL) { - Uniform* uniform = map_get(&shader->uniforms, key); - - if (uniform->type != UNIFORM_SAMPLER && !uniform->dirty) { - continue; - } - - uniform->dirty = false; - int count = uniform->count; - void* data = uniform->value.data; - - switch (uniform->type) { - case UNIFORM_FLOAT: - switch (uniform->components) { - case 1: glUniform1fv(uniform->location, count, data); break; - case 2: glUniform2fv(uniform->location, count, data); break; - case 3: glUniform3fv(uniform->location, count, data); break; - case 4: glUniform4fv(uniform->location, count, data); break; - } - break; - - case UNIFORM_INT: - switch (uniform->components) { - case 1: glUniform1iv(uniform->location, count, data); break; - case 2: glUniform2iv(uniform->location, count, data); break; - case 3: glUniform3iv(uniform->location, count, data); break; - case 4: glUniform4iv(uniform->location, count, data); break; - } - break; - - case UNIFORM_MATRIX: - switch (uniform->components) { - case 2: glUniformMatrix2fv(uniform->location, count, GL_FALSE, data); break; - case 3: glUniformMatrix3fv(uniform->location, count, GL_FALSE, data); break; - case 4: glUniformMatrix4fv(uniform->location, count, GL_FALSE, data); break; - } - break; - - case UNIFORM_SAMPLER: - for (int i = 0; i < count; i++) { - GLenum uniformTextureType; - switch (uniform->glType) { - case GL_SAMPLER_2D: uniformTextureType = GL_TEXTURE_2D; break; - case GL_SAMPLER_3D: uniformTextureType = GL_TEXTURE_3D; break; - case GL_SAMPLER_CUBE: uniformTextureType = GL_TEXTURE_CUBE_MAP; break; - case GL_SAMPLER_2D_ARRAY: uniformTextureType = GL_TEXTURE_2D_ARRAY; break; - } - gpuBindTexture(uniform->value.textures[i], uniform->baseTextureSlot + i); - } - break; - } - } -} - -int lovrShaderGetAttributeId(Shader* shader, const char* name) { - int* id = map_get(&shader->attributes, name); - return id ? *id : -1; -} - -bool lovrShaderHasUniform(Shader* shader, const char* name) { - return map_get(&shader->uniforms, name) != NULL; -} - -bool lovrShaderGetUniform(Shader* shader, const char* name, int* count, int* components, size_t* size, UniformType* type) { - Uniform* uniform = map_get(&shader->uniforms, name); - if (!uniform) { - return false; - } - - *count = uniform->count; - *components = uniform->components; - *size = uniform->size; - *type = uniform->type; - return true; -} - -static void lovrShaderSetUniform(Shader* shader, const char* name, UniformType type, void* data, int count, size_t size, const char* debug) { - Uniform* uniform = map_get(&shader->uniforms, name); - if (!uniform) { - return; - } - - const char* plural = (uniform->size / size) > 1 ? "s" : ""; - lovrAssert(uniform->type == type, "Unable to send %ss to uniform %s", debug, uniform->name); - lovrAssert(count * size <= uniform->size, "Expected at most %d %s%s for uniform %s, got %d", uniform->size / size, debug, plural, uniform->name, count); - - if (!uniform->dirty && !memcmp(uniform->value.data, data, count * size)) { - return; - } - - memcpy(uniform->value.data, data, count * size); - uniform->dirty = true; -} - -void lovrShaderSetFloat(Shader* shader, const char* name, float* data, int count) { - lovrShaderSetUniform(shader, name, UNIFORM_FLOAT, data, count, sizeof(float), "float"); -} - -void lovrShaderSetInt(Shader* shader, const char* name, int* data, int count) { - lovrShaderSetUniform(shader, name, UNIFORM_INT, data, count, sizeof(int), "int"); -} - -void lovrShaderSetMatrix(Shader* shader, const char* name, float* data, int count) { - lovrShaderSetUniform(shader, name, UNIFORM_MATRIX, data, count, sizeof(float), "float"); -} - -void lovrShaderSetTexture(Shader* shader, const char* name, Texture** data, int count) { - lovrShaderSetUniform(shader, name, UNIFORM_SAMPLER, data, count, sizeof(Texture*), "texture"); -} diff --git a/src/graphics/opengl/texture.c b/src/graphics/opengl/texture.c deleted file mode 100644 index a14bcc18..00000000 --- a/src/graphics/opengl/texture.c +++ /dev/null @@ -1,271 +0,0 @@ -#include "graphics/opengl/opengl.h" -#include "graphics/graphics.h" -#include "graphics/gpu.h" -#include "lib/vec/vec.h" -#include "math/math.h" -#include - -GLenum lovrConvertWrapMode(WrapMode mode) { - switch (mode) { - case WRAP_CLAMP: return GL_CLAMP_TO_EDGE; - case WRAP_REPEAT: return GL_REPEAT; - case WRAP_MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; - } -} - -GLenum lovrConvertTextureFormat(TextureFormat format) { - switch (format) { - case FORMAT_RGB: return GL_RGB; - case FORMAT_RGBA: return GL_RGBA; - case FORMAT_RGBA16F: return GL_RGBA; - case FORMAT_RGBA32F: return GL_RGBA; - case FORMAT_RG11B10F: return GL_RGB; - case FORMAT_DXT1: return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; - case FORMAT_DXT3: return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; - case FORMAT_DXT5: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - } -} - -GLenum lovrConvertTextureFormatInternal(TextureFormat format, bool srgb) { - switch (format) { - case FORMAT_RGB: return srgb ? GL_SRGB8 : GL_RGB8; - case FORMAT_RGBA: return srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8; - case FORMAT_RGBA16F: return GL_RGBA16F; - case FORMAT_RGBA32F: return GL_RGBA32F; - case FORMAT_RG11B10F: return GL_R11F_G11F_B10F; - case FORMAT_DXT1: return srgb ? GL_COMPRESSED_SRGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT; - case FORMAT_DXT3: return srgb ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT : GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; - case FORMAT_DXT5: return srgb ? GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT : GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - } -} - -bool lovrIsTextureFormatCompressed(TextureFormat format) { - switch (format) { - case FORMAT_DXT1: - case FORMAT_DXT3: - case FORMAT_DXT5: - return true; - default: - return false; - } -} - -static void lovrTextureAllocate(Texture* texture, TextureData* textureData) { - texture->allocated = true; - texture->width = textureData->width; - texture->height = textureData->height; - - if (lovrIsTextureFormatCompressed(textureData->format)) { - return; - } - - int w = textureData->width; - int h = textureData->height; - int mipmapCount = log2(MAX(w, h)) + 1; - bool srgb = lovrGraphicsIsGammaCorrect() && texture->srgb; - GLenum glFormat = lovrConvertTextureFormat(textureData->format); - GLenum internalFormat = lovrConvertTextureFormatInternal(textureData->format, srgb); -#ifndef EMSCRIPTEN - if (GLAD_GL_ARB_texture_storage) { -#endif - if (texture->type == TEXTURE_ARRAY) { - glTexStorage3D(texture->glType, mipmapCount, internalFormat, w, h, texture->depth); - } else { - glTexStorage2D(texture->glType, mipmapCount, internalFormat, w, h); - } -#ifndef EMSCRIPTEN - } else { - for (int i = 0; i < mipmapCount; i++) { - switch (texture->type) { - case TEXTURE_2D: - glTexImage2D(texture->glType, i, internalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, NULL); - break; - - case TEXTURE_CUBE: - for (int face = 0; face < 6; face++) { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, i, internalFormat, w, h, 0, glFormat, GL_UNSIGNED_BYTE, NULL); - } - break; - - case TEXTURE_ARRAY: - case TEXTURE_VOLUME: - glTexImage3D(texture->glType, i, internalFormat, w, h, texture->depth, 0, glFormat, GL_UNSIGNED_BYTE, NULL); - break; - } - w = MAX(w >> 1, 1); - h = MAX(h >> 1, 1); - } - } -#endif -} - -Texture* lovrTextureCreate(TextureType type, TextureData** slices, int depth, bool srgb, bool mipmaps) { - Texture* texture = lovrAlloc(sizeof(Texture), lovrTextureDestroy); - if (!texture) return NULL; - - texture->type = type; - switch (type) { - case TEXTURE_2D: texture->glType = GL_TEXTURE_2D; break; - case TEXTURE_ARRAY: texture->glType = GL_TEXTURE_2D_ARRAY; break; - case TEXTURE_CUBE: texture->glType = GL_TEXTURE_CUBE_MAP; break; - case TEXTURE_VOLUME: texture->glType = GL_TEXTURE_3D; break; - } - - texture->slices = calloc(depth, sizeof(TextureData**)); - texture->depth = depth; - texture->srgb = srgb; - texture->mipmaps = mipmaps; - - WrapMode wrap = type == TEXTURE_CUBE ? WRAP_CLAMP : WRAP_REPEAT; - glGenTextures(1, &texture->id); - gpuBindTexture(texture, 0); - lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter()); - lovrTextureSetWrap(texture, (TextureWrap) { .s = wrap, .t = wrap, .r = wrap }); - - lovrAssert(type != TEXTURE_CUBE || depth == 6, "6 images are required for a cube texture\n"); - lovrAssert(type != TEXTURE_2D || depth == 1, "2D textures can only contain a single image"); - - if (slices) { - for (int i = 0; i < depth; i++) { - lovrTextureReplacePixels(texture, slices[i], i); - } - } - - return texture; -} - -void lovrTextureDestroy(void* ref) { - Texture* texture = ref; - for (int i = 0; i < texture->depth; i++) { - lovrRelease(texture->slices[i]); - } - glDeleteTextures(1, &texture->id); - free(texture->slices); - free(texture); -} - -GLuint lovrTextureGetId(Texture* texture) { - return texture->id; -} - -int lovrTextureGetWidth(Texture* texture) { - return texture->width; -} - -int lovrTextureGetHeight(Texture* texture) { - return texture->height; -} - -int lovrTextureGetDepth(Texture* texture) { - return texture->depth; -} - -TextureType lovrTextureGetType(Texture* texture) { - return texture->type; -} - -void lovrTextureReplacePixels(Texture* texture, TextureData* textureData, int slice) { - lovrRetain(textureData); - lovrRelease(texture->slices[slice]); - texture->slices[slice] = textureData; - - if (!texture->allocated) { - lovrAssert(texture->type != TEXTURE_CUBE || textureData->width == textureData->height, "Cubemap images must be square"); - lovrTextureAllocate(texture, textureData); - } else { - lovrAssert(textureData->width == texture->width && textureData->height == texture->height, "All texture slices must have the same dimensions"); - } - - if (!textureData->blob.data) { - return; - } - - GLenum glFormat = lovrConvertTextureFormat(textureData->format); - GLenum glInternalFormat = lovrConvertTextureFormatInternal(textureData->format, texture->srgb); - GLenum binding = (texture->type == TEXTURE_CUBE) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice : texture->glType; - - if (lovrIsTextureFormatCompressed(textureData->format)) { - Mipmap m; int i; - vec_foreach(&textureData->mipmaps, m, i) { - switch (texture->type) { - case TEXTURE_2D: - case TEXTURE_CUBE: - glCompressedTexImage2D(binding, i, glInternalFormat, m.width, m.height, 0, m.size, m.data); - break; - case TEXTURE_ARRAY: - case TEXTURE_VOLUME: - glCompressedTexSubImage3D(binding, i, 0, 0, slice, m.width, m.height, 1, glInternalFormat, m.size, m.data); - break; - } - } - } else { - switch (texture->type) { - case TEXTURE_2D: - case TEXTURE_CUBE: - glTexSubImage2D(binding, 0, 0, 0, textureData->width, textureData->height, glFormat, GL_UNSIGNED_BYTE, textureData->blob.data); - break; - case TEXTURE_ARRAY: - case TEXTURE_VOLUME: - glTexSubImage3D(binding, 0, 0, 0, slice, textureData->width, textureData->height, 1, glFormat, GL_UNSIGNED_BYTE, textureData->blob.data); - break; - } - - if (texture->mipmaps) { - glGenerateMipmap(texture->glType); - } - } -} - -TextureFilter lovrTextureGetFilter(Texture* texture) { - return texture->filter; -} - -void lovrTextureSetFilter(Texture* texture, TextureFilter filter) { - float anisotropy = filter.mode == FILTER_ANISOTROPIC ? MAX(filter.anisotropy, 1.) : 1.; - gpuBindTexture(texture, 0); - texture->filter = filter; - - switch (filter.mode) { - case FILTER_NEAREST: - glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - break; - - case FILTER_BILINEAR: - if (texture->mipmaps) { - glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); - glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - break; - - case FILTER_TRILINEAR: - case FILTER_ANISOTROPIC: - if (texture->mipmaps) { - glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameteri(texture->glType, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(texture->glType, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - break; - } - - glTexParameteri(texture->glType, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy); -} - -TextureWrap lovrTextureGetWrap(Texture* texture) { - return texture->wrap; -} - -void lovrTextureSetWrap(Texture* texture, TextureWrap wrap) { - texture->wrap = wrap; - gpuBindTexture(texture, 0); - glTexParameteri(texture->glType, GL_TEXTURE_WRAP_S, lovrConvertWrapMode(wrap.s)); - glTexParameteri(texture->glType, GL_TEXTURE_WRAP_T, lovrConvertWrapMode(wrap.t)); - if (texture->type == TEXTURE_CUBE || texture->type == TEXTURE_VOLUME) { - glTexParameteri(texture->glType, GL_TEXTURE_WRAP_R, lovrConvertWrapMode(wrap.r)); - } -} diff --git a/src/graphics/shader.h b/src/graphics/shader.h index f5ecec64..9d442f04 100644 --- a/src/graphics/shader.h +++ b/src/graphics/shader.h @@ -15,7 +15,8 @@ typedef enum { SHADER_CUBE, SHADER_PANO, SHADER_FONT, - SHADER_FILL + SHADER_FILL, + MAX_DEFAULT_SHADERS } DefaultShader; typedef struct Shader Shader; diff --git a/src/headset/fake.c b/src/headset/fake.c index 49e13504..72400b57 100644 --- a/src/headset/fake.c +++ b/src/headset/fake.c @@ -3,6 +3,7 @@ #include "math/mat4.h" #include "math/vec3.h" #include "math/quat.h" +#include "lib/glfw.h" #define _USE_MATH_DEFINES #include #include @@ -286,21 +287,23 @@ static void fakeRenderTo(void (*callback)(void*), void* userdata) { float projection[16]; mat4_perspective(projection, state.clipNear, state.clipFar, 67 * M_PI / 180., (float) width / height); - float view[16]; - mat4_identity(view); - mat4_translate(view, 0, state.offset, 0); - mat4_multiply(view, state.transform); - mat4_invert(view); + float viewMatrix[16]; + mat4_identity(viewMatrix); + mat4_translate(viewMatrix, 0, state.offset, 0); + mat4_multiply(viewMatrix, state.transform); + mat4_invert(viewMatrix); - Color backgroundColor = lovrGraphicsGetBackgroundColor(); - lovrGraphicsPushLayer(NULL, 0, false); - lovrGraphicsClear(&backgroundColor, &(float) { 1. }, &(int) { 0 }); - lovrGraphicsSetCamera(projection, view); - lovrGraphicsSetViewport(0, 0, width, height); - callback(userdata); - lovrGraphicsSetViewport(width, 0, width, height); - callback(userdata); - lovrGraphicsPopLayer(); + for (int eye = 0; eye < 2; eye++) { + lovrGraphicsSetCamera(&(Camera) { + .viewport = { width * eye, 0, width, height }, + .viewMatrix = viewMatrix, + .projection = projection + }, eye == 0); + + callback(userdata); + } + + lovrGraphicsSetCamera(NULL, false); } static void fakeUpdate(float dt) { diff --git a/src/headset/openvr.c b/src/headset/openvr.c index 6b4ddf47..21428ed7 100644 --- a/src/headset/openvr.c +++ b/src/headset/openvr.c @@ -17,7 +17,7 @@ #pragma pack(pop) #endif -#include "graphics/opengl/opengl.h" +#include "graphics/opengl.h" // From openvr_capi.h extern intptr_t VR_InitInternal(EVRInitError *peError, EVRApplicationType eType); @@ -673,26 +673,23 @@ static void openvrRenderTo(void (*callback)(void*), void* userdata) { state.compositor->WaitGetPoses(state.renderPoses, 16, NULL, 0); mat4_fromMat34(head, state.renderPoses[state.headsetIndex].mDeviceToAbsoluteTracking.m); - Color backgroundColor = lovrGraphicsGetBackgroundColor(); - lovrGraphicsPushLayer(&state.canvas, 1, false); - lovrGraphicsClear(&backgroundColor, &(float) { 1. }, NULL); - - for (HeadsetEye i = EYE_LEFT; i <= EYE_RIGHT; i++) { - - // Camera - EVREye vrEye = (i == EYE_LEFT) ? EVREye_Eye_Left : EVREye_Eye_Right; + for (int i = 0; i < 2; i++) { + EVREye vrEye = (i == 0) ? EVREye_Eye_Left : EVREye_Eye_Right; mat4_fromMat44(projection, state.system->GetProjectionMatrix(vrEye, state.clipNear, state.clipFar).m); mat4_identity(view); mat4_translate(view, 0, state.offset, 0); mat4_multiply(view, head); mat4_multiply(view, mat4_fromMat34(eye, state.system->GetEyeToHeadTransform(vrEye).m)); mat4_invert(view); - lovrGraphicsSetCamera(projection, view); - lovrGraphicsSetViewport(state.renderWidth * i, 0, state.renderWidth, state.renderHeight); - // Render + lovrGraphicsSetCamera(&(Camera) { + .canvas = state.canvas, + .viewport = { state.renderWidth * i, 0, state.renderWidth, state.renderHeight }, + .viewMatrix = view, + .projection = projection + }, i == 0); + callback(userdata); - lovrCanvasResolve(state.canvas); } // Submit @@ -707,19 +704,15 @@ static void openvrRenderTo(void (*callback)(void*), void* userdata) { state.compositor->Submit(EVREye_Eye_Right, &eyeTexture, &right, EVRSubmitFlags_Submit_Default); glBindTexture(GL_TEXTURE_2D, lovrTextureGetId(oldTexture)); - lovrGraphicsPopLayer(); + lovrGraphicsSetCamera(NULL, false); state.isRendering = false; if (state.isMirrored) { - Color oldColor = lovrGraphicsGetColor(); + lovrGraphicsPushPipeline(); lovrGraphicsSetColor((Color) { 1, 1, 1, 1 }); - Shader* lastShader = lovrGraphicsGetShader(); - lovrRetain(lastShader); lovrGraphicsSetShader(NULL); lovrGraphicsFill((Texture*) state.canvas); - lovrGraphicsSetShader(lastShader); - lovrRelease(lastShader); - lovrGraphicsSetColor(oldColor); + lovrGraphicsPopPipeline(); } } diff --git a/src/headset/webvr.c b/src/headset/webvr.c index 686a43fc..c89f93d8 100644 --- a/src/headset/webvr.c +++ b/src/headset/webvr.c @@ -89,15 +89,18 @@ static void onMountChanged(bool mounted) { static void onFrame(float* leftView, float* rightView, float* leftProjection, float* rightProjection, void* userdata) { int width, height; webvrGetDisplayDimensions(&width, &height); - lovrGraphicsPushLayer(NULL, 0, false); - lovrGraphicsClear(true, true, true, lovrGraphicsGetBackgroundColor(), 1., 0); - lovrGraphicsSetCamera(leftProjection, leftView); - lovrGraphicsSetViewport(0, 0, width, height); - state.renderCallback(userdata); - lovrGraphicsSetCamera(rightProjection, rightView); - lovrGraphicsSetViewport(width, 0, width, height); - state.renderCallback(userdata); - lovrGraphicsPopLayer(); + mat4 views[2] = { leftView, rightView }; + mat4 projections[2] = { leftProjection, rightProjection }; + for (int eye = 0; eye < 2; eye++) { + lovrGraphicsSetCamera(&(Camera) { + .viewport = { width * eye, 0, width, height }, + .viewMatrix = views[eye], + .projection = projections[eye] + }, eye == 0); + + state.renderCallback(userdata); + } + lovrGraphicsSetCamera(NULL, false); } static bool webvrDriverInit(float offset) {