From 875a7f82374b2dff4ff0df5b33ab97ba07d7cfbe Mon Sep 17 00:00:00 2001 From: bjorn Date: Tue, 20 Feb 2024 15:07:30 -0800 Subject: [PATCH] Shader rework; - Undeprecate ShaderType (it's good actually, kinda like a pipeline bind point and more specific than having lovr trying to make sense of a random set of stages). - Use a default uniform block instead of push constants. The Constants macro now maps to a uniform block declaration. - It is no longer possible to create a shader by giving a single DefaultShader string (you can still give a per-stage DefaultShader though). - lovr.graphics.compileShader now takes all stages instead of a single stage. In general we need all stages when compiling GLSL because default uniforms require linking across stages. --- etc/shaders/lovr.glsl | 2 +- src/api/api.h | 1 + src/api/l_graphics.c | 118 ++++---- src/api/l_graphics_shader.c | 15 +- src/modules/graphics/graphics.c | 522 +++++++++++++++++++------------- src/modules/graphics/graphics.h | 17 +- 6 files changed, 395 insertions(+), 280 deletions(-) diff --git a/etc/shaders/lovr.glsl b/etc/shaders/lovr.glsl index d89dfb37..24330a37 100644 --- a/etc/shaders/lovr.glsl +++ b/etc/shaders/lovr.glsl @@ -140,7 +140,7 @@ layout(location = 14) in vec4 Tangent; // Helpers -#define Constants layout(push_constant) uniform PushConstants +#define Constants uniform DefaultUniformBlock #ifdef GL_COMPUTE_SHADER #define var(x) layout(set = 0, binding = x) #else diff --git a/src/api/api.h b/src/api/api.h index 327d7aba..527f4a6d 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -51,6 +51,7 @@ extern StringEntry lovrPassType[]; extern StringEntry lovrPermission[]; extern StringEntry lovrSampleFormat[]; extern StringEntry lovrShaderStage[]; +extern StringEntry lovrShaderType[]; extern StringEntry lovrShapeType[]; extern StringEntry lovrSmoothMode[]; extern StringEntry lovrStackType[]; diff --git a/src/api/l_graphics.c b/src/api/l_graphics.c index 21446d1e..87484ec7 100644 --- a/src/api/l_graphics.c +++ b/src/api/l_graphics.c @@ -146,6 +146,12 @@ StringEntry lovrShaderStage[] = { { 0 } }; +StringEntry lovrShaderType[] = { + [SHADER_GRAPHICS] = ENTRY("graphics"), + [SHADER_COMPUTE] = ENTRY("compute"), + { 0 } +}; + StringEntry lovrStackType[] = { [STACK_TRANSFORM] = ENTRY("transform"), [STACK_STATE] = ENTRY("state"), @@ -946,94 +952,88 @@ static int l_lovrGraphicsNewSampler(lua_State* L) { return 1; } -static ShaderSource luax_checkshadersource(lua_State* L, int index, ShaderStage stage, bool* allocated) { - ShaderSource source; +static ShaderSource luax_checkshadersource(lua_State* L, int index, ShaderStage stage, bool* shouldFree) { + *shouldFree = false; + if (lua_isstring(L, index)) { size_t length; const char* string = lua_tolstring(L, index, &length); + if (memchr(string, '\n', MIN(256, length))) { - source.code = string; - source.size = length; - *allocated = false; + return (ShaderSource) { stage, string, length }; } else { for (int i = 0; lovrDefaultShader[i].length; i++) { if (lovrDefaultShader[i].length == length && !memcmp(lovrDefaultShader[i].string, string, length)) { - *allocated = false; return lovrGraphicsGetDefaultShaderSource(i, stage); } } - source.code = luax_readfile(string, &source.size); + size_t size; + void* code = luax_readfile(string, &size); - if (source.code) { - *allocated = true; + if (code) { + *shouldFree = true; + return (ShaderSource) { stage, code, size }; } else { luaL_argerror(L, index, "single-line string was not filename or DefaultShader"); } } } else if (lua_isuserdata(L, index)) { Blob* blob = luax_checktype(L, index, Blob); - source.code = blob->data; - source.size = blob->size; - *allocated = false; + return (ShaderSource) { stage, blob->data, blob->size }; } else { - *allocated = false; - return lovrGraphicsGetDefaultShaderSource(SHADER_UNLIT, stage); + luax_typeerror(L, index, "string, Blob, or DefaultShader"); } - - ShaderSource bytecode = lovrGraphicsCompileShader(stage, &source, luax_readfile); - - if (bytecode.code != source.code) { - if (*allocated) free((void*) source.code); - *allocated = true; - return bytecode; - } - - return source; + return (ShaderSource) { 0 }; } static int l_lovrGraphicsCompileShader(lua_State* L) { - ShaderStage stage = luax_checkenum(L, 1, ShaderStage, NULL); - bool allocated; - ShaderSource spirv = luax_checkshadersource(L, 2, stage, &allocated); - Blob* blob = lovrBlobCreate((void*) spirv.code, spirv.size, "Compiled Shader Code"); - luax_pushtype(L, Blob, blob); - lovrRelease(blob, lovrBlobDestroy); - return 1; + ShaderSource inputs[2], outputs[2]; + bool shouldFree[2]; + uint32_t count; + + if (lua_gettop(L) == 1) { + inputs[0] = luax_checkshadersource(L, 1, STAGE_COMPUTE, &shouldFree[0]); + count = 1; + } else { + inputs[0] = luax_checkshadersource(L, 1, STAGE_VERTEX, &shouldFree[0]); + inputs[1] = luax_checkshadersource(L, 2, STAGE_FRAGMENT, &shouldFree[1]); + count = 2; + } + + lovrGraphicsCompileShader(inputs, outputs, count, luax_readfile); + + for (uint32_t i = 0; i < count; i++) { + if (shouldFree[i] && outputs[i].code != inputs[i].code) free((void*) inputs[i].code); + Blob* blob = lovrBlobCreate((void*) outputs[i].code, outputs[i].size, "Shader code"); + luax_pushtype(L, Blob, blob); + lovrRelease(blob, lovrBlobDestroy); + } + + return count; } static int l_lovrGraphicsNewShader(lua_State* L) { - ShaderInfo info = { 0 }; - bool allocated[2]; + ShaderSource source[2], compiled[2]; + ShaderInfo info = { .stages = compiled }; + bool shouldFree[2] = { 0 }; int index; - // If there's only one source given, it could be a DefaultShader or a compute shader - if (lua_gettop(L) == 1 || (lua_istable(L, 2) && luax_len(L, 2) == 0)) { - if (lua_type(L, 1) == LUA_TSTRING) { - size_t length; - const char* string = lua_tolstring(L, 1, &length); - for (int i = 0; lovrDefaultShader[i].length; i++) { - if (lovrDefaultShader[i].length == length && !memcmp(lovrDefaultShader[i].string, string, length)) { - info.source[STAGE_VERTEX] = lovrGraphicsGetDefaultShaderSource(i, STAGE_VERTEX); - info.source[STAGE_FRAGMENT] = lovrGraphicsGetDefaultShaderSource(i, STAGE_FRAGMENT); - allocated[0] = false; - allocated[1] = false; - break; - } - } - } - - if (!info.source[0].code) { - info.source[STAGE_COMPUTE] = luax_checkshadersource(L, 1, STAGE_COMPUTE, &allocated[0]); - } - + if (lua_gettop(L) == 1 || lua_istable(L, 2)) { + info.type = SHADER_COMPUTE; + source[0] = luax_checkshadersource(L, 1, STAGE_COMPUTE, &shouldFree[0]); + info.stageCount = 1; index = 2; } else { - info.source[STAGE_VERTEX] = luax_checkshadersource(L, 1, STAGE_VERTEX, &allocated[0]); - info.source[STAGE_FRAGMENT] = luax_checkshadersource(L, 2, STAGE_FRAGMENT, &allocated[1]); + info.type = SHADER_GRAPHICS; + source[0] = luax_checkshadersource(L, 1, STAGE_VERTEX, &shouldFree[0]); + source[1] = luax_checkshadersource(L, 2, STAGE_FRAGMENT, &shouldFree[1]); + info.stageCount = source[1].code ? 2 : 1; index = 3; } + lovrGraphicsCompileShader(source, compiled, info.stageCount, luax_readfile); + arr_t(ShaderFlag) flags; arr_init(&flags, realloc); @@ -1056,6 +1056,10 @@ static int l_lovrGraphicsNewShader(lua_State* L) { } lua_pop(L, 1); + lua_getfield(L, index, "type"); + info.type = lua_isnil(L, -1) ? info.type : luax_checkenum(L, -1, ShaderType, NULL); + lua_pop(L, 1); + lua_getfield(L, index, "label"); info.label = lua_tostring(L, -1); lua_pop(L, 1); @@ -1069,8 +1073,8 @@ static int l_lovrGraphicsNewShader(lua_State* L) { Shader* shader = lovrShaderCreate(&info); luax_pushtype(L, Shader, shader); lovrRelease(shader, lovrShaderDestroy); - if (allocated[0]) free((void*) info.source[0].code); - if (allocated[1]) free((void*) info.source[1].code); + if (shouldFree[0] && source[0].code != compiled[0].code) free((void*) source[0].code); + if (shouldFree[1] && source[1].code != compiled[1].code) free((void*) source[1].code); arr_free(&flags); return 1; } diff --git a/src/api/l_graphics_shader.c b/src/api/l_graphics_shader.c index 658c1db0..0709569c 100644 --- a/src/api/l_graphics_shader.c +++ b/src/api/l_graphics_shader.c @@ -33,6 +33,13 @@ static int l_lovrShaderClone(lua_State* L) { return 1; } +static int l_lovrShaderGetType(lua_State* L) { + Shader* shader = luax_checktype(L, 1, Shader); + const ShaderInfo* info = lovrShaderGetInfo(shader); + luax_pushenum(L, ShaderType, info->type); + return 1; +} + static int l_lovrShaderHasStage(lua_State* L) { Shader* shader = luax_checktype(L, 1, Shader); ShaderStage stage = luax_checkenum(L, 2, ShaderStage, NULL); @@ -86,18 +93,12 @@ static int l_lovrShaderGetBufferFormat(lua_State* L) { return 2; } -// Deprecated -static int l_lovrShaderGetType(lua_State* L) { - lua_pushstring(L, lovrShaderHasStage(luax_checktype(L, 1, Shader), STAGE_COMPUTE) ? "compute" : "graphics"); - return 1; -} - const luaL_Reg lovrShader[] = { { "clone", l_lovrShaderClone }, + { "getType", l_lovrShaderGetType }, { "hasStage", l_lovrShaderHasStage }, { "hasAttribute", l_lovrShaderHasAttribute }, { "getWorkgroupSize", l_lovrShaderGetWorkgroupSize }, { "getBufferFormat", l_lovrShaderGetBufferFormat }, - { "getType", l_lovrShaderGetType }, // Deprecated { NULL, NULL } }; diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index f279f105..c51c7bef 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -32,6 +32,7 @@ #define MAX_CUSTOM_ATTRIBUTES 10 #define LAYOUT_BUILTIN 0 #define LAYOUT_MATERIAL 1 +#define LAYOUT_UNIFORMS 2 #define FLOAT_BITS(f) ((union { float f; uint32_t u; }) { f }).u typedef struct { @@ -94,9 +95,9 @@ struct Sampler { }; enum { - VERTEX = (1 << STAGE_VERTEX), - FRAGMENT = (1 << STAGE_FRAGMENT), - COMPUTE = (1 << STAGE_COMPUTE) + FLAG_VERTEX = (1 << 0), + FLAG_FRAGMENT = (1 << 1), + FLAG_COMPUTE = (1 << 2) }; typedef struct { @@ -129,12 +130,12 @@ struct Shader { uint32_t textureMask; uint32_t samplerMask; uint32_t storageMask; - uint32_t constantSize; - uint32_t constantCount; + uint32_t uniformSize; + uint32_t uniformCount; uint32_t stageMask; ShaderAttribute* attributes; ShaderResource* resources; - DataField* constants; + DataField* uniforms; DataField* fields; uint32_t flagCount; uint32_t overrideCount; @@ -353,7 +354,7 @@ enum { enum { DIRTY_BINDINGS = (1 << 0), - DIRTY_CONSTANTS = (1 << 1), + DIRTY_UNIFORMS = (1 << 1), DIRTY_CAMERA = (1 << 2), NEEDS_VIEW_CULL = (1 << 3) }; @@ -441,7 +442,8 @@ typedef struct { Shader* shader; gpu_bundle_info* bundleInfo; gpu_bundle* bundle; - void* constants; + gpu_buffer* uniformBuffer; + uint32_t uniformOffset; union { struct { uint32_t x; @@ -471,9 +473,10 @@ typedef struct { gpu_bundle_info* bundleInfo; gpu_pipeline* pipeline; gpu_bundle* bundle; - void* constants; gpu_buffer* vertexBuffer; gpu_buffer* indexBuffer; + gpu_buffer* uniformBuffer; + uint32_t uniformOffset; union { struct { uint32_t start; @@ -521,9 +524,10 @@ struct Pass { Pipeline* pipeline; uint32_t transformIndex; uint32_t pipelineIndex; - char* constants; gpu_binding* bindings; uint32_t bindingMask; + void* uniforms; + uint32_t uniformSize; uint32_t computeCount; Compute* computes; uint32_t drawCount; @@ -710,6 +714,13 @@ bool lovrGraphicsInit(GraphicsConfig* config) { size_t materialLayout = getLayout(materialSlots, COUNTOF(materialSlots)); if (materialLayout != LAYOUT_MATERIAL) lovrUnreachable(); + gpu_slot uniformSlots[] = { + { 0, GPU_SLOT_UNIFORM_BUFFER_DYNAMIC, GPU_STAGE_GRAPHICS | GPU_STAGE_COMPUTE } + }; + + size_t uniformLayout = getLayout(uniformSlots, COUNTOF(uniformSlots)); + if (uniformLayout != LAYOUT_UNIFORMS) lovrUnreachable(); + float data[] = { 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f }; state.defaultBuffer = lovrBufferCreate(&(BufferInfo) { @@ -1035,7 +1046,9 @@ static void recordComputePass(Pass* pass, gpu_stream* stream) { gpu_pipeline* pipeline = NULL; gpu_bundle_info* bundleInfo = NULL; - void* constants = NULL; + gpu_bundle* uniformBundle = NULL; + gpu_buffer* uniformBuffer = NULL; + uint32_t uniformOffset = 0; gpu_compute_begin(stream); @@ -1053,9 +1066,19 @@ static void recordComputePass(Pass* pass, gpu_stream* stream) { gpu_bind_bundles(stream, compute->shader->gpu, &bundle, 0, 1, NULL, 0); } - if (compute->constants && compute->constants != constants) { - gpu_push_constants(stream, compute->shader->gpu, compute->constants, compute->shader->constantSize); - constants = compute->constants; + if (compute->uniformBuffer != uniformBuffer || compute->uniformOffset != uniformOffset) { + if (compute->uniformBuffer != uniformBuffer) { + uniformBundle = getBundle(LAYOUT_UNIFORMS, &(gpu_binding) { + .number = 0, + .type = GPU_SLOT_UNIFORM_BUFFER_DYNAMIC, + .buffer.object = compute->uniformBuffer, + .buffer.extent = compute->shader->uniformSize + }, 1); + } + + gpu_bind_bundles(stream, compute->shader->gpu, &uniformBundle, 1, 1, &compute->uniformOffset, 1); + uniformBuffer = compute->uniformBuffer; + uniformOffset = compute->uniformOffset; } if (compute->flags & COMPUTE_INDIRECT) { @@ -1366,13 +1389,14 @@ static void recordRenderPass(Pass* pass, gpu_stream* stream) { Material* material = NULL; gpu_buffer* vertexBuffer = NULL; gpu_buffer* indexBuffer = NULL; - void* constants = NULL; + gpu_buffer* uniformBuffer = NULL; + uint32_t uniformOffset = 0; + gpu_bundle* uniformBundle = NULL; gpu_bind_vertex_buffers(stream, &state.defaultBuffer->gpu, &state.defaultBuffer->base, 1, 1); for (uint32_t i = 0; i < activeDrawCount; i++) { Draw* draw = &pass->draws[activeDraws[i]]; - bool constantsDirty = draw->constants != constants; if (pass->tally.buffer && draw->tally != tally) { if (tally != ~0u) gpu_tally_finish(stream, pass->tally.gpu, tally * canvas->views); @@ -1385,22 +1409,37 @@ static void recordRenderPass(Pass* pass, gpu_stream* stream) { pipeline = draw->pipeline; } - if ((i & 0xff) == 0 || draw->camera != cameraIndex || constantsDirty) { + if ((i & 0xff) == 0 || draw->camera != cameraIndex) { uint32_t dynamicOffsets[] = { draw->camera * canvas->views * sizeof(Camera), (i >> 8) * 256 * sizeof(DrawData) }; gpu_bind_bundles(stream, draw->shader->gpu, &builtinBundle, 0, 1, dynamicOffsets, COUNTOF(dynamicOffsets)); cameraIndex = draw->camera; } - if (draw->material != material || constantsDirty) { + if (draw->material != material) { gpu_bind_bundles(stream, draw->shader->gpu, &draw->material->bundle, 1, 1, NULL, 0); material = draw->material; } - if (draw->bundle && (draw->bundle != bundle || constantsDirty)) { + if (draw->bundle && (draw->bundle != bundle)) { gpu_bind_bundles(stream, draw->shader->gpu, &draw->bundle, 2, 1, NULL, 0); bundle = draw->bundle; } + if (draw->uniformBuffer != uniformBuffer || draw->uniformOffset != uniformOffset) { + if (draw->uniformBuffer != uniformBuffer) { + uniformBundle = getBundle(LAYOUT_UNIFORMS, &(gpu_binding) { + .number = 0, + .type = GPU_SLOT_UNIFORM_BUFFER_DYNAMIC, + .buffer.object = draw->uniformBuffer, + .buffer.extent = draw->shader->uniformSize + }, 1); + } + + gpu_bind_bundles(stream, draw->shader->gpu, &uniformBundle, 3, 1, &draw->uniformOffset, 1); + uniformBuffer = draw->uniformBuffer; + uniformOffset = draw->uniformOffset; + } + if (draw->vertexBuffer && draw->vertexBuffer != vertexBuffer) { gpu_bind_vertex_buffers(stream, &draw->vertexBuffer, NULL, 0, 1); vertexBuffer = draw->vertexBuffer; @@ -1412,11 +1451,6 @@ static void recordRenderPass(Pass* pass, gpu_stream* stream) { indexBuffer = draw->indexBuffer; } - if (draw->constants && constantsDirty) { - gpu_push_constants(stream, draw->shader->gpu, draw->constants, draw->shader->constantSize); - constants = draw->constants; - } - if (draw->flags & DRAW_INDIRECT) { if (draw->indexBuffer) { gpu_draw_indirect_indexed(stream, draw->indirect.buffer, draw->indirect.offset, draw->indirect.count, draw->indirect.stride); @@ -2534,15 +2568,9 @@ static glsl_include_result_t* includer(void* cb, const char* path, const char* i } #endif -ShaderSource lovrGraphicsCompileShader(ShaderStage stage, ShaderSource* source, ShaderIncluder* io) { - uint32_t magic = 0x07230203; - - if (source->size % 4 == 0 && source->size >= 4 && !memcmp(source->code, &magic, 4)) { - return *source; - } - +void lovrGraphicsCompileShader(ShaderSource* stages, ShaderSource* outputs, uint32_t stageCount, ShaderIncluder* io) { #ifdef LOVR_USE_GLSLANG - const glslang_stage_t stages[] = { + const glslang_stage_t stageMap[] = { [STAGE_VERTEX] = GLSLANG_STAGE_VERTEX, [STAGE_FRAGMENT] = GLSLANG_STAGE_FRAGMENT, [STAGE_COMPUTE] = GLSLANG_STAGE_COMPUTE @@ -2560,66 +2588,91 @@ ShaderSource lovrGraphicsCompileShader(ShaderStage stage, ShaderSource* source, "#extension GL_EXT_samplerless_texture_functions : require\n" "#extension GL_GOOGLE_include_directive : require\n"; - const char* strings[] = { - prefix, - (const char*) etc_shaders_lovr_glsl, - "#line 1\n", - source->code - }; + glslang_program_t* program = NULL; + glslang_shader_t* shaders[2] = { 0 }; - lovrCheck(source->size <= INT_MAX, "Shader is way too big"); - - int lengths[] = { - -1, - etc_shaders_lovr_glsl_len, - -1, - (int) source->size - }; - - const glslang_resource_t* resource = glslang_default_resource(); - - glslang_input_t input = { - .language = GLSLANG_SOURCE_GLSL, - .stage = stages[stage], - .client = GLSLANG_CLIENT_VULKAN, - .client_version = GLSLANG_TARGET_VULKAN_1_1, - .target_language = GLSLANG_TARGET_SPV, - .target_language_version = GLSLANG_TARGET_SPV_1_3, - .strings = strings, - .lengths = lengths, - .string_count = COUNTOF(strings), - .default_version = 460, - .default_profile = GLSLANG_NO_PROFILE, - .resource = resource, - .callbacks.include_local = includer, - .callbacks_ctx = (void*) io - }; - - glslang_shader_t* shader = glslang_shader_create(&input); - - int options = 0; - options |= GLSLANG_SHADER_AUTO_MAP_BINDINGS; - options |= GLSLANG_SHADER_AUTO_MAP_LOCATIONS; - options |= GLSLANG_SHADER_VULKAN_RULES_RELAXED; - - glslang_shader_set_options(shader, options); - - if (!glslang_shader_preprocess(shader, &input)) { - lovrThrow("Could not preprocess %s shader:\n%s", stageNames[stage], glslang_shader_get_info_log(shader)); - return (ShaderSource) { NULL, 0 }; + if (stageCount > COUNTOF(shaders)) { + lovrUnreachable(); } - if (!glslang_shader_parse(shader, &input)) { - lovrThrow("Could not parse %s shader:\n%s", stageNames[stage], glslang_shader_get_info_log(shader)); - return (ShaderSource) { NULL, 0 }; + for (uint32_t i = 0; i < stageCount; i++) { + ShaderSource* source = &stages[i]; + + // It's okay to pass precompiled SPIR-V here, and it will be returned unchanged. However, it's + // dangerous to mix SPIR-V and GLSL because then glslang won't perform cross-stage linking, + // which means that e.g. the default uniform block might be different for each stage. This + // isn't a problem when using the default shaders since they don't use uniforms. + uint32_t magic = 0x07230203; + if (source->size % 4 == 0 && source->size >= 4 && !memcmp(source->code, &magic, 4)) { + outputs[i] = stages[i]; + continue; + } else if (!program) { + program = glslang_program_create(); + } + + const char* strings[] = { + prefix, + (const char*) etc_shaders_lovr_glsl, + "#line 1\n", + source->code + }; + + lovrCheck(source->size <= INT_MAX, "Shader is way too big"); + + int lengths[] = { + -1, + etc_shaders_lovr_glsl_len, + -1, + (int) source->size + }; + + const glslang_resource_t* resource = glslang_default_resource(); + + glslang_input_t input = { + .language = GLSLANG_SOURCE_GLSL, + .stage = stageMap[source->stage], + .client = GLSLANG_CLIENT_VULKAN, + .client_version = GLSLANG_TARGET_VULKAN_1_1, + .target_language = GLSLANG_TARGET_SPV, + .target_language_version = GLSLANG_TARGET_SPV_1_3, + .strings = strings, + .lengths = lengths, + .string_count = COUNTOF(strings), + .default_version = 460, + .default_profile = GLSLANG_NO_PROFILE, + .forward_compatible = true, + .resource = resource, + .callbacks.include_local = includer, + .callbacks_ctx = (void*) io + }; + + shaders[i] = glslang_shader_create(&input); + + int options = 0; + options |= GLSLANG_SHADER_AUTO_MAP_BINDINGS; + options |= GLSLANG_SHADER_AUTO_MAP_LOCATIONS; + options |= GLSLANG_SHADER_VULKAN_RULES_RELAXED; + + glslang_shader_set_options(shaders[i], options); + + if (!glslang_shader_preprocess(shaders[i], &input)) { + lovrThrow("Could not preprocess %s shader:\n%s", stageNames[source->stage], glslang_shader_get_info_log(shaders[i])); + } + + if (!glslang_shader_parse(shaders[i], &input)) { + lovrThrow("Could not parse %s shader:\n%s", stageNames[source->stage], glslang_shader_get_info_log(shaders[i])); + } + + glslang_program_add_shader(program, shaders[i]); } - glslang_program_t* program = glslang_program_create(); - glslang_program_add_shader(program, shader); + // We might not need to do anything if all the inputs were already SPIR-V + if (!program) { + return; + } if (!glslang_program_link(program, 0)) { lovrThrow("Could not link shader:\n%s", glslang_program_get_info_log(program)); - return (ShaderSource) { NULL, 0 }; } glslang_program_map_io(program); @@ -2630,25 +2683,36 @@ ShaderSource lovrGraphicsCompileShader(ShaderStage stage, ShaderSource* source, spvOptions.generate_debug_info = true; spvOptions.emit_nonsemantic_shader_debug_info = true; spvOptions.emit_nonsemantic_shader_debug_source = true; - glslang_program_add_source_text(program, stages[stage], source->code, source->size); } - glslang_program_SPIRV_generate_with_options(program, stages[stage], &spvOptions); + for (uint32_t i = 0; i < stageCount; i++) { + if (!shaders[i]) continue; - void* words = glslang_program_SPIRV_get_ptr(program); - size_t size = glslang_program_SPIRV_get_size(program) * 4; + ShaderSource* source = &stages[i]; - void* data = malloc(size); - lovrAssert(data, "Out of memory"); - memcpy(data, words, size); + if (state.config.debug && state.features.shaderDebug) { + glslang_program_add_source_text(program, stageMap[source->stage], source->code, source->size); + } + + glslang_program_SPIRV_generate_with_options(program, stageMap[source->stage], &spvOptions); + + void* words = glslang_program_SPIRV_get_ptr(program); + size_t size = glslang_program_SPIRV_get_size(program) * 4; + + void* data = malloc(size); + lovrAssert(data, "Out of memory"); + memcpy(data, words, size); + + outputs[i].stage = source->stage; + outputs[i].code = data; + outputs[i].size = size; + + glslang_shader_delete(shaders[i]); + } glslang_program_delete(program); - glslang_shader_delete(shader); - - return (ShaderSource) { data, size }; #else lovrThrow("Could not compile shader: No shader compiler available"); - return (ShaderSource) { NULL, 0 }; #endif } @@ -2679,7 +2743,7 @@ static void lovrShaderInit(Shader* shader) { } } - if (shader->stageMask & COMPUTE) { + if (shader->info.type == SHADER_COMPUTE) { gpu_compute_pipeline_info pipelineInfo = { .shader = shader->gpu, .flags = shader->flags, @@ -2694,47 +2758,47 @@ static void lovrShaderInit(Shader* shader) { } ShaderSource lovrGraphicsGetDefaultShaderSource(DefaultShader type, ShaderStage stage) { - const ShaderSource sources[][STAGE_COUNT] = { + const ShaderSource sources[][3] = { [SHADER_UNLIT] = { - [STAGE_VERTEX] = { lovr_shader_unlit_vert, sizeof(lovr_shader_unlit_vert) }, - [STAGE_FRAGMENT] = { lovr_shader_unlit_frag, sizeof(lovr_shader_unlit_frag) } + [STAGE_VERTEX] = { STAGE_VERTEX, lovr_shader_unlit_vert, sizeof(lovr_shader_unlit_vert) }, + [STAGE_FRAGMENT] = { STAGE_FRAGMENT, lovr_shader_unlit_frag, sizeof(lovr_shader_unlit_frag) } }, [SHADER_NORMAL] = { - [STAGE_VERTEX] = { lovr_shader_unlit_vert, sizeof(lovr_shader_unlit_vert) }, - [STAGE_FRAGMENT] = { lovr_shader_normal_frag, sizeof(lovr_shader_normal_frag) } + [STAGE_VERTEX] = { STAGE_VERTEX, lovr_shader_unlit_vert, sizeof(lovr_shader_unlit_vert) }, + [STAGE_FRAGMENT] = { STAGE_FRAGMENT, lovr_shader_normal_frag, sizeof(lovr_shader_normal_frag) } }, [SHADER_FONT] = { - [STAGE_VERTEX] = { lovr_shader_unlit_vert, sizeof(lovr_shader_unlit_vert) }, - [STAGE_FRAGMENT] = { lovr_shader_font_frag, sizeof(lovr_shader_font_frag) } + [STAGE_VERTEX] = { STAGE_VERTEX, lovr_shader_unlit_vert, sizeof(lovr_shader_unlit_vert) }, + [STAGE_FRAGMENT] = { STAGE_FRAGMENT, lovr_shader_font_frag, sizeof(lovr_shader_font_frag) } }, [SHADER_CUBEMAP] = { - [STAGE_VERTEX] = { lovr_shader_cubemap_vert, sizeof(lovr_shader_cubemap_vert) }, - [STAGE_FRAGMENT] = { lovr_shader_cubemap_frag, sizeof(lovr_shader_cubemap_frag) } + [STAGE_VERTEX] = { STAGE_VERTEX, lovr_shader_cubemap_vert, sizeof(lovr_shader_cubemap_vert) }, + [STAGE_FRAGMENT] = { STAGE_FRAGMENT, lovr_shader_cubemap_frag, sizeof(lovr_shader_cubemap_frag) } }, [SHADER_EQUIRECT] = { - [STAGE_VERTEX] = { lovr_shader_cubemap_vert, sizeof(lovr_shader_cubemap_vert) }, - [STAGE_FRAGMENT] = { lovr_shader_equirect_frag, sizeof(lovr_shader_equirect_frag) } + [STAGE_VERTEX] = { STAGE_VERTEX, lovr_shader_cubemap_vert, sizeof(lovr_shader_cubemap_vert) }, + [STAGE_FRAGMENT] = { STAGE_FRAGMENT, lovr_shader_equirect_frag, sizeof(lovr_shader_equirect_frag) } }, [SHADER_FILL_2D] = { - [STAGE_VERTEX] = { lovr_shader_fill_vert, sizeof(lovr_shader_fill_vert) }, - [STAGE_FRAGMENT] = { lovr_shader_unlit_frag, sizeof(lovr_shader_unlit_frag) } + [STAGE_VERTEX] = { STAGE_VERTEX, lovr_shader_fill_vert, sizeof(lovr_shader_fill_vert) }, + [STAGE_FRAGMENT] = { STAGE_FRAGMENT, lovr_shader_unlit_frag, sizeof(lovr_shader_unlit_frag) } }, [SHADER_FILL_ARRAY] = { - [STAGE_VERTEX] = { lovr_shader_fill_vert, sizeof(lovr_shader_fill_vert) }, - [STAGE_FRAGMENT] = { lovr_shader_fill_array_frag, sizeof(lovr_shader_fill_array_frag) } + [STAGE_VERTEX] = { STAGE_VERTEX, lovr_shader_fill_vert, sizeof(lovr_shader_fill_vert) }, + [STAGE_FRAGMENT] = { STAGE_FRAGMENT, lovr_shader_fill_array_frag, sizeof(lovr_shader_fill_array_frag) } }, [SHADER_LOGO] = { - [STAGE_VERTEX] = { lovr_shader_unlit_vert, sizeof(lovr_shader_unlit_vert) }, - [STAGE_FRAGMENT] = { lovr_shader_logo_frag, sizeof(lovr_shader_logo_frag) } + [STAGE_VERTEX] = { STAGE_VERTEX, lovr_shader_unlit_vert, sizeof(lovr_shader_unlit_vert) }, + [STAGE_FRAGMENT] = { STAGE_FRAGMENT, lovr_shader_logo_frag, sizeof(lovr_shader_logo_frag) } }, [SHADER_ANIMATOR] = { - [STAGE_COMPUTE] = { lovr_shader_animator_comp, sizeof(lovr_shader_animator_comp) } + [STAGE_COMPUTE] = { STAGE_COMPUTE, lovr_shader_animator_comp, sizeof(lovr_shader_animator_comp) } }, [SHADER_BLENDER] = { - [STAGE_COMPUTE] = { lovr_shader_blender_comp, sizeof(lovr_shader_blender_comp) } + [STAGE_COMPUTE] = { STAGE_COMPUTE, lovr_shader_blender_comp, sizeof(lovr_shader_blender_comp) } }, [SHADER_TALLY_MERGE] = { - [STAGE_COMPUTE] = { lovr_shader_tallymerge_comp, sizeof(lovr_shader_tallymerge_comp) } + [STAGE_COMPUTE] = { STAGE_COMPUTE, lovr_shader_tallymerge_comp, sizeof(lovr_shader_tallymerge_comp) } } }; @@ -2751,14 +2815,22 @@ Shader* lovrGraphicsGetDefaultShader(DefaultShader type) { case SHADER_BLENDER: case SHADER_TALLY_MERGE: return state.defaultShaders[type] = lovrShaderCreate(&(ShaderInfo) { - .source[STAGE_COMPUTE] = lovrGraphicsGetDefaultShaderSource(type, STAGE_COMPUTE), + .type = SHADER_COMPUTE, + .stages = (ShaderSource[1]) { + lovrGraphicsGetDefaultShaderSource(type, STAGE_COMPUTE) + }, + .stageCount = 1, .flags = &(ShaderFlag) { NULL, 0, state.device.subgroupSize }, .flagCount = 1 }); default: return state.defaultShaders[type] = lovrShaderCreate(&(ShaderInfo) { - .source[STAGE_VERTEX] = lovrGraphicsGetDefaultShaderSource(type, STAGE_VERTEX), - .source[STAGE_FRAGMENT] = lovrGraphicsGetDefaultShaderSource(type, STAGE_FRAGMENT) + .type = SHADER_GRAPHICS, + .stages = (ShaderSource[2]) { + lovrGraphicsGetDefaultShaderSource(type, STAGE_VERTEX), + lovrGraphicsGetDefaultShaderSource(type, STAGE_FRAGMENT) + }, + .stageCount = 2 }); } } @@ -2767,24 +2839,25 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { Shader* shader = calloc(1, sizeof(Shader) + gpu_sizeof_shader()); lovrAssert(shader, "Out of memory"); - for (uint32_t i = 0; i < COUNTOF(info->source); i++) { - if (info->source[i].code) { - shader->stageMask |= (1 << i); - } + size_t stack = tempPush(&state.allocator); + + for (uint32_t i = 0; i < info->stageCount; i++) { + shader->stageMask |= (1 << info->stages[i].stage); } - ShaderStage stages[2]; - uint32_t stageCount = 0; + if (info->type == SHADER_GRAPHICS) { + uint32_t mask = FLAG_VERTEX | FLAG_FRAGMENT; + lovrCheck(shader->stageMask & FLAG_VERTEX, "Graphics shaders must have a vertex stage"); + lovrCheck((shader->stageMask & mask) == mask, "Graphics shaders can only have vertex and pixel stages"); + } else if (info->type == SHADER_COMPUTE) { + lovrCheck(shader->stageMask == FLAG_COMPUTE, "Compute shaders can only have a compute stage"); + } - if (shader->stageMask == VERTEX) { - stages[stageCount++] = STAGE_VERTEX; - } else if (shader->stageMask == (VERTEX | FRAGMENT)) { - stages[stageCount++] = STAGE_VERTEX; - stages[stageCount++] = STAGE_FRAGMENT; - } else if (shader->stageMask == COMPUTE) { - stages[stageCount++] = STAGE_COMPUTE; - } else { - lovrThrow("Invalid combination of shader stages given. Expected vertex, vertex + pixel, or compute"); + // Copy the source, because we perform edits on the SPIR-V and the input might be readonly memory + void* source[2]; + for (uint32_t i = 0; i < info->stageCount; i++) { + source[i] = tempAlloc(&state.allocator, info->stages[i].size); + memcpy(source[i], info->stages[i].code, info->stages[i].size); } // Parse SPIR-V @@ -2794,8 +2867,8 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { uint32_t maxSpecConstants = 0; uint32_t maxFields = 0; uint32_t maxChars = 0; - for (uint32_t i = 0; i < stageCount; i++) { - result = spv_parse(info->source[stages[i]].code, info->source[stages[i]].size, &spv[i]); + for (uint32_t i = 0; i < info->stageCount; i++) { + result = spv_parse(source[i], info->stages[i].size, &spv[i]); lovrCheck(result == SPV_OK, "Failed to load Shader: %s\n", spv_result_to_string(result)); lovrCheck(spv[i].version <= 0x00010300, "Invalid SPIR-V version (up to 1.3 is supported)"); @@ -2806,7 +2879,7 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { spv[i].fields = tempAlloc(&state.allocator, spv[i].fieldCount * sizeof(spv_field)); memset(spv[i].fields, 0, spv[i].fieldCount * sizeof(spv_field)); - result = spv_parse(info->source[stages[i]].code, info->source[stages[i]].size, &spv[i]); + result = spv_parse(source[i], info->stages[i].size, &spv[i]); lovrCheck(result == SPV_OK, "Failed to load Shader: %s\n", spv_result_to_string(result)); checkShaderFeatures(spv[i].features, spv[i].featureCount); @@ -2832,7 +2905,7 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { lovrAssert(shader->flags && shader->flagLookup, "Out of memory"); // Stage-specific metadata - if (shader->stageMask & COMPUTE) { + if (info->type == SHADER_COMPUTE) { memcpy(shader->workgroupSize, spv[0].workgroupSize, 3 * sizeof(uint32_t)); lovrCheck(shader->workgroupSize[0] <= state.limits.workgroupSize[0], "Shader workgroup size exceeds the 'workgroupSize' limit"); lovrCheck(shader->workgroupSize[1] <= state.limits.workgroupSize[1], "Shader workgroup size exceeds the 'workgroupSize' limit"); @@ -2850,12 +2923,13 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { } } - uint32_t userSet = shader->stageMask & COMPUTE ? 0 : 2; + uint32_t resourceSet = info->type == SHADER_COMPUTE ? 0 : 2; + uint32_t uniformSet = info->type == SHADER_COMPUTE ? 1 : 3; uint32_t lastResourceCount = 0; // Resources - for (uint32_t s = 0; s < stageCount; s++, lastResourceCount = shader->resourceCount) { - ShaderStage stage = stages[s]; + for (uint32_t s = 0; s < info->stageCount; s++, lastResourceCount = shader->resourceCount) { + ShaderStage stage = info->stages[s].stage; for (uint32_t i = 0; i < spv[s].resourceCount; i++) { spv_resource* resource = &spv[s].resources[i]; uint32_t* set = (uint32_t*) resource->set; @@ -2865,7 +2939,7 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { continue; } - if (!(*set == userSet || (*set == 0 && *binding > LAST_BUILTIN_BINDING))) { + if (!(*set == resourceSet || (*set == 0 && *binding > LAST_BUILTIN_BINDING))) { continue; } @@ -2905,7 +2979,7 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { lovrCheck(other->type == resourceTypes[resource->type], "Shader variable '%s' is declared in multiple shader stages with different types", resource->name); slots[j].stages |= stageMap[stage]; shader->resources[j].phase |= stagePhase[stage]; - *set = userSet; + *set = resourceSet; *binding = shader->resources[j].binding; skip = true; break; @@ -2916,12 +2990,24 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { continue; } + if (!strcmp(resource->name, "gl_DefaultUniformBlock") || !strcmp(resource->name, "DefaultUniformBlock")) { + spv_field* block = resource->bufferFields; + shader->uniformSize = block->elementSize; + shader->uniformCount = block->fieldCount; + shader->uniforms = shader->fields + ((s == 1 ? spv[0].fieldCount : 0) + (block->fields - spv[s].fields)); + *set = uniformSet; + *binding = 0; + continue; + } + uint32_t index = shader->resourceCount++; lovrAssert(index < MAX_SHADER_RESOURCES, "Shader resource count exceeds resourcesPerShader limit (%d)", MAX_SHADER_RESOURCES); - *set = userSet; - *binding = index; + if (*set != resourceSet) { + *set = resourceSet; + *binding = index; + } slots[index] = (gpu_slot) { .number = index, @@ -2978,7 +3064,7 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { // Fields char* name = shader->names; - for (uint32_t s = 0; s < stageCount; s++) { + for (uint32_t s = 0; s < info->stageCount; s++) { for (uint32_t i = 0; i < spv[s].fieldCount; i++) { static const DataType dataTypes[] = { [SPV_B32] = TYPE_U32, @@ -3032,24 +3118,8 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { } } - // Push constant fields (use the biggest struct that actually exists, if any) - spv_field* c1 = spv[0].pushConstants; - spv_field* c2 = spv[1].pushConstants; - - if (c1 && (!c2 || c1->elementSize > c2->elementSize)) { - shader->constants = shader->fields + (c1->fields - spv[0].fields); - shader->constantCount = c1->fieldCount; - shader->constantSize = c1->elementSize; - } else if (c2) { - shader->constants = shader->fields + spv[0].fieldCount + (c2->fields - spv[1].fields); - shader->constantCount = c2->fieldCount; - shader->constantSize = c2->elementSize; - } - - lovrCheck(shader->constantSize <= state.limits.pushConstantSize, "Shader push constants block is too big"); - // Specialization constants - for (uint32_t s = 0; s < stageCount; s++) { + for (uint32_t s = 0; s < info->stageCount; s++) { for (uint32_t i = 0; i < spv[s].specConstantCount; i++) { spv_spec_constant* constant = &spv[s].specConstants[i]; @@ -3096,28 +3166,44 @@ Shader* lovrShaderCreate(const ShaderInfo* info) { } } + // Push constants + uint32_t pushConstantSize = 0; + for (uint32_t i = 0; i < info->stageCount; i++) { + if (spv[i].pushConstants) { + pushConstantSize = MAX(pushConstantSize, spv[i].pushConstants->elementSize); + } + } + shader->ref = 1; shader->gpu = (gpu_shader*) (shader + 1); shader->info = *info; shader->layout = getLayout(slots, shader->resourceCount); gpu_shader_info gpu = { - .vertex = { info->source[STAGE_VERTEX].code, info->source[STAGE_VERTEX].size }, - .fragment = { info->source[STAGE_FRAGMENT].code, info->source[STAGE_FRAGMENT].size }, - .compute = { info->source[STAGE_COMPUTE].code, info->source[STAGE_COMPUTE].size }, - .pushConstantSize = shader->constantSize, + .pushConstantSize = pushConstantSize, .label = info->label }; - if (shader->stageMask & (VERTEX | FRAGMENT)) { + for (uint32_t i = 0; i < info->stageCount; i++) { + switch (info->stages[i].stage) { + case STAGE_VERTEX: gpu.vertex = (gpu_shader_stage) { .code = source[i], .length = info->stages[i].size }; break; + case STAGE_FRAGMENT: gpu.fragment = (gpu_shader_stage) { .code = source[i], .length = info->stages[i].size }; break; + case STAGE_COMPUTE: gpu.compute = (gpu_shader_stage) { .code = source[i], .length = info->stages[i].size }; break; + default: break; + } + } + + if (info->type == SHADER_GRAPHICS) { gpu.layouts[0] = state.layouts.data[LAYOUT_BUILTIN].gpu; gpu.layouts[1] = state.layouts.data[LAYOUT_MATERIAL].gpu; } - gpu.layouts[userSet] = shader->resourceCount > 0 ? state.layouts.data[shader->layout].gpu : NULL; + gpu.layouts[resourceSet] = state.layouts.data[shader->layout].gpu; + if (shader->uniformSize > 0) gpu.layouts[uniformSet] = state.layouts.data[LAYOUT_UNIFORMS].gpu; gpu_shader_init(shader->gpu, &gpu); lovrShaderInit(shader); + tempPop(&state.allocator, stack); return shader; } @@ -3137,13 +3223,13 @@ Shader* lovrShaderClone(Shader* parent, ShaderFlag* flags, uint32_t count) { shader->textureMask = parent->textureMask; shader->samplerMask = parent->samplerMask; shader->storageMask = parent->storageMask; - shader->constantSize = parent->constantSize; - shader->constantCount = parent->constantCount; + shader->uniformSize = parent->uniformSize; + shader->uniformCount = parent->uniformCount; shader->resourceCount = parent->resourceCount; shader->flagCount = parent->flagCount; shader->attributes = parent->attributes; shader->resources = parent->resources; - shader->constants = parent->constants; + shader->uniforms = parent->uniforms; shader->fields = parent->fields; shader->names = parent->names; shader->flags = malloc(shader->flagCount * sizeof(gpu_shader_flag)); @@ -5142,11 +5228,12 @@ void lovrPassReset(Pass* pass) { pass->allocator.cursor = 0; pass->access[ACCESS_RENDER] = NULL; pass->access[ACCESS_COMPUTE] = NULL; - pass->flags = DIRTY_BINDINGS | DIRTY_CONSTANTS; + pass->flags = DIRTY_BINDINGS; pass->transform = lovrPassAllocate(pass, TRANSFORM_STACK_SIZE * 16 * sizeof(float)); pass->pipeline = lovrPassAllocate(pass, PIPELINE_STACK_SIZE * sizeof(Pipeline)); - pass->constants = lovrPassAllocate(pass, state.limits.pushConstantSize); pass->bindings = lovrPassAllocate(pass, 32 * sizeof(gpu_binding)); + pass->uniforms = NULL; + pass->uniformSize = 0; pass->computeCount = 0; pass->computes = NULL; pass->drawCount = 0; @@ -5573,8 +5660,8 @@ void lovrPassSetShader(Pass* pass, Shader* shader) { Shader* previous = pass->pipeline->shader; if (shader == previous) return; - bool fromCompute = previous && (previous->stageMask & COMPUTE); - bool toCompute = shader && (shader->stageMask & COMPUTE); + bool fromCompute = previous && previous->info.type == SHADER_COMPUTE; + bool toCompute = shader && shader->info.type == SHADER_COMPUTE; if (fromCompute ^ toCompute) { pass->bindingMask = 0; @@ -5645,8 +5732,12 @@ void lovrPassSetShader(Pass* pass, Shader* shader) { pass->pipeline->lastVertexBuffer = NULL; } - if (shader && shader->constantSize > 0 && (!previous || previous->constantSize != shader->constantSize)) { - pass->flags |= DIRTY_CONSTANTS; + if (shader && shader->uniformSize > pass->uniformSize) { + void* uniforms = lovrPassAllocate(pass, shader->uniformSize); + if (pass->uniforms) memcpy(uniforms, pass->uniforms, pass->uniformSize); + pass->uniformSize = shader->uniformSize; + pass->uniforms = uniforms; + pass->flags |= DIRTY_UNIFORMS; } } @@ -5779,11 +5870,11 @@ void lovrPassSendData(Pass* pass, const char* name, size_t length, void** data, lovrCheck(shader, "A Shader must be active to send data to it"); uint32_t hash = (uint32_t) hash64(name, length); - for (uint32_t i = 0; i < shader->constantCount; i++) { - if (shader->constants[i].hash == hash) { - *data = (char*) pass->constants + shader->constants[i].offset; - *format = &shader->constants[i]; - pass->flags |= DIRTY_CONSTANTS; + for (uint32_t i = 0; i < shader->uniformCount; i++) { + if (shader->uniforms[i].hash == hash) { + *data = (char*) pass->uniforms + shader->uniforms[i].offset; + *format = &shader->uniforms[i]; + pass->flags |= DIRTY_UNIFORMS; return; } } @@ -5892,7 +5983,7 @@ static void lovrPassResolvePipeline(Pass* pass, DrawInfo* info, Draw* draw, Draw } } -static void lovrPassResolveBuffers(Pass* pass, DrawInfo* info, Draw* draw) { +static void lovrPassResolveVertices(Pass* pass, DrawInfo* info, Draw* draw) { CachedShape* cached = info->hash ? &pass->geocache[info->hash & (COUNTOF(pass->geocache) - 1)] : NULL; if (cached && cached->hash == info->hash) { @@ -5977,19 +6068,11 @@ static gpu_bundle_info* lovrPassResolveBindings(Pass* pass, Shader* shader, gpu_ return bundle; } -static void* lovrPassResolveConstants(Pass* pass, Shader* shader, void* previous) { - if (shader->constantSize == 0) { - return NULL; - } - - if (~pass->flags & DIRTY_CONSTANTS) { - return previous; - } - - void* constants = lovrPassAllocate(pass, shader->constantSize); - memcpy(constants, pass->constants, shader->constantSize); - pass->flags &= ~DIRTY_CONSTANTS; - return constants; +static void lovrPassResolveUniforms(Pass* pass, Shader* shader, gpu_buffer** buffer, uint32_t* offset) { + BufferView view = lovrPassGetBuffer(pass, shader->uniformSize, state.limits.uniformBufferAlign); + memcpy(view.pointer, pass->uniforms, shader->uniformSize); + *buffer = view.buffer; + *offset = view.offset; } void lovrPassDraw(Pass* pass, DrawInfo* info) { @@ -6001,7 +6084,7 @@ void lovrPassDraw(Pass* pass, DrawInfo* info) { pass->draws = draws; } - Draw* prev = pass->drawCount > 0 ? &pass->draws[pass->drawCount - 1] : NULL; + Draw* previous = pass->drawCount > 0 ? &pass->draws[pass->drawCount - 1] : NULL; Draw* draw = &pass->draws[pass->drawCount++]; draw->flags = 0; @@ -6010,7 +6093,7 @@ void lovrPassDraw(Pass* pass, DrawInfo* info) { pass->flags &= ~DIRTY_CAMERA; draw->shader = pass->pipeline->shader ? pass->pipeline->shader : lovrGraphicsGetDefaultShader(info->shader); - lovrCheck(draw->shader->stageMask & (VERTEX | FRAGMENT), "Tried to draw while a compute shader is active"); + lovrCheck(draw->shader->info.type == SHADER_GRAPHICS, "Tried to draw while a compute shader is active"); lovrRetain(draw->shader); draw->material = info->material; @@ -6023,11 +6106,17 @@ void lovrPassDraw(Pass* pass, DrawInfo* info) { draw->instances = MAX(info->instances, 1); draw->baseVertex = info->baseVertex; - lovrPassResolvePipeline(pass, info, draw, prev); - lovrPassResolveBuffers(pass, info, draw); + lovrPassResolvePipeline(pass, info, draw, previous); + lovrPassResolveVertices(pass, info, draw); + draw->bundleInfo = lovrPassResolveBindings(pass, draw->shader, previous ? previous->bundleInfo : NULL); - draw->bundleInfo = lovrPassResolveBindings(pass, draw->shader, prev ? prev->bundleInfo : NULL); - draw->constants = lovrPassResolveConstants(pass, draw->shader, prev ? prev->constants : NULL); + if (pass->flags & DIRTY_UNIFORMS) { + lovrPassResolveUniforms(pass, draw->shader, &draw->uniformBuffer, &draw->uniformOffset); + pass->flags &= ~DIRTY_UNIFORMS; + } else { + draw->uniformBuffer = previous ? previous->uniformBuffer : NULL; + draw->uniformOffset = previous ? previous->uniformOffset : 0; + } if (pass->pipeline->viewCull && info->bounds) { memcpy(draw->bounds, info->bounds, sizeof(draw->bounds)); @@ -7081,7 +7170,7 @@ void lovrPassMeshIndirect(Pass* pass, Buffer* vertices, Buffer* indices, Buffer* pass->draws = draws; } - Draw* prev = pass->drawCount > 0 ? &pass->draws[pass->drawCount - 1] : NULL; + Draw* previous = pass->drawCount > 0 ? &pass->draws[pass->drawCount - 1] : NULL; Draw* draw = &pass->draws[pass->drawCount++]; draw->flags = DRAW_INDIRECT; @@ -7101,11 +7190,17 @@ void lovrPassMeshIndirect(Pass* pass, Buffer* vertices, Buffer* indices, Buffer* draw->indirect.count = count; draw->indirect.stride = stride; - lovrPassResolvePipeline(pass, &info, draw, prev); - lovrPassResolveBuffers(pass, &info, draw); + lovrPassResolvePipeline(pass, &info, draw, previous); + lovrPassResolveVertices(pass, &info, draw); + draw->bundleInfo = lovrPassResolveBindings(pass, shader, previous ? previous->bundleInfo : NULL); - draw->bundleInfo = lovrPassResolveBindings(pass, shader, prev ? prev->bundleInfo : NULL); - draw->constants = lovrPassResolveConstants(pass, shader, prev ? prev->constants : NULL); + if (pass->flags & DIRTY_UNIFORMS) { + lovrPassResolveUniforms(pass, shader, &draw->uniformBuffer, &draw->uniformOffset); + pass->flags &= ~DIRTY_UNIFORMS; + } else { + draw->uniformBuffer = previous ? previous->uniformBuffer : NULL; + draw->uniformOffset = previous ? previous->uniformOffset : 0; + } mat4_init(draw->transform, pass->transform); memcpy(draw->color, pass->pipeline->color, 4 * sizeof(float)); @@ -7150,7 +7245,7 @@ void lovrPassCompute(Pass* pass, uint32_t x, uint32_t y, uint32_t z, Buffer* ind Compute* compute = &pass->computes[pass->computeCount++]; Shader* shader = pass->pipeline->shader; - lovrCheck(shader->stageMask == COMPUTE, "To run a compute shader, a compute shader must be active"); + lovrCheck(shader->info.type == SHADER_COMPUTE, "To run a compute shader, a compute shader must be active"); lovrCheck(x <= state.limits.workgroupCount[0], "Compute %s count exceeds workgroupCount limit", "x"); lovrCheck(y <= state.limits.workgroupCount[1], "Compute %s count exceeds workgroupCount limit", "y"); lovrCheck(z <= state.limits.workgroupCount[2], "Compute %s count exceeds workgroupCount limit", "z"); @@ -7160,7 +7255,14 @@ void lovrPassCompute(Pass* pass, uint32_t x, uint32_t y, uint32_t z, Buffer* ind lovrRetain(shader); compute->bundleInfo = lovrPassResolveBindings(pass, shader, previous ? previous->bundleInfo : NULL); - compute->constants = lovrPassResolveConstants(pass, shader, previous ? previous->constants : NULL); + + if (pass->flags & DIRTY_UNIFORMS) { + lovrPassResolveUniforms(pass, shader, &compute->uniformBuffer, &compute->uniformOffset); + pass->flags &= ~DIRTY_UNIFORMS; + } else { + compute->uniformBuffer = previous ? previous->uniformBuffer : NULL; + compute->uniformOffset = previous ? previous->uniformOffset : 0; + } if (indirect) { compute->flags |= COMPUTE_INDIRECT; diff --git a/src/modules/graphics/graphics.h b/src/modules/graphics/graphics.h index 2a2325c9..7079cdfc 100644 --- a/src/modules/graphics/graphics.h +++ b/src/modules/graphics/graphics.h @@ -294,14 +294,19 @@ typedef enum { DEFAULT_SHADER_COUNT } DefaultShader; +typedef enum { + SHADER_GRAPHICS, + SHADER_COMPUTE +} ShaderType; + typedef enum { STAGE_VERTEX, STAGE_FRAGMENT, - STAGE_COMPUTE, - STAGE_COUNT + STAGE_COMPUTE } ShaderStage; typedef struct { + ShaderStage stage; const void* code; size_t size; } ShaderSource; @@ -313,15 +318,17 @@ typedef struct { } ShaderFlag; typedef struct { - ShaderSource source[STAGE_COUNT]; - uint32_t flagCount; + ShaderType type; + ShaderSource* stages; + uint32_t stageCount; ShaderFlag* flags; + uint32_t flagCount; const char* label; } ShaderInfo; typedef void* ShaderIncluder(const char* filename, size_t* bytesRead); -ShaderSource lovrGraphicsCompileShader(ShaderStage stage, ShaderSource* source, ShaderIncluder* includer); +void lovrGraphicsCompileShader(ShaderSource* stages, ShaderSource* outputs, uint32_t count, ShaderIncluder* includer); ShaderSource lovrGraphicsGetDefaultShaderSource(DefaultShader type, ShaderStage stage); Shader* lovrGraphicsGetDefaultShader(DefaultShader type); Shader* lovrShaderCreate(const ShaderInfo* info);