lovr/src/modules/graphics/graphics.c

1344 lines
42 KiB
C

#include "graphics/graphics.h"
#include "graphics/buffer.h"
#include "graphics/canvas.h"
#include "graphics/material.h"
#include "graphics/mesh.h"
#include "graphics/shader.h"
#include "graphics/texture.h"
#include "data/rasterizer.h"
#include "event/event.h"
#include "math/math.h"
#include "core/maf.h"
#include "core/os.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define MAX_TRANSFORMS 64
#define MAX_BATCHES 4
#define MAX_DRAWS 256
typedef enum {
STREAM_VERTEX,
STREAM_DRAWID,
STREAM_INDEX,
STREAM_MODEL,
STREAM_COLOR,
STREAM_FRAME,
MAX_STREAMS
} StreamType;
typedef enum {
BATCH_POINTS,
BATCH_LINES,
BATCH_PLANE,
BATCH_BOX,
BATCH_ARC,
BATCH_SPHERE,
BATCH_CYLINDER,
BATCH_SKYBOX,
BATCH_TEXT,
BATCH_FILL,
BATCH_MESH
} BatchType;
typedef union {
struct { DrawStyle style; } triangles;
struct { DrawStyle style; } plane;
struct { DrawStyle style; } box;
struct { DrawStyle style; ArcMode mode; float r1; float r2; int segments; } arc;
struct { float r1; float r2; bool capped; int segments; } cylinder;
struct { int segments; } sphere;
struct { float spread; } text;
struct { float u; float v; float w; float h; } fill;
struct { uint32_t rangeStart; uint32_t rangeCount; uint32_t instances; float* pose; } mesh;
} BatchParams;
typedef struct {
BatchType type;
BatchParams params;
DrawMode topology;
DefaultShader shader;
Mesh* mesh;
Pipeline* pipeline;
Material* material;
Texture* texture;
mat4 transform;
uint32_t vertexCount;
uint32_t indexCount;
float** vertices;
uint16_t** indices;
uint16_t* baseVertex;
bool instanced;
} BatchRequest;
typedef struct {
BatchType type;
BatchParams params;
DrawCommand draw;
Material* material;
mat4 transforms;
Color* colors;
uint32_t drawStart;
uint32_t drawCount;
bool indexed;
} Batch;
typedef struct {
float viewMatrix[2][16];
float projection[2][16];
} FrameData;
static struct {
bool initialized;
bool debug;
int width;
int height;
Canvas* backbuffer;
FrameData frameData;
bool frameDataDirty;
Canvas* defaultCanvas;
Shader* defaultShaders[MAX_DEFAULT_SHADERS][2];
Material* defaultMaterial;
Font* defaultFont;
TextureFilter defaultFilter;
float transforms[MAX_TRANSFORMS][16];
int transform;
Color backgroundColor;
Color linearBackgroundColor;
Canvas* canvas;
Color color;
Color linearColor;
Font* font;
Pipeline pipeline;
float pointSize;
Shader* shader;
Mesh* mesh;
Mesh* instancedMesh;
Buffer* identityBuffer;
Buffer* buffers[MAX_STREAMS];
uint32_t head[MAX_STREAMS];
uint32_t tail[MAX_STREAMS];
Batch batches[MAX_BATCHES];
uint8_t batchCount;
} state;
static const uint32_t bufferCount[] = {
[STREAM_VERTEX] = (1 << 16) - 1,
[STREAM_DRAWID] = (1 << 16) - 1,
[STREAM_INDEX] = 1 << 16,
#if defined(LOVR_WEBGL) // Work around bugs where big UBOs don't work
[STREAM_MODEL] = MAX_DRAWS,
[STREAM_COLOR] = MAX_DRAWS,
#else
[STREAM_MODEL] = MAX_DRAWS * MAX_BATCHES,
[STREAM_COLOR] = MAX_DRAWS * MAX_BATCHES,
#endif
[STREAM_FRAME] = 4
};
static const size_t bufferStride[] = {
[STREAM_VERTEX] = 8 * sizeof(float),
[STREAM_DRAWID] = sizeof(uint8_t),
[STREAM_INDEX] = sizeof(uint16_t),
[STREAM_MODEL] = 16 * sizeof(float),
[STREAM_COLOR] = 4 * sizeof(float),
[STREAM_FRAME] = sizeof(FrameData)
};
static const BufferType bufferType[] = {
[STREAM_VERTEX] = BUFFER_VERTEX,
[STREAM_DRAWID] = BUFFER_GENERIC,
[STREAM_INDEX] = BUFFER_INDEX,
[STREAM_MODEL] = BUFFER_UNIFORM,
[STREAM_COLOR] = BUFFER_UNIFORM,
[STREAM_FRAME] = BUFFER_UNIFORM
};
static void gammaCorrect(Color* color) {
color->r = lovrMathGammaToLinear(color->r);
color->g = lovrMathGammaToLinear(color->g);
color->b = lovrMathGammaToLinear(color->b);
}
static void onQuitRequest(void) {
lovrEventPush((Event) { .type = EVENT_QUIT, .data.quit = { .exitCode = 0 } });
}
static void onResizeWindow(int width, int height) {
state.width = width;
state.height = height;
lovrCanvasSetWidth(state.defaultCanvas, width);
lovrCanvasSetHeight(state.defaultCanvas, height);
lovrEventPush((Event) { .type = EVENT_RESIZE, .data.resize = { width, height } });
}
static void* lovrGraphicsMapBuffer(StreamType type, uint32_t count) {
lovrAssert(count <= bufferCount[type], "Whoa there! Tried to get %d elements from a buffer that only has %d elements.", count, bufferCount[type]);
if (state.head[type] + count > bufferCount[type]) {
lovrAssert(state.batchCount == 0, "Internal error: Batches still exist during Buffer reset");
lovrBufferDiscard(state.buffers[type]);
state.tail[type] = 0;
state.head[type] = 0;
}
return lovrBufferMap(state.buffers[type], state.head[type] * bufferStride[type], true);
}
// Base
bool lovrGraphicsInit(bool debug) {
state.debug = debug;
return false; // See lovrGraphicsCreateWindow for actual initialization
}
void lovrGraphicsDestroy() {
if (!state.initialized) return;
lovrGraphicsSetShader(NULL);
lovrGraphicsSetFont(NULL);
lovrGraphicsSetCanvas(NULL);
for (int i = 0; i < MAX_DEFAULT_SHADERS; i++) {
lovrRelease(state.defaultShaders[i][false], lovrShaderDestroy);
lovrRelease(state.defaultShaders[i][true], lovrShaderDestroy);
}
for (int i = 0; i < MAX_STREAMS; i++) {
lovrRelease(state.buffers[i], lovrBufferDestroy);
}
lovrRelease(state.mesh, lovrMeshDestroy);
lovrRelease(state.instancedMesh, lovrMeshDestroy);
lovrRelease(state.identityBuffer, lovrBufferDestroy);
lovrRelease(state.defaultMaterial, lovrMaterialDestroy);
lovrRelease(state.defaultFont, lovrFontDestroy);
lovrRelease(state.defaultCanvas, lovrCanvasDestroy);
lovrGpuDestroy();
memset(&state, 0, sizeof(state));
}
void lovrGraphicsPresent() {
lovrGraphicsFlush();
os_window_swap();
lovrGpuPresent();
}
void lovrGraphicsCreateWindow(WindowFlags* flags) {
os_window_config config = {
.width = flags->width,
.height = flags->height,
.fullscreen = flags->fullscreen,
.resizable = flags->resizable,
.debug = state.debug,
.vsync = flags->vsync,
.msaa = flags->msaa,
.title = flags->title,
.icon.data = flags->icon.data,
.icon.width = flags->icon.width,
.icon.height = flags->icon.height
};
lovrAssert(!state.initialized, "Window is already created");
lovrAssert(os_window_open(&config), "Could not create window");
os_window_set_vsync(flags->vsync); // Force vsync in case lovr.headset changed it in a previous restart
os_on_quit(onQuitRequest);
os_on_resize(onResizeWindow);
os_window_get_fbsize(&state.width, &state.height);
lovrGpuInit(os_get_gl_proc_address, state.debug);
state.defaultCanvas = lovrCanvasCreateFromHandle(state.width, state.height, (CanvasFlags) { .stereo = false }, 0, 0, 0, 1, true);
state.backbuffer = state.defaultCanvas;
for (int i = 0; i < MAX_STREAMS; i++) {
state.buffers[i] = lovrBufferCreate(bufferCount[i] * bufferStride[i], NULL, bufferType[i], USAGE_STREAM, false);
}
// The identity buffer is used for autoinstanced meshes and instanced primitives and maps the
// instance ID to a vertex attribute. Its contents never change, so they are initialized here.
state.identityBuffer = lovrBufferCreate(MAX_DRAWS * sizeof(uint8_t), NULL, BUFFER_VERTEX, USAGE_STATIC, false);
uint8_t* id = lovrBufferMap(state.identityBuffer, 0, true);
for (int i = 0; i < MAX_DRAWS; i++) id[i] = i;
lovrBufferFlush(state.identityBuffer, 0, MAX_DRAWS);
lovrBufferUnmap(state.identityBuffer);
Buffer* vertexBuffer = state.buffers[STREAM_VERTEX];
size_t stride = bufferStride[STREAM_VERTEX];
MeshAttribute position = { .buffer = vertexBuffer, .offset = 0, .stride = stride, .type = F32, .components = 3 };
MeshAttribute normal = { .buffer = vertexBuffer, .offset = 12, .stride = stride, .type = F32, .components = 3 };
MeshAttribute texCoord = { .buffer = vertexBuffer, .offset = 24, .stride = stride, .type = F32, .components = 2 };
MeshAttribute drawId = { .buffer = state.buffers[STREAM_DRAWID], .type = U8, .components = 1 };
MeshAttribute identity = { .buffer = state.identityBuffer, .type = U8, .components = 1, .divisor = 1 };
state.mesh = lovrMeshCreate(DRAW_TRIANGLES, NULL, 0);
lovrMeshAttachAttribute(state.mesh, "lovrPosition", &position);
lovrMeshAttachAttribute(state.mesh, "lovrNormal", &normal);
lovrMeshAttachAttribute(state.mesh, "lovrTexCoord", &texCoord);
lovrMeshAttachAttribute(state.mesh, "lovrDrawID", &drawId);
state.instancedMesh = lovrMeshCreate(DRAW_TRIANGLES, NULL, 0);
lovrMeshAttachAttribute(state.instancedMesh, "lovrPosition", &position);
lovrMeshAttachAttribute(state.instancedMesh, "lovrNormal", &normal);
lovrMeshAttachAttribute(state.instancedMesh, "lovrTexCoord", &texCoord);
lovrMeshAttachAttribute(state.instancedMesh, "lovrDrawID", &identity);
lovrGraphicsReset();
state.initialized = true;
}
int lovrGraphicsGetWidth() {
return state.width;
}
int lovrGraphicsGetHeight() {
return state.height;
}
float lovrGraphicsGetPixelDensity() {
int width, height, framebufferWidth, framebufferHeight;
os_window_get_size(&width, &height);
os_window_get_fbsize(&framebufferWidth, &framebufferHeight);
if (width == 0 || framebufferWidth == 0) {
return 0.f;
} else {
return (float) framebufferWidth / (float) width;
}
}
void lovrGraphicsSetBackbuffer(Canvas* canvas, bool stereo, bool clear) {
lovrGraphicsFlush();
if (!canvas) {
canvas = state.defaultCanvas;
lovrCanvasSetStereo(canvas, stereo);
}
if (canvas != state.backbuffer) {
lovrCanvasResolve(state.backbuffer);
state.backbuffer = canvas;
}
if (clear) {
lovrGpuClear(state.backbuffer, &state.linearBackgroundColor, &(float) { 1. }, &(int) { 0 });
}
}
void lovrGraphicsGetViewMatrix(uint32_t index, float* viewMatrix) {
lovrAssert(index < 2, "Invalid view index %d", index);
mat4_init(viewMatrix, state.frameData.viewMatrix[index]);
}
void lovrGraphicsSetViewMatrix(uint32_t index, float* viewMatrix) {
lovrAssert(index < 2, "Invalid view index %d", index);
lovrGraphicsFlush();
if (viewMatrix) {
mat4_init(state.frameData.viewMatrix[index], viewMatrix);
} else {
mat4_identity(state.frameData.viewMatrix[index]);
}
state.frameDataDirty = true;
}
void lovrGraphicsGetProjection(uint32_t index, float* projection) {
lovrAssert(index < 2, "Invalid view index %d", index);
mat4_init(projection, state.frameData.projection[index]);
}
void lovrGraphicsSetProjection(uint32_t index, float* projection) {
lovrAssert(index < 2, "Invalid view index %d", index);
lovrGraphicsFlush();
if (projection) {
mat4_init(state.frameData.projection[index], projection);
} else {
float fov = 67.f * (float) M_PI / 180.f;
float aspect = (float) state.width / state.height;
mat4_perspective(state.frameData.projection[index], .01f, 100.f, fov, aspect);
}
state.frameDataDirty = true;
}
Buffer* lovrGraphicsGetIdentityBuffer() {
return state.identityBuffer;
}
// State
void lovrGraphicsReset() {
state.transform = 0;
lovrGraphicsSetViewMatrix(0, NULL);
lovrGraphicsSetViewMatrix(1, NULL);
lovrGraphicsSetProjection(0, NULL);
lovrGraphicsSetProjection(1, NULL);
lovrGraphicsSetAlphaSampling(false);
lovrGraphicsSetBackgroundColor((Color) { 0, 0, 0, 1 });
lovrGraphicsSetBlendMode(BLEND_ALPHA, BLEND_ALPHA_MULTIPLY);
lovrGraphicsSetCanvas(NULL);
lovrGraphicsSetColor((Color) { 1, 1, 1, 1 });
lovrGraphicsSetColorMask(true, true, true, true);
lovrGraphicsSetCullingEnabled(false);
lovrGraphicsSetDefaultFilter((TextureFilter) { .mode = FILTER_TRILINEAR });
lovrGraphicsSetDepthTest(COMPARE_LEQUAL, true);
lovrGraphicsSetFont(NULL);
lovrGraphicsSetLineWidth(1.f);
lovrGraphicsSetPointSize(1.f);
lovrGraphicsSetShader(NULL);
lovrGraphicsSetStencilTest(COMPARE_NONE, 0);
lovrGraphicsSetWinding(WINDING_COUNTERCLOCKWISE);
lovrGraphicsSetWireframe(false);
lovrGraphicsOrigin();
}
bool lovrGraphicsGetAlphaSampling() {
return state.pipeline.alphaSampling;
}
void lovrGraphicsSetAlphaSampling(bool sample) {
state.pipeline.alphaSampling = sample;
}
Color lovrGraphicsGetBackgroundColor() {
return state.backgroundColor;
}
void lovrGraphicsSetBackgroundColor(Color color) {
state.backgroundColor = state.linearBackgroundColor = color;
#if !defined(LOVR_WEBGL)
gammaCorrect(&state.linearBackgroundColor);
#endif
}
void lovrGraphicsGetBlendMode(BlendMode* mode, BlendAlphaMode* alphaMode) {
*mode = state.pipeline.blendMode;
*alphaMode = state.pipeline.blendAlphaMode;
}
void lovrGraphicsSetBlendMode(BlendMode mode, BlendAlphaMode alphaMode) {
state.pipeline.blendMode = mode;
state.pipeline.blendAlphaMode = alphaMode;
}
Canvas* lovrGraphicsGetCanvas() {
return state.canvas;
}
void lovrGraphicsSetCanvas(Canvas* canvas) {
if (state.canvas && canvas != state.canvas) {
// The canvas must be flushed because if someone uses its textures to do a draw there is no way
// to know that using that Texture requires the Canvas' batches to be flushed.
lovrGraphicsFlushCanvas(state.canvas);
lovrCanvasResolve(state.canvas);
}
lovrRetain(canvas);
lovrRelease(state.canvas, lovrCanvasDestroy);
state.canvas = canvas;
}
Color lovrGraphicsGetColor() {
return state.color;
}
void lovrGraphicsSetColor(Color color) {
state.color = state.linearColor = color;
gammaCorrect(&state.linearColor);
}
void lovrGraphicsGetColorMask(bool* r, bool* g, bool* b, bool* a) {
*r = state.pipeline.colorMask & 0x8;
*g = state.pipeline.colorMask & 0x4;
*b = state.pipeline.colorMask & 0x2;
*a = state.pipeline.colorMask & 0x1;
}
void lovrGraphicsSetColorMask(bool r, bool g, bool b, bool a) {
state.pipeline.colorMask = (r << 3) | (g << 2) | (b << 1) | a;
}
bool lovrGraphicsIsCullingEnabled() {
return state.pipeline.culling;
}
void lovrGraphicsSetCullingEnabled(bool culling) {
state.pipeline.culling = culling;
}
TextureFilter lovrGraphicsGetDefaultFilter() {
return state.defaultFilter;
}
void lovrGraphicsSetDefaultFilter(TextureFilter filter) {
state.defaultFilter = filter;
}
void lovrGraphicsGetDepthTest(CompareMode* mode, bool* write) {
*mode = state.pipeline.depthTest;
*write = state.pipeline.depthWrite;
}
void lovrGraphicsSetDepthTest(CompareMode mode, bool write) {
state.pipeline.depthTest = mode;
state.pipeline.depthWrite = write;
}
Font* lovrGraphicsGetFont() {
if (!state.font) {
if (!state.defaultFont) {
Rasterizer* rasterizer = lovrRasterizerCreate(NULL, 32);
state.defaultFont = lovrFontCreate(rasterizer, 1, 3.);
lovrRelease(rasterizer, lovrRasterizerDestroy);
}
lovrGraphicsSetFont(state.defaultFont);
}
return state.font;
}
void lovrGraphicsSetFont(Font* font) {
lovrRetain(font);
lovrRelease(state.font, lovrFontDestroy);
state.font = font;
}
float lovrGraphicsGetLineWidth() {
return state.pipeline.lineWidth;
}
void lovrGraphicsSetLineWidth(float width) {
state.pipeline.lineWidth = width;
}
float lovrGraphicsGetPointSize() {
return state.pointSize;
}
void lovrGraphicsSetPointSize(float size) {
state.pointSize = size;
}
Shader* lovrGraphicsGetShader() {
return state.shader;
}
void lovrGraphicsSetShader(Shader* shader) {
lovrAssert(!shader || lovrShaderGetType(shader) == SHADER_GRAPHICS, "Compute shaders can not be set as the active shader");
lovrRetain(shader);
lovrRelease(state.shader, lovrShaderDestroy);
state.shader = shader;
}
void lovrGraphicsGetStencilTest(CompareMode* mode, int* value) {
*mode = state.pipeline.stencilMode;
*value = state.pipeline.stencilValue;
}
void lovrGraphicsSetStencilTest(CompareMode mode, int value) {
state.pipeline.stencilMode = mode;
state.pipeline.stencilValue = value;
}
Winding lovrGraphicsGetWinding() {
return state.pipeline.winding;
}
void lovrGraphicsSetWinding(Winding winding) {
state.pipeline.winding = winding;
}
bool lovrGraphicsIsWireframe() {
return state.pipeline.wireframe;
}
void lovrGraphicsSetWireframe(bool wireframe) {
#ifdef LOVR_GL
state.pipeline.wireframe = wireframe;
#endif
}
// Transforms
void lovrGraphicsPush() {
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() {
lovrAssert(--state.transform >= 0, "Unbalanced matrix stack (more pops than pushes?)");
}
void lovrGraphicsOrigin() {
mat4_identity(state.transforms[state.transform]);
}
void lovrGraphicsTranslate(vec3 translation) {
mat4_translate(state.transforms[state.transform], translation[0], translation[1], translation[2]);
}
void lovrGraphicsRotate(quat rotation) {
mat4_rotateQuat(state.transforms[state.transform], rotation);
}
void lovrGraphicsScale(vec3 scale) {
mat4_scale(state.transforms[state.transform], scale[0], scale[1], scale[2]);
}
void lovrGraphicsMatrixTransform(mat4 transform) {
mat4_mul(state.transforms[state.transform], transform);
}
// Rendering
static void lovrGraphicsBatch(BatchRequest* req) {
// Resolve objects
Mesh* mesh = req->mesh ? req->mesh : (req->instanced ? state.instancedMesh : state.mesh);
Canvas* canvas = state.canvas ? state.canvas : state.backbuffer;
bool stereo = lovrCanvasIsStereo(canvas);
Shader* shader = state.shader ? state.shader : (state.defaultShaders[req->shader][stereo] ? state.defaultShaders[req->shader][stereo] : (state.defaultShaders[req->shader][stereo] = lovrShaderCreateDefault(req->shader, NULL, 0, stereo)));
Pipeline* pipeline = req->pipeline ? req->pipeline : &state.pipeline;
Material* material = req->material ? req->material : (state.defaultMaterial ? state.defaultMaterial : (state.defaultMaterial = lovrMaterialCreate()));
if (!req->material) {
if (req->type == BATCH_SKYBOX && lovrTextureGetType(req->texture) == TEXTURE_CUBE) {
lovrShaderSetTextures(shader, "lovrSkyboxTexture", &req->texture, 0, 1);
} else {
lovrMaterialSetTexture(material, TEXTURE_DIFFUSE, req->texture);
}
}
if (lovrShaderHasUniform(shader, "lovrPose")) {
if (req->type == BATCH_MESH && req->params.mesh.pose) {
lovrShaderSetMatrices(shader, "lovrPose", req->params.mesh.pose, 0, MAX_BONES * 16);
} else {
lovrShaderSetMatrices(shader, "lovrPose", (float[]) MAT4_IDENTITY, 0, 16);
}
}
// Try to find an existing batch to use
Batch* batch = NULL;
for (int i = state.batchCount - 1; i >= 0; i--) {
if (req->type == BATCH_MESH && req->params.mesh.instances > 1) { break; }
Batch* b = &state.batches[i];
if (b->type != req->type) { goto next; }
if (b->drawCount >= MAX_DRAWS) { goto next; }
if (b->draw.mesh != mesh) { goto next; }
if (b->draw.canvas != canvas) { goto next; }
if (b->draw.shader != shader) { goto next; }
if (b->material != material) { goto next; }
if (memcmp(&b->draw.pipeline, pipeline, sizeof(Pipeline))) { goto next; }
if (memcmp(&b->params, &req->params, sizeof(BatchParams))) { goto next; }
batch = b;
break;
next:
// Draws can't be reordered when blending is on, depth test is off, or either of the batches
// are streaming their vertices (since the vertices of a batch must be contiguous)
if (b->draw.pipeline.blendMode != BLEND_NONE || pipeline->blendMode != BLEND_NONE) { break; }
if (b->draw.pipeline.depthTest == COMPARE_NONE || pipeline->depthTest == COMPARE_NONE) { break; }
if (!req->instanced) { break; }
}
// The final draw id isn't known until the batch is fully resolved and all the potential flushes
// have occurred, so we have to do this weird thing where we map the draw id buffer early on but
// write the ids much later.
uint8_t* ids = NULL;
// Figure out if a flush is necessary before mapping buffers for vertex data or UBOs.
// - A flush is necessary if vertices are about to be written (during the first element of an
// instanced batch or any element of a stream batch) and any of the ranges go past the end.
// - If a new batch is required but there isn't space for it, flush to make space.
// - If a new batch is required, make sure there is space for the matrix/color UBO streams.
// It's important to flush before mapping any streams, because flushing unmaps all streams.
bool needFlush = false;
bool hasVertices = req->vertexCount > 0 && (!req->instanced || !batch);
bool hasIndices = hasVertices && req->indexCount > 0;
needFlush = needFlush || (hasVertices && state.head[STREAM_VERTEX] + req->vertexCount > bufferCount[STREAM_VERTEX]);
needFlush = needFlush || (hasVertices && state.head[STREAM_DRAWID] + req->vertexCount > bufferCount[STREAM_DRAWID]);
needFlush = needFlush || (hasIndices && state.head[STREAM_INDEX] + req->indexCount > bufferCount[STREAM_INDEX]);
needFlush = needFlush || (!batch && state.batchCount >= MAX_BATCHES);
needFlush = needFlush || (!batch && state.head[STREAM_MODEL] + MAX_DRAWS > bufferCount[STREAM_MODEL]);
needFlush = needFlush || (!batch && state.head[STREAM_COLOR] + MAX_DRAWS > bufferCount[STREAM_COLOR]);
if (needFlush) lovrGraphicsFlush();
if (req->vertexCount > 0 && (!req->instanced || !batch)) {
*(req->vertices) = lovrGraphicsMapBuffer(STREAM_VERTEX, req->vertexCount);
ids = lovrGraphicsMapBuffer(STREAM_DRAWID, req->vertexCount);
if (req->indexCount > 0) {
*(req->indices) = lovrGraphicsMapBuffer(STREAM_INDEX, req->indexCount);
*(req->baseVertex) = state.head[STREAM_VERTEX];
}
}
// Start a new batch
if (!batch || state.batchCount == 0) {
float* transforms = lovrGraphicsMapBuffer(STREAM_MODEL, MAX_DRAWS);
Color* colors = lovrGraphicsMapBuffer(STREAM_COLOR, MAX_DRAWS);
uint32_t rangeStart, rangeCount, instances;
if (req->type == BATCH_MESH) {
rangeStart = req->params.mesh.rangeStart;
rangeCount = req->params.mesh.rangeCount;
instances = req->instanced ? 0 : req->params.mesh.instances;
} else {
rangeStart = req->indexCount > 0 ? state.head[STREAM_INDEX] : state.head[STREAM_VERTEX];
rangeCount = 0;
instances = 0;
}
batch = &state.batches[state.batchCount++];
*batch = (Batch) {
.type = req->type,
.params = req->params,
.draw = {
.mesh = mesh,
.canvas = canvas,
.shader = shader,
.pipeline = *pipeline,
.topology = req->topology,
.rangeStart = rangeStart,
.rangeCount = rangeCount,
.instances = instances
},
.material = material,
.transforms = transforms,
.colors = colors,
.drawStart = state.head[STREAM_MODEL],
.indexed = req->indexCount > 0
};
state.head[STREAM_MODEL] += MAX_DRAWS;
state.head[STREAM_COLOR] += MAX_DRAWS;
}
// Transform
if (req->transform) {
float transform[16];
mat4_mul(mat4_init(transform, state.transforms[state.transform]), req->transform);
memcpy(&batch->transforms[16 * batch->drawCount], transform, 16 * sizeof(float));
} else {
memcpy(&batch->transforms[16 * batch->drawCount], state.transforms[state.transform], 16 * sizeof(float));
}
// Color
batch->colors[batch->drawCount] = state.linearColor;
// Cursors
if (!req->instanced || batch->drawCount == 0) {
if (ids) {
memset(ids, batch->drawCount, req->vertexCount * sizeof(uint8_t));
}
batch->draw.rangeCount += batch->indexed ? req->indexCount : req->vertexCount;
state.head[STREAM_VERTEX] += req->vertexCount;
state.head[STREAM_DRAWID] += req->vertexCount;
state.head[STREAM_INDEX] += req->indexCount;
}
if (req->instanced) {
batch->draw.instances++;
}
batch->drawCount++;
}
void lovrGraphicsFlush() {
if (state.batchCount == 0) {
return;
}
// Prevent infinite flushing >_>
int batchCount = state.batchCount;
state.batchCount = 0;
if (state.frameDataDirty) {
state.frameDataDirty = false;
void* data = lovrGraphicsMapBuffer(STREAM_FRAME, 1);
memcpy(data, &state.frameData, sizeof(FrameData));
state.head[STREAM_FRAME]++;
}
// Flush buffers
for (int i = 0; i < MAX_STREAMS; i++) {
lovrBufferFlush(state.buffers[i], state.tail[i] * bufferStride[i], (state.head[i] - state.tail[i]) * bufferStride[i]);
lovrBufferUnmap(state.buffers[i]);
state.tail[i] = state.head[i];
}
for (int b = 0; b < batchCount; b++) {
Batch* batch = &state.batches[b];
// Uniforms
lovrMaterialBind(batch->material, batch->draw.shader);
lovrShaderSetBlock(batch->draw.shader, "lovrModelBlock", state.buffers[STREAM_MODEL], batch->drawStart * bufferStride[STREAM_MODEL], MAX_DRAWS * bufferStride[STREAM_MODEL], ACCESS_READ);
lovrShaderSetBlock(batch->draw.shader, "lovrColorBlock", state.buffers[STREAM_COLOR], batch->drawStart * bufferStride[STREAM_COLOR], MAX_DRAWS * bufferStride[STREAM_COLOR], ACCESS_READ);
lovrShaderSetBlock(batch->draw.shader, "lovrFrameBlock", state.buffers[STREAM_FRAME], (state.head[STREAM_FRAME] - 1) * bufferStride[STREAM_FRAME], bufferStride[STREAM_FRAME], ACCESS_READ);
if (batch->type == BATCH_TEXT) {
Texture* texture = lovrMaterialGetTexture(batch->material, TEXTURE_DIFFUSE);
uint32_t width = lovrTextureGetWidth(texture, 0);
uint32_t height = lovrTextureGetHeight(texture, 0);
float range[2] = { batch->params.text.spread / width, batch->params.text.spread / height };
lovrShaderSetFloats(batch->draw.shader, "lovrSdfRange", range, 0, 2);
}
if (batch->draw.topology == DRAW_POINTS) {
lovrShaderSetFloats(batch->draw.shader, "lovrPointSize", &state.pointSize, 0, 1);
}
// Other bindings (TODO try to get rid of all this!)
if (batch->type == BATCH_MESH) {
lovrMeshSetAttributeEnabled(batch->draw.mesh, "lovrDrawID", batch->params.mesh.instances <= 1);
} else {
if (batch->draw.mesh == state.instancedMesh && batch->draw.instances <= 1) {
batch->draw.mesh = state.mesh;
}
if (batch->indexed) {
lovrMeshSetIndexBuffer(batch->draw.mesh, state.buffers[STREAM_INDEX], bufferCount[STREAM_INDEX], sizeof(uint16_t), 0);
} else {
lovrMeshSetIndexBuffer(batch->draw.mesh, NULL, 0, 0, 0);
}
}
lovrGpuDraw(&batch->draw);
}
}
void lovrGraphicsFlushCanvas(Canvas* canvas) {
for (int i = state.batchCount - 1; i >= 0; i--) {
if (state.batches[i].draw.canvas == canvas) {
lovrGraphicsFlush();
return;
}
}
}
void lovrGraphicsFlushShader(Shader* shader) {
for (int i = state.batchCount - 1; i >= 0; i--) {
if (state.batches[i].draw.shader == shader) {
lovrGraphicsFlush();
return;
}
}
}
void lovrGraphicsFlushMaterial(Material* material) {
for (int i = state.batchCount - 1; i >= 0; i--) {
if (state.batches[i].material == material) {
lovrGraphicsFlush();
return;
}
}
}
void lovrGraphicsFlushMesh(Mesh* mesh) {
for (int i = state.batchCount - 1; i >= 0; i--) {
if (state.batches[i].draw.mesh == mesh) {
lovrGraphicsFlush();
return;
}
}
}
void lovrGraphicsClear(Color* color, float* depth, int* stencil) {
#if !defined(LOVR_WEBGL)
if (color) gammaCorrect(color);
#endif
if (color || depth || stencil) lovrGraphicsFlush();
lovrGpuClear(state.canvas ? state.canvas : state.backbuffer, color, depth, stencil);
}
void lovrGraphicsDiscard(bool color, bool depth, bool stencil) {
if (color || depth || stencil) lovrGraphicsFlush();
lovrGpuDiscard(state.canvas ? state.canvas : state.backbuffer, color, depth, stencil);
}
void lovrGraphicsPoints(uint32_t count, float** vertices) {
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_POINTS,
.topology = DRAW_POINTS,
.vertexCount = count,
.vertices = vertices
});
}
void lovrGraphicsLine(uint32_t count, float** vertices) {
uint32_t indexCount = count + 1;
uint16_t* indices;
uint16_t baseVertex;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_LINES,
.topology = DRAW_LINE_STRIP,
.vertexCount = count,
.vertices = vertices,
.indexCount = indexCount,
.indices = &indices,
.baseVertex = &baseVertex
});
indices[0] = 0xffff;
for (uint32_t i = 1; i < indexCount; i++) {
indices[i] = baseVertex + i - 1;
}
}
void lovrGraphicsPlane(DrawStyle style, Material* material, mat4 transform, float u, float v, float w, float h) {
float* vertices = NULL;
uint16_t* indices = NULL;
uint16_t baseVertex;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_PLANE,
.params.plane.style = style,
.topology = style == STYLE_LINE ? DRAW_LINE_LOOP : DRAW_TRIANGLES,
.material = material,
.transform = transform,
.vertexCount = 4,
.indexCount = style == STYLE_LINE ? 5 : 6,
.vertices = &vertices,
.indices = &indices,
.baseVertex = &baseVertex
});
if (style == STYLE_LINE) {
static float vertexData[] = {
-.5f, .5f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
.5f, .5f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
.5f, -.5f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
-.5f, -.5f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f
};
memcpy(vertices, vertexData, sizeof(vertexData));
indices[0] = 0xffff;
indices[1] = 0 + baseVertex;
indices[2] = 1 + baseVertex;
indices[3] = 2 + baseVertex;
indices[4] = 3 + baseVertex;
} else {
float vertexData[] = {
-.5f, .5f, 0.f, 0.f, 0.f, -1.f, u, v + h,
-.5f, -.5f, 0.f, 0.f, 0.f, -1.f, u, v,
.5f, .5f, 0.f, 0.f, 0.f, -1.f, u + w, v + h,
.5f, -.5f, 0.f, 0.f, 0.f, -1.f, u + w, v
};
memcpy(vertices, vertexData, sizeof(vertexData));
static uint16_t indexData[] = { 0, 1, 2, 2, 1, 3 };
for (size_t i = 0; i < sizeof(indexData) / sizeof(indexData[0]); i++) {
indices[i] = indexData[i] + baseVertex;
}
}
}
void lovrGraphicsBox(DrawStyle style, Material* material, mat4 transform) {
float* vertices = NULL;
uint16_t* indices = NULL;
uint16_t baseVertex;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_BOX,
.params.box.style = style,
.topology = style == STYLE_LINE ? DRAW_LINES : DRAW_TRIANGLES,
.material = material,
.transform = transform,
.vertexCount = style == STYLE_LINE ? 8 : 24,
.indexCount = style == STYLE_LINE ? 24 : 36,
.vertices = &vertices,
.indices = &indices,
.baseVertex = &baseVertex,
.instanced = true
});
if (vertices) {
if (style == STYLE_LINE) {
static float vertexData[] = {
-.5f, .5f, -.5f, 0.f, 0.f, 0.f, 0.f, 0.f, // Front
.5f, .5f, -.5f, 0.f, 0.f, 0.f, 0.f, 0.f,
.5f, -.5f, -.5f, 0.f, 0.f, 0.f, 0.f, 0.f,
-.5f, -.5f, -.5f, 0.f, 0.f, 0.f, 0.f, 0.f,
-.5f, .5f, .5f, 0.f, 0.f, 0.f, 0.f, 0.f, // Back
.5f, .5f, .5f, 0.f, 0.f, 0.f, 0.f, 0.f,
.5f, -.5f, .5f, 0.f, 0.f, 0.f, 0.f, 0.f,
-.5f, -.5f, .5f, 0.f, 0.f, 0.f, 0.f, 0.f
};
memcpy(vertices, vertexData, sizeof(vertexData));
static uint16_t indexData[] = {
0, 1, 1, 2, 2, 3, 3, 0, // Front
4, 5, 5, 6, 6, 7, 7, 4, // Back
0, 4, 1, 5, 2, 6, 3, 7 // Connections
};
for (size_t i = 0; i < sizeof(indexData) / sizeof(indexData[0]); i++) {
indices[i] = indexData[i] + baseVertex;
}
} else {
static float vertexData[] = {
-.5f, -.5f, -.5f, 0.f, 0.f, -1.f, 0.f, 0.f, // Front
-.5f, .5f, -.5f, 0.f, 0.f, -1.f, 0.f, 1.f,
.5f, -.5f, -.5f, 0.f, 0.f, -1.f, 1.f, 0.f,
.5f, .5f, -.5f, 0.f, 0.f, -1.f, 1.f, 1.f,
.5f, .5f, -.5f, 1.f, 0.f, 0.f, 0.f, 1.f, // Right
.5f, .5f, .5f, 1.f, 0.f, 0.f, 1.f, 1.f,
.5f, -.5f, -.5f, 1.f, 0.f, 0.f, 0.f, 0.f,
.5f, -.5f, .5f, 1.f, 0.f, 0.f, 1.f, 0.f,
.5f, -.5f, .5f, 0.f, 0.f, 1.f, 0.f, 0.f, // Back
.5f, .5f, .5f, 0.f, 0.f, 1.f, 0.f, 1.f,
-.5f, -.5f, .5f, 0.f, 0.f, 1.f, 1.f, 0.f,
-.5f, .5f, .5f, 0.f, 0.f, 1.f, 1.f, 1.f,
-.5f, .5f, .5f, -1.f, 0.f, 0.f, 0.f, 1.f, // Left
-.5f, .5f, -.5f, -1.f, 0.f, 0.f, 1.f, 1.f,
-.5f, -.5f, .5f, -1.f, 0.f, 0.f, 0.f, 0.f,
-.5f, -.5f, -.5f, -1.f, 0.f, 0.f, 1.f, 0.f,
-.5f, -.5f, -.5f, 0.f, -1.f, 0.f, 0.f, 0.f, // Bottom
.5f, -.5f, -.5f, 0.f, -1.f, 0.f, 1.f, 0.f,
-.5f, -.5f, .5f, 0.f, -1.f, 0.f, 0.f, 1.f,
.5f, -.5f, .5f, 0.f, -1.f, 0.f, 1.f, 1.f,
-.5f, .5f, -.5f, 0.f, 1.f, 0.f, 0.f, 1.f, // Top
-.5f, .5f, .5f, 0.f, 1.f, 0.f, 0.f, 0.f,
.5f, .5f, -.5f, 0.f, 1.f, 0.f, 1.f, 1.f,
.5f, .5f, .5f, 0.f, 1.f, 0.f, 1.f, 0.f
};
memcpy(vertices, vertexData, sizeof(vertexData));
uint16_t indexData[] = {
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
12, 13, 14, 14, 13, 15,
16, 17, 18, 18, 17, 19,
20, 21, 22, 22, 21, 23
};
for (size_t i = 0; i < sizeof(indexData) / sizeof(indexData[0]); i++) {
indices[i] = indexData[i] + baseVertex;
}
}
}
}
void lovrGraphicsArc(DrawStyle style, ArcMode mode, Material* material, mat4 transform, float r1, float r2, int segments) {
bool hasCenterPoint = false;
if (fabsf(r1 - r2) >= 2.f * (float) M_PI) {
r1 = 0.f;
r2 = 2.f * (float) M_PI;
} else {
hasCenterPoint = mode == ARC_MODE_PIE;
}
uint32_t vertexCount = segments + 1 + hasCenterPoint;
float* vertices = NULL;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_ARC,
.params.arc.r1 = r1,
.params.arc.r2 = r2,
.params.arc.mode = mode,
.params.arc.style = style,
.params.arc.segments = segments,
.topology = style == STYLE_LINE ? (mode == ARC_MODE_OPEN ? DRAW_LINE_STRIP : DRAW_LINE_LOOP) : DRAW_TRIANGLE_FAN,
.material = material,
.transform = transform,
.vertexCount = vertexCount,
.vertices = &vertices,
.instanced = true
});
if (vertices) {
if (hasCenterPoint) {
memcpy(vertices, ((float[]) { 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, .5f, .5f }), 8 * sizeof(float));
vertices += 8;
}
float theta = r1;
float angleShift = (r2 - r1) / (float) segments;
for (int i = 0; i <= segments; i++) {
float x = cosf(theta);
float y = sinf(theta);
memcpy(vertices, ((float[]) { x, y, 0.f, 0.f, 0.f, 1.f, (1.f + x) * .5f, (1.f - y) * .5f }), 8 * sizeof(float));
vertices += 8;
theta += angleShift;
}
}
}
void lovrGraphicsCircle(DrawStyle style, Material* material, mat4 transform, int segments) {
lovrGraphicsArc(style, ARC_MODE_OPEN, material, transform, 0, 2.f * (float) M_PI, segments);
}
void lovrGraphicsCylinder(Material* material, mat4 transform, float r1, float r2, bool capped, int segments) {
float length = vec3_length((float[4]) { transform[8], transform[9], transform[10] });
r1 /= length;
r2 /= length;
uint32_t vertexCount = ((capped && r1) * (segments + 2) + (capped && r2) * (segments + 2) + 2 * (segments + 1));
uint32_t indexCount = 3 * segments * ((capped && r1) + (capped && r2) + 2);
float* vertices = NULL;
uint16_t* indices = NULL;
uint16_t baseVertex;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_CYLINDER,
.params.cylinder.r1 = r1,
.params.cylinder.r2 = r2,
.params.cylinder.capped = capped,
.params.cylinder.segments = segments,
.topology = DRAW_TRIANGLES,
.material = material,
.transform = transform,
.vertexCount = vertexCount,
.indexCount = indexCount,
.vertices = &vertices,
.indices = &indices,
.baseVertex = &baseVertex,
.instanced = true
});
if (vertices) {
float* v = vertices;
// Ring
for (int i = 0; i <= segments; i++) {
float t = (float) i / segments;
float theta = t * (2 * M_PI);
float X = cosf(theta);
float Y = sinf(theta);
memcpy(vertices, (float[16]) {
r1 * X, r1 * Y, -.5f, X, Y, 0.f, 1.f - t, 1.f,
r2 * X, r2 * Y, .5f, X, Y, 0.f, 1.f - t, 0.f
}, 16 * sizeof(float));
vertices += 16;
}
// Top
int top = (segments + 1) * 2 + baseVertex;
if (capped && r1 != 0) {
memcpy(vertices, (float[8]) { 0.f, 0.f, -.5f, 0.f, 0.f, -1.f, .5f, .5f }, 8 * sizeof(float));
vertices += 8;
for (int i = 0; i <= segments; i++) {
int j = i * 2 * 8;
float x = v[j + 0];
float y = v[j + 1];
float z = v[j + 2];
float u = 1.f - (x / r1 * .5 + .5);
float v = y / r1 * .5 + .5;
memcpy(vertices, (float[8]) { x, y, z, 0.f, 0.f, -1.f, u, v }, 8 * sizeof(float));
vertices += 8;
}
}
// Bottom
int bot = (segments + 1) * 2 + (1 + segments + 1) * (capped && r1 != 0) + baseVertex;
if (capped && r2 != 0) {
memcpy(vertices, (float[8]) { 0.f, 0.f, .5f, 0.f, 0.f, 1.f, .5f, .5f }, 8 * sizeof(float));
vertices += 8;
for (int i = 0; i <= segments; i++) {
int j = i * 2 * 8 + 8;
float x = v[j + 0];
float y = v[j + 1];
float z = v[j + 2];
float u = x / r1 * .5 + .5;
float v = y / r1 * .5 + .5;
memcpy(vertices, (float[8]) { x, y, z, 0.f, 0.f, 1.f, u, v }, 8 * sizeof(float));
vertices += 8;
}
}
// Indices
for (int i = 0; i < segments; i++) {
int j = 2 * i + baseVertex;
memcpy(indices, (uint16_t[6]) { j, j + 2, j + 1, j + 1, j + 2, j + 3 }, 6 * sizeof(uint16_t));
indices += 6;
if (capped && r1 != 0.f) {
memcpy(indices, (uint16_t[3]) { top, top + i + 2, top + i + 1 }, 3 * sizeof(uint16_t));
indices += 3;
}
if (capped && r2 != 0.f) {
memcpy(indices, (uint16_t[3]) { bot, bot + i + 1, bot + i + 2 }, 3 * sizeof(uint16_t));
indices += 3;
}
}
}
}
void lovrGraphicsSphere(Material* material, mat4 transform, int segments) {
float* vertices = NULL;
uint16_t* indices = NULL;
uint16_t baseVertex;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_SPHERE,
.params.sphere.segments = segments,
.topology = DRAW_TRIANGLES,
.material = material,
.transform = transform,
.vertexCount = (segments + 1) * (segments + 1),
.indexCount = segments * segments * 6,
.vertices = &vertices,
.indices = &indices,
.baseVertex = &baseVertex,
.instanced = true
});
if (vertices) {
for (int i = 0; i <= segments; i++) {
float v = i / (float) segments;
float sinV = sinf(v * (float) M_PI);
float cosV = cosf(v * (float) M_PI);
for (int k = 0; k <= segments; k++) {
float u = k / (float) segments;
float x = sinf(u * 2.f * (float) M_PI) * sinV;
float y = cosV;
float z = -cosf(u * 2.f * (float) M_PI) * sinV;
memcpy(vertices, ((float[8]) { x, y, z, x, y, z, u, 1.f - v }), 8 * sizeof(float));
vertices += 8;
}
}
for (int i = 0; i < segments; i++) {
uint16_t offset0 = i * (segments + 1) + baseVertex;
uint16_t offset1 = (i + 1) * (segments + 1) + baseVertex;
for (int j = 0; j < segments; j++) {
uint16_t i0 = offset0 + j;
uint16_t i1 = offset1 + j;
memcpy(indices, ((uint16_t[]) { i0, i0 + 1, i1, i1, i0 + 1, i1 + 1 }), 6 * sizeof(uint16_t));
indices += 6;
}
}
}
}
void lovrGraphicsSkybox(Texture* texture) {
TextureType type = lovrTextureGetType(texture);
lovrAssert(type == TEXTURE_CUBE || type == TEXTURE_2D, "Only 2D and cube textures can be used as skyboxes");
Pipeline pipeline = state.pipeline;
pipeline.winding = WINDING_COUNTERCLOCKWISE;
float* vertices = NULL;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_SKYBOX,
.topology = DRAW_TRIANGLE_STRIP,
.shader = type == TEXTURE_CUBE ? SHADER_CUBE : SHADER_PANO,
.pipeline = &pipeline,
.texture = texture,
.vertexCount = 4,
.vertices = &vertices,
.instanced = true
});
if (vertices) {
static float vertexData[] = {
-1.f, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f,
-1.f, -1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f,
1.f, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f,
1.f, -1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f
};
memcpy(vertices, vertexData, sizeof(vertexData));
}
}
void lovrGraphicsPrint(const char* str, size_t length, mat4 transform, float wrap, HorizontalAlign halign, VerticalAlign valign) {
float width;
float lastLineWidth;
float height;
uint32_t lineCount;
uint32_t glyphCount;
Font* font = lovrGraphicsGetFont();
lovrFontMeasure(font, str, length, wrap, &width, &lastLineWidth, &height, &lineCount, &glyphCount);
if (glyphCount == 0) {
return;
}
float scale = 1.f / lovrFontGetPixelDensity(font);
mat4_scale(transform, scale, scale, scale);
mat4_translate(transform, 0.f, height * (valign / 2.f), 0.f);
Pipeline pipeline = state.pipeline;
pipeline.blendMode = pipeline.blendMode == BLEND_NONE ? BLEND_ALPHA : pipeline.blendMode;
float* vertices;
uint16_t* indices;
uint16_t baseVertex;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_TEXT,
.params.text.spread = lovrFontGetSpread(font),
.topology = DRAW_TRIANGLES,
.shader = SHADER_FONT,
.pipeline = &pipeline,
.transform = transform,
.texture = lovrFontGetTexture(font),
.vertexCount = glyphCount * 4,
.indexCount = glyphCount * 6,
.vertices = &vertices,
.indices = &indices,
.baseVertex = &baseVertex
});
lovrFontRender(font, str, length, wrap, halign, vertices, indices, baseVertex);
}
void lovrGraphicsFill(Texture* texture, float u, float v, float w, float h) {
Pipeline pipeline = state.pipeline;
pipeline.depthTest = COMPARE_NONE;
pipeline.depthWrite = false;
float* vertices = NULL;
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_FILL,
.params.fill = { .u = u, .v = v, .w = w, .h = h },
.topology = DRAW_TRIANGLE_STRIP,
.shader = SHADER_FILL,
.texture = texture,
.pipeline = &pipeline,
.vertexCount = 4,
.vertices = &vertices
});
if (vertices) {
memcpy(vertices, (float[32]) {
-1.f, 1.f, 0.f, 0.f, 0.f, 0.f, u, v + h,
-1.f, -1.f, 0.f, 0.f, 0.f, 0.f, u, v,
1.f, 1.f, 0.f, 0.f, 0.f, 0.f, u + w, v + h,
1.f, -1.f, 0.f, 0.f, 0.f, 0.f, u + w, v
}, 32 * sizeof(float));
}
}
void lovrGraphicsDrawMesh(Mesh* mesh, mat4 transform, uint32_t instances, float* pose) {
uint32_t vertexCount = lovrMeshGetVertexCount(mesh);
uint32_t indexCount = lovrMeshGetIndexCount(mesh);
uint32_t defaultCount = indexCount > 0 ? indexCount : vertexCount;
uint32_t rangeStart, rangeCount;
lovrMeshGetDrawRange(mesh, &rangeStart, &rangeCount);
rangeCount = rangeCount > 0 ? rangeCount : defaultCount;
DrawMode mode = lovrMeshGetDrawMode(mesh);
Material* material = lovrMeshGetMaterial(mesh);
lovrGraphicsBatch(&(BatchRequest) {
.type = BATCH_MESH,
.params.mesh.rangeStart = rangeStart,
.params.mesh.rangeCount = rangeCount,
.params.mesh.instances = instances,
.params.mesh.pose = pose,
.mesh = mesh,
.topology = mode,
.transform = transform,
.material = material,
.instanced = instances <= 1
});
}