I came in like a wrecking ball;

This commit is contained in:
bjorn 2018-08-19 20:48:45 -07:00
parent 87c1429778
commit 7795bb9276
7 changed files with 17 additions and 377 deletions

View File

@ -256,7 +256,7 @@ int l_lovrGraphicsInit(lua_State* L) {
luax_registertype(L, "Shader", lovrShader);
luax_registertype(L, "ShaderBlock", lovrShaderBlock);
luax_registertype(L, "Texture", lovrTexture);
luax_extendtype(L, "Texture", "Canvas", lovrTexture, lovrCanvas);
luax_registertype(L, "Canvas", lovrCanvas);
luax_pushconf(L);
@ -436,22 +436,10 @@ int l_lovrGraphicsSetBlendMode(lua_State* L) {
}
int l_lovrGraphicsGetCanvas(lua_State* L) {
Canvas* canvas[MAX_CANVASES];
int count;
lovrGraphicsGetCanvas(canvas, &count);
for (int i = 0; i < count; i++) {
luax_pushobject(L, canvas[i]);
}
return count;
return 0;
}
int l_lovrGraphicsSetCanvas(lua_State* L) {
Canvas* canvas[MAX_CANVASES];
int count = MIN(lua_gettop(L), MAX_CANVASES);
for (int i = 0; i < count; i++) {
canvas[i] = luax_checktype(L, i + 1, Canvas);
}
lovrGraphicsSetCanvas(canvas, count);
return 0;
}
@ -949,37 +937,7 @@ int l_lovrGraphicsNewShaderBlock(lua_State* L) {
}
int l_lovrGraphicsNewCanvas(lua_State* L) {
int width = luaL_checkinteger(L, 1);
int height = luaL_checkinteger(L, 2);
luaL_argcheck(L, width > 0, 1, "width must be positive");
luaL_argcheck(L, height > 0, 2, "height must be positive");
TextureFormat format = FORMAT_RGBA;
CanvasFlags flags = { .msaa = 0, .depth = true, .stencil = false, .mipmaps = true };
if (lua_istable(L, 3)) {
lua_getfield(L, 3, "format");
format = luaL_checkoption(L, -1, "rgba", TextureFormats);
lua_pop(L, 1);
lua_getfield(L, 3, "msaa");
flags.msaa = luaL_optinteger(L, -1, 0);
lua_pop(L, 1);
lua_getfield(L, 3, "depth");
flags.depth = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, 3, "stencil");
flags.stencil = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, 3, "mipmaps");
flags.mipmaps = lua_toboolean(L, -1);
lua_pop(L, 1);
}
Canvas* canvas = lovrCanvasCreate(width, height, format, flags);
Canvas* canvas = lovrCanvasCreate();
luax_pushobject(L, canvas);
lovrRelease(canvas);
return 1;

View File

@ -1,46 +1,6 @@
#include "api.h"
#include "graphics/graphics.h"
#include "graphics/canvas.h"
int l_lovrCanvasRenderTo(lua_State* L) {
Canvas* canvas = luax_checktype(L, 1, Canvas);
luaL_checktype(L, 2, LUA_TFUNCTION);
int nargs = lua_gettop(L) - 2;
Canvas* old[MAX_CANVASES];
int count;
lovrGraphicsGetCanvas(old, &count);
lovrGraphicsSetCanvas(&canvas, 1);
lua_call(L, nargs, 0);
lovrGraphicsSetCanvas(old, count);
return 0;
}
int l_lovrCanvasGetFormat(lua_State* L) {
Canvas* canvas = luax_checktype(L, 1, Canvas);
TextureFormat format = lovrCanvasGetFormat(canvas);
lua_pushstring(L, TextureFormats[format]);
return 1;
}
int l_lovrCanvasGetMSAA(lua_State* L) {
Canvas* canvas = luax_checktype(L, 1, Canvas);
lua_pushinteger(L, lovrCanvasGetMSAA(canvas));
return 1;
}
int l_lovrCanvasNewTextureData(lua_State* L) {
Canvas* canvas = luax_checktype(L, 1, Canvas);
TextureData* textureData = lovrCanvasNewTextureData(canvas);
luax_pushobject(L, textureData);
lovrRelease(textureData);
return 1;
}
const luaL_Reg lovrCanvas[] = {
{ "renderTo", l_lovrCanvasRenderTo },
{ "getFormat", l_lovrCanvasGetFormat },
{ "getMSAA", l_lovrCanvasGetMSAA },
{ "newTextureData", l_lovrCanvasNewTextureData },
{ NULL, NULL }
};

