orca/src/gl_canvas.c

504 lines
16 KiB
C

/************************************************************//**
*
* @file: gl_canvas.c
* @author: Martin Fouilleul
* @date: 29/01/2023
* @revision:
*
*****************************************************************/
#include"graphics_internal.h"
#include"macro_helpers.h"
#include"glsl_shaders.h"
#include"gl_api.h"
#define LOG_SUBSYSTEM "Graphics"
typedef struct mg_gl_canvas_backend
{
mg_canvas_backend interface;
mg_surface surface;
GLuint vao;
GLuint dummyVertexBuffer;
GLuint vertexBuffer;
GLuint shapeBuffer;
GLuint indexBuffer;
GLuint tileCounterBuffer;
GLuint tileArrayBuffer;
GLuint clearCounterProgram;
GLuint tileProgram;
GLuint sortProgram;
GLuint drawProgram;
GLuint blitProgram;
GLuint outTexture;
char* indexMapping;
char* vertexMapping;
char* shapeMapping;
} mg_gl_canvas_backend;
typedef struct mg_gl_image
{
mg_image_data interface;
GLuint textureID;
} mg_gl_image;
//NOTE: debugger
typedef struct debug_vertex
{
vec4 cubic;
vec2 pos;
int shapeIndex;
u8 pad[4];
} debug_vertex;
typedef struct debug_shape
{
vec4 color;
vec4 clip;
vec2 uv;
u8 pad[8];
} debug_shape;
#define LayoutNext(prevName, prevType, nextType) \
AlignUpOnPow2(_cat3_(LAYOUT_, prevName, _OFFSET)+_cat3_(LAYOUT_, prevType, _SIZE), _cat3_(LAYOUT_, nextType, _ALIGN))
enum {
LAYOUT_VEC2_SIZE = 8,
LAYOUT_VEC2_ALIGN = 8,
LAYOUT_VEC4_SIZE = 16,
LAYOUT_VEC4_ALIGN = 16,
LAYOUT_INT_SIZE = 4,
LAYOUT_INT_ALIGN = 4,
LAYOUT_MAT2x3_SIZE = sizeof(float)*6,
LAYOUT_MAT2x3_ALIGN = 4,
LAYOUT_CUBIC_OFFSET = 0,
LAYOUT_POS_OFFSET = LayoutNext(CUBIC, VEC4, VEC2),
LAYOUT_ZINDEX_OFFSET = LayoutNext(POS, VEC2, INT),
LAYOUT_VERTEX_ALIGN = 16,
LAYOUT_VERTEX_SIZE = LayoutNext(ZINDEX, INT, VERTEX),
LAYOUT_COLOR_OFFSET = 0,
LAYOUT_CLIP_OFFSET = LayoutNext(COLOR, VEC4, VEC4),
LAYOUT_UV_TRANSFORM_OFFSET = LayoutNext(CLIP, VEC4, MAT2x3),
LAYOUT_SHAPE_ALIGN = 16,
LAYOUT_SHAPE_SIZE = LayoutNext(UV_TRANSFORM, MAT2x3, SHAPE),
MG_GL_CANVAS_MAX_BUFFER_LENGTH = 1<<20,
MG_GL_CANVAS_MAX_SHAPE_BUFFER_SIZE = LAYOUT_SHAPE_SIZE * MG_GL_CANVAS_MAX_BUFFER_LENGTH,
MG_GL_CANVAS_MAX_VERTEX_BUFFER_SIZE = LAYOUT_VERTEX_SIZE * MG_GL_CANVAS_MAX_BUFFER_LENGTH,
MG_GL_CANVAS_MAX_INDEX_BUFFER_SIZE = LAYOUT_INT_SIZE * MG_GL_CANVAS_MAX_BUFFER_LENGTH,
//TODO: actually size this dynamically
MG_GL_CANVAS_MAX_TILE_COUNT = 65536, //NOTE: this allows for 256*256 tiles (e.g. 4096*4096 pixels)
MG_GL_CANVAS_TILE_COUNTER_BUFFER_SIZE = LAYOUT_INT_SIZE * MG_GL_CANVAS_MAX_TILE_COUNT,
MG_GL_CANVAS_TILE_ARRAY_LENGTH = 1<<10, // max overlapping triangles per tiles
MG_GL_CANVAS_TILE_ARRAY_BUFFER_SIZE = LAYOUT_INT_SIZE * MG_GL_CANVAS_MAX_TILE_COUNT * MG_GL_CANVAS_TILE_ARRAY_LENGTH,
};
void mg_gl_canvas_update_vertex_layout(mg_gl_canvas_backend* backend)
{
backend->interface.vertexLayout = (mg_vertex_layout){
.maxVertexCount = MG_GL_CANVAS_MAX_BUFFER_LENGTH,
.maxIndexCount = MG_GL_CANVAS_MAX_BUFFER_LENGTH,
.posBuffer = backend->vertexMapping + LAYOUT_POS_OFFSET,
.posStride = LAYOUT_VERTEX_SIZE,
.cubicBuffer = backend->vertexMapping + LAYOUT_CUBIC_OFFSET,
.cubicStride = LAYOUT_VERTEX_SIZE,
.shapeIndexBuffer = backend->vertexMapping + LAYOUT_ZINDEX_OFFSET,
.shapeIndexStride = LAYOUT_VERTEX_SIZE,
.colorBuffer = backend->shapeMapping + LAYOUT_COLOR_OFFSET,
.colorStride = LAYOUT_SHAPE_SIZE,
.clipBuffer = backend->shapeMapping + LAYOUT_CLIP_OFFSET,
.clipStride = LAYOUT_SHAPE_SIZE,
.uvTransformBuffer = backend->shapeMapping + LAYOUT_UV_TRANSFORM_OFFSET,
.uvTransformStride = LAYOUT_SHAPE_SIZE,
.indexBuffer = backend->indexMapping,
.indexStride = LAYOUT_INT_SIZE};
}
void mg_gl_send_buffers(mg_gl_canvas_backend* backend, int shapeCount, int vertexCount, int indexCount)
{
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->vertexBuffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, LAYOUT_VERTEX_SIZE*vertexCount, backend->vertexMapping, GL_STREAM_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->shapeBuffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, LAYOUT_SHAPE_SIZE*shapeCount, backend->shapeMapping, GL_STREAM_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->indexBuffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, LAYOUT_INT_SIZE*indexCount, backend->indexMapping, GL_STREAM_DRAW);
}
void mg_gl_canvas_begin(mg_canvas_backend* interface)
{
mg_gl_canvas_backend* backend = (mg_gl_canvas_backend*)interface;
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
void mg_gl_canvas_end(mg_canvas_backend* interface)
{
//NOTE: nothing to do here...
}
void mg_gl_canvas_clear(mg_canvas_backend* interface, mg_color clearColor)
{
mg_gl_canvas_backend* backend = (mg_gl_canvas_backend*)interface;
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
glClear(GL_COLOR_BUFFER_BIT);
}
void mg_gl_canvas_draw_batch(mg_canvas_backend* interface, mg_image_data* imageInterface, u32 shapeCount, u32 vertexCount, u32 indexCount)
{
mg_gl_canvas_backend* backend = (mg_gl_canvas_backend*)interface;
/*NOTE: if we want debug_vertex while debugging, the following ensures the struct def doesn't get stripped away
debug_vertex vertex;
debug_shape shape;
printf("foo %p, bar %p\n", &vertex, &shape);
//*/
mg_gl_send_buffers(backend, shapeCount, vertexCount, indexCount);
mp_rect frame = mg_surface_get_frame(backend->surface);
vec2 contentsScaling = mg_surface_contents_scaling(backend->surface);
const int tileSize = 16;
const int tileCountX = (frame.w*contentsScaling.x + tileSize - 1)/tileSize;
const int tileCountY = (frame.h*contentsScaling.y + tileSize - 1)/tileSize;
const int tileArrayLength = MG_GL_CANVAS_TILE_ARRAY_LENGTH;
//TODO: ensure there's enough space in tile buffer
//NOTE: first clear counters
glUseProgram(backend->clearCounterProgram);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, backend->tileCounterBuffer);
glDispatchCompute(tileCountX*tileCountY, 1, 1);
//NOTE: we first distribute triangles into tiles:
glUseProgram(backend->tileProgram);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, backend->vertexBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, backend->shapeBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, backend->indexBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, backend->tileCounterBuffer);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, backend->tileArrayBuffer);
glUniform1ui(0, indexCount);
glUniform2ui(1, tileCountX, tileCountY);
glUniform1ui(2, tileSize);
glUniform1ui(3, tileArrayLength);
glUniform2f(4, contentsScaling.x, contentsScaling.y);
u32 threadCount = indexCount/3;
glDispatchCompute((threadCount + 255)/256, 1, 1);
//NOTE: next we sort triangles in each tile
glUseProgram(backend->sortProgram);
glUniform1ui(0, indexCount);
glUniform2ui(1, tileCountX, tileCountY);
glUniform1ui(2, tileSize);
glUniform1ui(3, tileArrayLength);
glDispatchCompute(tileCountX * tileCountY, 1, 1);
//NOTE: then we fire the drawing shader that will select only triangles in its tile
glUseProgram(backend->drawProgram);
glBindImageTexture(0, backend->outTexture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
glUniform1ui(0, indexCount);
glUniform2ui(1, tileCountX, tileCountY);
glUniform1ui(2, tileSize);
glUniform1ui(3, tileArrayLength);
glUniform2f(4, contentsScaling.x, contentsScaling.y);
if(imageInterface)
{
//TODO: make sure this image belongs to that context
mg_gl_image* image = (mg_gl_image*)imageInterface;
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, image->textureID);
glUniform1ui(5, 1);
}
else
{
glUniform1ui(5, 0);
}
glDispatchCompute(tileCountX, tileCountY, 1);
//NOTE: now blit out texture to surface
glUseProgram(backend->blitProgram);
glBindBuffer(GL_ARRAY_BUFFER, backend->dummyVertexBuffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, backend->outTexture);
glUniform1i(0, 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
mg_gl_canvas_update_vertex_layout(backend);
}
void mg_gl_canvas_destroy(mg_canvas_backend* interface)
{
mg_gl_canvas_backend* backend = (mg_gl_canvas_backend*)interface;
glDeleteTextures(1, &backend->outTexture);
glDeleteBuffers(1, &backend->dummyVertexBuffer);
glDeleteBuffers(1, &backend->vertexBuffer);
glDeleteBuffers(1, &backend->shapeBuffer);
glDeleteBuffers(1, &backend->indexBuffer);
glDeleteBuffers(1, &backend->tileCounterBuffer);
glDeleteBuffers(1, &backend->tileArrayBuffer);
glDeleteVertexArrays(1, &backend->vao);
free(backend->shapeMapping);
free(backend->vertexMapping);
free(backend->indexMapping);
free(backend);
}
mg_image_data* mg_gl_canvas_image_create(mg_canvas_backend* interface, vec2 size)
{
mg_gl_image* image = 0;
image = malloc_type(mg_gl_image);
if(image)
{
glGenTextures(1, &image->textureID);
glBindTexture(GL_TEXTURE_2D, image->textureID);
// glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, size.x, size.y);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
image->interface.size = size;
}
return((mg_image_data*)image);
}
void mg_gl_canvas_image_destroy(mg_canvas_backend* interface, mg_image_data* imageInterface)
{
//TODO: check that this image belongs to this context
mg_gl_image* image = (mg_gl_image*)imageInterface;
glDeleteTextures(1, &image->textureID);
free(image);
}
void mg_gl_canvas_image_upload_region(mg_canvas_backend* interface,
mg_image_data* imageInterface,
mp_rect region,
u8* pixels)
{
//TODO: check that this image belongs to this context
mg_gl_image* image = (mg_gl_image*)imageInterface;
glBindTexture(GL_TEXTURE_2D, image->textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, region.w, region.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
static int mg_gl_compile_shader(const char* name, GLuint shader, const char* source)
{
int res = 0;
const char* sources[3] = {"#version 430", glsl_common, source};
glShaderSource(shader, 3, sources, 0);
glCompileShader(shader);
int status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if(!status)
{
char buffer[256];
int size = 0;
glGetShaderInfoLog(shader, 256, &size, buffer);
printf("Shader compile error (%s): %.*s\n", name, size, buffer);
res = -1;
}
return(res);
}
static int mg_gl_canvas_compile_compute_program_named(const char* name, const char* source, GLuint* outProgram)
{
int res = 0;
*outProgram = 0;
GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
GLuint program = glCreateProgram();
res |= mg_gl_compile_shader(name, shader, source);
if(!res)
{
glAttachShader(program, shader);
glLinkProgram(program);
int status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if(!status)
{
char buffer[256];
int size = 0;
glGetProgramInfoLog(program, 256, &size, buffer);
LOG_ERROR("Shader link error (%s): %.*s\n", name, size, buffer);
res = -1;
}
else
{
*outProgram = program;
}
}
return(res);
}
int mg_gl_canvas_compile_render_program_named(const char* progName,
const char* vertexName,
const char* fragmentName,
const char* vertexSrc,
const char* fragmentSrc,
GLuint* outProgram)
{
int res = 0;
*outProgram = 0;
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
GLuint program = glCreateProgram();
res |= mg_gl_compile_shader(vertexName, vertexShader, vertexSrc);
res |= mg_gl_compile_shader(fragmentName, fragmentShader, fragmentSrc);
if(!res)
{
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
int status = 0;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if(!status)
{
char buffer[256];
int size = 0;
glGetProgramInfoLog(program, 256, &size, buffer);
LOG_ERROR("Shader link error (%s): %.*s\n", progName, size, buffer);
res = -1;
}
else
{
*outProgram = program;
}
}
return(res);
}
#define mg_gl_canvas_compile_compute_program(src, out) \
mg_gl_canvas_compile_compute_program_named(#src, src, out)
#define mg_gl_canvas_compile_render_program(progName, shaderSrc, vertexSrc, out) \
mg_gl_canvas_compile_render_program_named(progName, #shaderSrc, #vertexSrc, shaderSrc, vertexSrc, out)
mg_canvas_backend* mg_gl_canvas_create(mg_surface surface)
{
mg_gl_canvas_backend* backend = 0;
mg_surface_data* surfaceData = mg_surface_data_from_handle(surface);
int err = 0;
if(surfaceData && surfaceData->backend == MG_BACKEND_GL)
{
backend = malloc_type(mg_gl_canvas_backend);
memset(backend, 0, sizeof(mg_gl_canvas_backend));
backend->surface = surface;
//NOTE(martin): setup interface functions
backend->interface.destroy = mg_gl_canvas_destroy;
backend->interface.begin = mg_gl_canvas_begin;
backend->interface.end = mg_gl_canvas_end;
backend->interface.clear = mg_gl_canvas_clear;
backend->interface.drawBatch = mg_gl_canvas_draw_batch;
backend->interface.imageCreate = mg_gl_canvas_image_create;
backend->interface.imageDestroy = mg_gl_canvas_image_destroy;
backend->interface.imageUploadRegion = mg_gl_canvas_image_upload_region;
mg_surface_prepare(surface);
glGenVertexArrays(1, &backend->vao);
glBindVertexArray(backend->vao);
glGenBuffers(1, &backend->dummyVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, backend->dummyVertexBuffer);
glGenBuffers(1, &backend->vertexBuffer);
glGenBuffers(1, &backend->shapeBuffer);
glGenBuffers(1, &backend->indexBuffer);
glGenBuffers(1, &backend->tileCounterBuffer);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->tileCounterBuffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, MG_GL_CANVAS_TILE_COUNTER_BUFFER_SIZE, 0, GL_DYNAMIC_COPY);
glGenBuffers(1, &backend->tileArrayBuffer);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, backend->tileArrayBuffer);
glBufferData(GL_SHADER_STORAGE_BUFFER, MG_GL_CANVAS_TILE_ARRAY_BUFFER_SIZE, 0, GL_DYNAMIC_COPY);
mp_rect frame = mg_surface_get_frame(backend->surface);
vec2 contentsScaling = mg_surface_contents_scaling(backend->surface);
glGenTextures(1, &backend->outTexture);
glBindTexture(GL_TEXTURE_2D, backend->outTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, frame.w*contentsScaling.x, frame.h*contentsScaling.y);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//NOTE: create programs
err |= mg_gl_canvas_compile_compute_program(glsl_clear_counters, &backend->clearCounterProgram);
err |= mg_gl_canvas_compile_compute_program(glsl_tile, &backend->tileProgram);
err |= mg_gl_canvas_compile_compute_program(glsl_sort, &backend->sortProgram);
err |= mg_gl_canvas_compile_compute_program(glsl_draw, &backend->drawProgram);
err |= mg_gl_canvas_compile_render_program("blit", glsl_blit_vertex, glsl_blit_fragment, &backend->blitProgram);
if(glGetError() != GL_NO_ERROR)
{
err |= -1;
}
backend->shapeMapping = malloc_array(char, MG_GL_CANVAS_MAX_SHAPE_BUFFER_SIZE);
backend->vertexMapping = malloc_array(char, MG_GL_CANVAS_MAX_VERTEX_BUFFER_SIZE);
backend->indexMapping = malloc_array(char, MG_GL_CANVAS_MAX_INDEX_BUFFER_SIZE);
if( !backend->shapeMapping
|| !backend->shapeMapping
|| !backend->shapeMapping)
{
err |= -1;
}
if(err)
{
mg_gl_canvas_destroy((mg_canvas_backend*)backend);
backend = 0;
}
else
{
mg_gl_canvas_update_vertex_layout(backend);
}
}
return((mg_canvas_backend*)backend);
}
#undef LOG_SUBSYSTEM