View File

@ -1,25 +1,6 @@
#include "graphics/texture.h"
#include "data/textureData.h"
#include <stdbool.h>
#pragma once
#define MAX_CANVASES 4
typedef struct {
int msaa;
bool depth;
bool stencil;
bool mipmaps;
} CanvasFlags;
typedef struct Canvas Canvas;
bool lovrCanvasSupportsFormat(TextureFormat format);
Canvas* lovrCanvasCreate(int width, int height, TextureFormat format, CanvasFlags flags);
Canvas* lovrCanvasCreate();
void lovrCanvasDestroy(void* ref);
void lovrCanvasResolve(Canvas* canvas);
TextureFormat lovrCanvasGetFormat(Canvas* canvas);
int lovrCanvasGetMSAA(Canvas* canvas);
TextureData* lovrCanvasNewTextureData(Canvas* canvas);

View File

@ -34,7 +34,6 @@ void lovrGraphicsDestroy() {
}
lovrGraphicsSetShader(NULL);
lovrGraphicsSetFont(NULL);
lovrGraphicsSetCanvas(NULL, 0);
for (int i = 0; i < MAX_DEFAULT_SHADERS; i++) {
lovrRelease(state.defaultShaders[i]);
}
@ -142,9 +141,8 @@ void lovrGraphicsSetCamera(Camera* camera, bool clear) {
}
if (clear) {
int canvasCount = state.camera.canvas != NULL;
Color backgroundColor = lovrGraphicsGetBackgroundColor();
lovrGpuClear(&state.camera.canvas, canvasCount, &backgroundColor, &(float) { 1. }, &(int) { 0 });
lovrGpuClear(state.camera.canvas, &backgroundColor, &(float) { 1. }, &(int) { 0 });
}
}
@ -158,7 +156,6 @@ void lovrGraphicsReset() {
lovrGraphicsSetCamera(NULL, false);
lovrGraphicsSetBackgroundColor((Color) { 0, 0, 0, 1 });
lovrGraphicsSetBlendMode(BLEND_ALPHA, BLEND_ALPHA_MULTIPLY);
lovrGraphicsSetCanvas(NULL, 0);
lovrGraphicsSetColor((Color) { 1, 1, 1, 1 });
lovrGraphicsSetCullingEnabled(false);
lovrGraphicsSetDefaultFilter((TextureFilter) { .mode = FILTER_TRILINEAR });
@ -176,17 +173,11 @@ void lovrGraphicsReset() {
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));
for (int i = 0; i < state.pipelines[state.pipeline].canvasCount; i++) {
lovrRetain(state.pipelines[state.pipeline].canvas[i]);
}
lovrRetain(state.pipelines[state.pipeline].font);
lovrRetain(state.pipelines[state.pipeline].shader);
}
void lovrGraphicsPopPipeline() {
for (int i = 0; i < state.pipelines[state.pipeline].canvasCount; i++) {
lovrRelease(state.pipelines[state.pipeline].canvas[i]);
}
lovrRelease(state.pipelines[state.pipeline].font);
lovrRelease(state.pipelines[state.pipeline].shader);
lovrAssert(--state.pipeline >= 0, "Unbalanced pipeline stack (more pops than pushes?)");
@ -210,24 +201,6 @@ void lovrGraphicsSetBlendMode(BlendMode mode, BlendAlphaMode alphaMode) {
state.pipelines[state.pipeline].blendAlphaMode = alphaMode;
}
void lovrGraphicsGetCanvas(Canvas** canvas, int* count) {
*count = state.pipelines[state.pipeline].canvasCount;
memcpy(canvas, state.pipelines[state.pipeline].canvas, *count);
}
void lovrGraphicsSetCanvas(Canvas** canvas, int count) {
for (int i = 0; i < count; i++) {
lovrRetain(canvas[i]);
}
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() {
return state.pipelines[state.pipeline].color;
}
@ -381,11 +354,7 @@ VertexPointer lovrGraphicsGetVertexPointer(uint32_t count) {
void lovrGraphicsClear(Color* color, float* depth, int* stencil) {
Pipeline* pipeline = &state.pipelines[state.pipeline];
if (pipeline->canvasCount > 0) {
lovrGpuClear(pipeline->canvas, pipeline->canvasCount, color, depth, stencil);
} else {
lovrGpuClear(&state.camera.canvas, state.camera.canvas != NULL, color, depth, stencil);
}
lovrGpuClear(state.camera.canvas, color, depth, stencil);
}
void lovrGraphicsDraw(DrawOptions* draw) {

View File

@ -106,8 +106,6 @@ typedef struct {
Color backgroundColor;
BlendMode blendMode;
BlendAlphaMode blendAlphaMode;
Canvas* canvas[MAX_CANVASES];
int canvasCount;
Color color;
bool culling;
CompareMode depthTest;
@ -192,8 +190,6 @@ Color lovrGraphicsGetBackgroundColor();
void lovrGraphicsSetBackgroundColor(Color color);
void lovrGraphicsGetBlendMode(BlendMode* mode, BlendAlphaMode* alphaMode);
void lovrGraphicsSetBlendMode(BlendMode mode, BlendAlphaMode alphaMode);
void lovrGraphicsGetCanvas(Canvas** canvas, int* count);
void lovrGraphicsSetCanvas(Canvas** canvas, int count);
Color lovrGraphicsGetColor();
void lovrGraphicsSetColor(Color color);
bool lovrGraphicsIsCullingEnabled();
@ -252,7 +248,7 @@ typedef void (*gpuProc)(void);
void lovrGpuInit(bool srgb, bool singlepass, gpuProc (*getProcAddress)(const char*));
void lovrGpuDestroy();
void lovrGpuClear(Canvas** canvas, int canvasCount, Color* color, float* depth, int* stencil);
void lovrGpuClear(Canvas* canvas, Color* color, float* depth, int* stencil);
void lovrGpuDraw(DrawCommand* command);
void lovrGpuCompute(Shader* shader, int x, int y, int z);
void lovrGpuWait(uint8_t flags);

View File

@ -49,8 +49,6 @@ static struct {
bool stencilWriting;
Winding winding;
bool wireframe;
Canvas* canvas[MAX_CANVASES];
int canvasCount;
uint32_t framebuffer;
uint32_t indexBuffer;
uint32_t program;
@ -111,13 +109,8 @@ struct Texture {
};
struct Canvas {
Texture texture;
GLuint framebuffer;
GLuint resolveFramebuffer;
GLuint depthStencilBuffer;
GLuint msaaTexture;
CanvasFlags flags;
Canvas** attachments[MAX_CANVASES];
Ref ref;
uint32_t framebuffer;
};
typedef struct {
@ -286,16 +279,6 @@ static GLenum convertMeshDrawMode(MeshDrawMode mode) {
}
}
static bool isCanvasFormatSupported(TextureFormat format) {
switch (format) {
case FORMAT_DXT1:
case FORMAT_DXT3:
case FORMAT_DXT5:
return false;
default: return true;
}
}
static UniformType getUniformType(GLenum type, const char* debug) {
switch (type) {
case GL_FLOAT:
@ -448,13 +431,6 @@ static void lovrGpuCleanupIncoherentResource(void* resource, uint8_t incoherent)
// GPU
static void lovrGpuBindFramebuffer(uint32_t framebuffer) {
if (state.framebuffer != framebuffer) {
state.framebuffer = framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
}
}
static void lovrGpuBindIndexBuffer(uint32_t indexBuffer) {
if (state.indexBuffer != indexBuffer) {
state.indexBuffer = indexBuffer;
@ -529,13 +505,6 @@ static void lovrGpuBindVertexBuffer(uint32_t vertexBuffer) {
}
}
static void lovrGpuSetViewport(float viewport[4]) {
if (memcmp(state.viewport, viewport, 4 * sizeof(float))) {
memcpy(state.viewport, viewport, 4 * sizeof(float));
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
}
}
static void lovrGpuUseProgram(uint32_t program) {
if (state.program != program) {
state.program = program;
@ -590,34 +559,7 @@ void lovrGpuDestroy() {
}
}
void lovrGpuClear(Canvas** canvas, int canvasCount, Color* color, float* depth, int* stencil) {
lovrGpuBindFramebuffer(canvasCount > 0 ? canvas[0]->framebuffer : 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) {
if (!state.depthWrite) {
state.depthWrite = true;
glDepthMask(state.depthWrite);
}
glClearBufferfv(GL_DEPTH, 0, depth);
}
if (stencil) {
glClearBufferiv(GL_STENCIL, 0, stencil);
}
if (canvasCount > 0) {
lovrCanvasResolve(canvas[0]);
}
void lovrGpuClear(Canvas* canvas, Color* color, float* depth, int* stencil) {
}
void lovrGraphicsStencil(StencilAction action, int replaceValue, StencilCallback callback, void* userdata) {
@ -864,65 +806,14 @@ void lovrGpuDraw(DrawCommand* command) {
lovrShaderSetMatrices(shader, "lovrMaterialTransform", material->transform, 0, 9);
// Canvas
Canvas** canvas = pipeline->canvasCount > 0 ? pipeline->canvas : &command->camera.canvas;
int canvasCount = pipeline->canvasCount > 0 ? pipeline->canvasCount : (command->camera.canvas != NULL);
if (canvasCount != state.canvasCount || memcmp(state.canvas, canvas, canvasCount * sizeof(Canvas*))) {
if (state.canvasCount > 0) {
lovrCanvasResolve(state.canvas[0]);
}
state.canvasCount = canvasCount;
if (canvasCount > 0) {
memcpy(state.canvas, canvas, canvasCount * sizeof(Canvas*));
lovrGpuBindFramebuffer(canvas[0]->framebuffer);
GLenum buffers[MAX_CANVASES];
for (int i = 0; i < canvasCount; i++) {
buffers[i] = GL_COLOR_ATTACHMENT0 + i;
if (canvas[i]->flags.msaa > 0) {
glFramebufferRenderbuffer(GL_FRAMEBUFFER, buffers[i], GL_RENDERBUFFER, canvas[i]->msaaTexture);
} else {
glFramebufferTexture2D(GL_FRAMEBUFFER, buffers[i], GL_TEXTURE_2D, lovrTextureGetId((Texture*) canvas[i]), 0);
}
}
glDrawBuffers(canvasCount, buffers);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
lovrAssert(status == GL_FRAMEBUFFER_COMPLETE, "Unable to bind framebuffer");
} else {
lovrGpuBindFramebuffer(0);
}
}
// We need to synchronize if any attached textures have pending writes
for (int i = 0; i < canvasCount; i++) {
if ((canvas[i]->texture.incoherent >> BARRIER_CANVAS) & 1) {
lovrGpuWait(1 << BARRIER_CANVAS);
break;
}
}
// Bind attributes
lovrMeshBind(mesh, shader);
bool stereo = pipeline->canvasCount == 0 && command->camera.stereo == true;
bool stereo = false;
int drawCount = 1 + (stereo == true && !state.singlepass);
// Draw (TODEW)
for (int i = 0; i < drawCount; i++) {
if (pipeline->canvasCount > 0) {
int width = lovrTextureGetWidth((Texture*) pipeline->canvas[0], 0);
int height = lovrTextureGetHeight((Texture*) pipeline->canvas[0], 0);
lovrGpuSetViewport((float[4]) { 0, 0, width, height });
#ifndef EMSCRIPTEN
} else if (state.singlepass) {
glViewportArrayv(0, 2, command->camera.viewport[0]);
#endif
} else {
lovrGpuSetViewport(command->camera.viewport[i]);
}
// Bind uniforms
lovrShaderSetInts(shader, "lovrIsStereo", &(int) { stereo && state.singlepass }, 0, 1);
@ -1295,125 +1186,16 @@ void lovrTextureSetWrap(Texture* texture, TextureWrap wrap) {
// Canvas
Canvas* lovrCanvasCreate(int width, int height, TextureFormat format, CanvasFlags flags) {
lovrAssert(isCanvasFormatSupported(format), "Unsupported texture format for Canvas");
Canvas* lovrCanvasCreate() {
Canvas* canvas = lovrAlloc(Canvas, lovrCanvasDestroy);
Texture* texture = lovrTextureCreate(TEXTURE_2D, NULL, 0, true, flags.mipmaps);
if (!canvas || !texture) {
lovrRelease(canvas);
lovrRelease(texture);
return NULL;
}
lovrTextureAllocate(texture, width, height, 1, format);
Ref ref = canvas->texture.ref;
canvas->texture = *texture;
canvas->texture.ref = ref;
canvas->flags = flags;
// Framebuffer
glGenFramebuffers(1, &canvas->framebuffer);
lovrGpuBindFramebuffer(canvas->framebuffer);
// Color attachment
if (flags.msaa > 0) {
GLenum internalFormat = convertTextureFormatInternal(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);
lovrGpuBindFramebuffer(canvas->resolveFramebuffer);
glBindTexture(GL_TEXTURE_2D, canvas->texture.id);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas->texture.id, 0);
lovrGpuBindFramebuffer(canvas->framebuffer);
}
lovrAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Error creating Canvas");
lovrGpuClear(&canvas, 1, &(Color) { 0, 0, 0, 0 }, &(float) { 1. }, &(int) { 0 });
if (!canvas) return NULL;
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);
}
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);
lovrGpuBindFramebuffer(canvas->resolveFramebuffer);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
if (canvas->flags.mipmaps) {
lovrGpuBindTexture(&canvas->texture, 0);
glGenerateMipmap(canvas->texture.target);
}
}
TextureFormat lovrCanvasGetFormat(Canvas* canvas) {
return canvas->texture.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;
if ((canvas->texture.incoherent >> BARRIER_TEXTURE) & 1) {
lovrGpuWait(1 << BARRIER_TEXTURE);
}
lovrGpuBindFramebuffer(canvas->framebuffer);
glReadPixels(0, 0, canvas->texture.width, canvas->texture.height, GL_RGBA, GL_UNSIGNED_BYTE, textureData->blob.data);
return textureData;
free(ref);
}
// Shader

View File

@ -240,11 +240,7 @@ static void ensureCanvas() {
return;
}
int maxMSAA = lovrGraphicsGetLimits().textureMSAA;
int msaa = state.msaa == -1 ? maxMSAA : MIN(state.msaa, maxMSAA);
state.system->GetRecommendedRenderTargetSize(&state.renderWidth, &state.renderHeight);
CanvasFlags flags = { .msaa = msaa, .depth = true, .stencil = true, .mipmaps = false };
state.canvas = lovrCanvasCreate(state.renderWidth * 2, state.renderHeight, FORMAT_RGB, flags);
state.canvas = lovrCanvasCreate();
}
static bool openvrInit(float offset, int msaa) {
@ -685,12 +681,10 @@ static void openvrRenderTo(void (*callback)(void*), void* userdata) {
lovrGraphicsSetCamera(&camera, true);
callback(userdata);
lovrGraphicsSetCamera(NULL, false);
lovrGraphicsSetCanvas(NULL, 0);
lovrCanvasResolve(state.canvas);
state.isRendering = false;
// Submit
uintptr_t texture = (uintptr_t) lovrTextureGetId((Texture*) state.canvas);
uintptr_t texture = (uintptr_t) 0; // TODO
EColorSpace colorSpace = lovrGraphicsIsGammaCorrect() ? EColorSpace_ColorSpace_Linear : EColorSpace_ColorSpace_Gamma;
Texture_t eyeTexture = { (void*) texture, ETextureType_TextureType_OpenGL, colorSpace };
VRTextureBounds_t left = { 0, 0, .5, 1. };
@ -703,7 +697,7 @@ static void openvrRenderTo(void (*callback)(void*), void* userdata) {
lovrGraphicsPushPipeline();
lovrGraphicsSetColor((Color) { 1, 1, 1, 1 });
lovrGraphicsSetShader(NULL);
lovrGraphicsFill((Texture*) state.canvas);
// TODO
lovrGraphicsPopPipeline();
}
}