rtengine/src/asset_compiler/effect_processor.c
Kevin Trogant 3d0d4169f1 Progress towards rendering with effects
- Added a null renderer to simplify testing
2024-03-08 01:13:10 +01:00

456 lines
17 KiB
C

#include "description_parser.h"
#include "processor.h"
#include "shader_compiler.h"
#include "runtime/buffer_manager.h"
#include "runtime/config.h"
#include "runtime/mem_arena.h"
#include "runtime/runtime.h"
#include "gfx/effect.h"
#include "gfx/gfx.h"
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <string.h>
typedef struct {
rt_resource shaders[3];
char *shader_names[3];
unsigned int shader_count;
unsigned int vertex_shader;
unsigned int fragment_shader;
unsigned int compute_shader;
/* TODO Fixed function settings */
/* Sampler settings */
uint16_t uniform_binding_count;
uint16_t storage_binding_count;
uint16_t texture_binding_count;
} rt_parsed_pipeline_data;
typedef struct {
unsigned int pass_count;
rt_parsed_pipeline_data pipelines[RT_MAX_SUBRESOURCES];
rt_render_pass_id pass_ids[RT_MAX_SUBRESOURCES];
} rt_parsed_effect_data;
enum {
RT_SHADER_NOT_PRESENT = RT_ASSET_PROCESSING_FAILED + 1
};
extern RT_DLLIMPORT rt_cvar rt_Renderer;
static char *GenerateShaderName(rt_shader_type type,
rt_shader_stage stage,
rt_shader_optimization_level optimization,
const char *file_name,
rt_arena *arena) {
size_t name_len = strlen(file_name) + 5 /* type */
+ 5 /* stage */
+ 3 /* optimization */
+ 1; /* '\0' */
char *res_name = rtArenaPush(arena, name_len);
if (!res_name)
return NULL;
const char *type_str = NULL;
switch (type) {
case RT_SHADER_TYPE_VULKAN:
type_str = ":vk";
break;
default:
return NULL;
}
const char *stage_str = NULL;
switch (stage) {
case RT_SHADER_STAGE_VERTEX:
stage_str = ":vert";
break;
case RT_SHADER_STAGE_FRAGMENT:
stage_str = ":frag";
break;
case RT_SHADER_STAGE_COMPUTE:
stage_str = ":comp";
break;
default:
return NULL;
}
const char *optim_str = NULL;
switch (optimization) {
case RT_SHADER_OPTIMIZATION_NONE:
optim_str = ":O0";
break;
case RT_SHADER_OPTIMIZATION_SIZE:
optim_str = ":Os";
break;
case RT_SHADER_OPTIMIZATION_SPEED:
optim_str = ":Ox";
break;
default:
return NULL;
}
rtSPrint(res_name, name_len, "%s%s%s%s", file_name, type_str, stage_str, optim_str);
return res_name;
}
static rt_result ParseShader(rt_parse_state *state,
unsigned int root_list,
const char *name,
const char *file_path,
rt_shader_type type,
rt_shader_stage stage,
rt_shader_optimization_level optimization,
rt_resource *p_resource,
char **p_resource_name,
rt_arena *arena) {
const rt_parsed_stmt *stmt = rtFindStatement(state, root_list, name);
if (stmt) {
if (stmt->form != RT_STMT_FORM_LIST) {
rtReportError("GFX",
"Expected a list as the value of "
"\"%s\" in %s",
name,
file_path);
return RT_ASSET_PROCESSING_FAILED;
}
const rt_parsed_stmt_list *shader_list = &state->statement_lists[stmt->list_index];
unsigned int stmt_index = shader_list->first;
for (unsigned int i = 0; i < shader_list->count; ++i) {
const rt_parsed_stmt *shader = &state->statements[stmt_index];
if (shader->form != RT_STMT_FORM_VALUE && shader->form != RT_STMT_FORM_BLOCK) {
rtReportError("GFX",
"Expected a simple statement or block for "
"\"%s.%*s\" in %s",
name,
(int)shader->attribute.length,
shader->attribute.start,
file_path);
return RT_ASSET_PROCESSING_FAILED;
}
rt_shader_type in_file_type = RT_SHADER_TYPE_INVALID;
if (rtCompareSpanToString(shader->attribute, "vk") == 0) {
in_file_type = RT_SHADER_TYPE_VULKAN;
} else {
rtReportError("GFX",
"Invalid renderer backend"
"\"%*s\" in %s of file %s",
(int)shader->attribute.length,
shader->attribute.start,
name,
file_path);
return RT_ASSET_PROCESSING_FAILED;
}
if (in_file_type == type) {
if (shader->form == RT_STMT_FORM_BLOCK) {
/* Inline code */
rt_shader_bytecode bytecode =
CompileShader(type, stage, optimization, shader->block, file_path, arena);
if (!bytecode.bytes)
return RT_ASSET_PROCESSING_FAILED;
*p_resource_name =
GenerateShaderName(type, stage, optimization, file_path, arena);
if (!*p_resource_name)
return RT_ASSET_PROCESSING_FAILED;
rt_resource resource;
resource.type = RT_RESOURCE_SHADER;
resource.dependency_count = 0;
resource.subresource_count = 0;
resource.data = rtArenaPush(arena, sizeof(rt_shader_info) + bytecode.len);
if (!resource.data)
return RT_ASSET_PROCESSING_FAILED;
rt_shader_info *shader_info = resource.data;
uint8_t *shader_bytecode = (uint8_t *)(shader_info + 1);
shader_info->stage = stage;
shader_info->type = type;
shader_info->bytecode_length = bytecode.len;
rtSetRelptr(&shader_info->bytecode, shader_bytecode);
memcpy(shader_bytecode, bytecode.bytes, bytecode.len);
memcpy(p_resource, &resource, sizeof(resource));
break;
} else if (shader->form != RT_STMT_FORM_VALUE) {
/* A filename */
rtLog("AC", "Shader source files not implemented yet!");
return RT_ASSET_PROCESSING_FAILED;
}
}
stmt_index = shader->next;
}
return RT_SUCCESS;
}
return RT_SHADER_NOT_PRESENT;
}
static rt_shader_optimization_level
ParseOptimizationLevel(rt_parse_state *state, unsigned int root_list, const char *file_path) {
const rt_parsed_stmt *stmt = rtFindStatement(state, root_list, "optimization");
rt_shader_optimization_level optimization_level = RT_SHADER_OPTIMIZATION_NONE;
if (stmt) {
if (stmt->form != RT_STMT_FORM_VALUE) {
rtReportError("GFX",
"Expected a simple statement for"
"\"optimization\" in %s",
file_path);
return RT_SHADER_OPTIMIZATION_NONE;
}
if (rtCompareSpanToString(stmt->value, "speed") == 0) {
optimization_level = RT_SHADER_OPTIMIZATION_SPEED;
} else if (rtCompareSpanToString(stmt->value, "size") == 0) {
optimization_level = RT_SHADER_OPTIMIZATION_SIZE;
} else if (rtCompareSpanToString(stmt->value, "none") == 0) {
optimization_level = RT_SHADER_OPTIMIZATION_NONE;
} else {
rtReportError("GFX",
"Expected one of 'speed', 'size' and 'none' for \"optimization\" in %s",
file_path);
}
}
return optimization_level;
}
static rt_result ParsePipeline(rt_parse_state *state,
unsigned int root_list,
const char *file_path,
rt_shader_optimization_level optimization,
rt_parsed_pipeline_data *pipeline,
rt_arena *arena) {
rt_result result = RT_SUCCESS;
rt_shader_type type = RT_SHADER_TYPE_INVALID;
if (strcmp(rt_Renderer.s, "vk") == 0)
type = RT_SHADER_TYPE_VULKAN;
if (type == RT_SHADER_TYPE_INVALID) {
result = RT_ASSET_PROCESSING_FAILED;
rtLog("AC", "rt_Renderer (%s) could not be translated to a shader type.", rt_Renderer.s);
goto out;
}
/* Process shader stages */
result = ParseShader(state,
root_list,
"vertex",
file_path,
type,
RT_SHADER_STAGE_VERTEX,
optimization,
&pipeline->shaders[pipeline->shader_count],
&pipeline->shader_names[pipeline->shader_count],
arena);
if (result == RT_SUCCESS) {
pipeline->vertex_shader = pipeline->shader_count;
++pipeline->shader_count;
} else if (result == RT_SHADER_NOT_PRESENT) {
pipeline->vertex_shader = UINT_MAX;
} else {
goto out;
}
result = RT_SUCCESS;
result = ParseShader(state,
root_list,
"fragment",
file_path,
type,
RT_SHADER_STAGE_FRAGMENT,
optimization,
&pipeline->shaders[pipeline->shader_count],
&pipeline->shader_names[pipeline->shader_count],
arena);
if (result == RT_SUCCESS) {
pipeline->fragment_shader = pipeline->shader_count;
++pipeline->shader_count;
} else if (result == RT_SHADER_NOT_PRESENT) {
pipeline->fragment_shader = UINT_MAX;
} else {
goto out;
}
result = RT_SUCCESS;
result = ParseShader(state,
root_list,
"compute",
file_path,
type,
RT_SHADER_STAGE_COMPUTE,
optimization,
&pipeline->shaders[pipeline->shader_count],
&pipeline->shader_names[pipeline->shader_count],
arena);
if (result == RT_SUCCESS) {
pipeline->compute_shader = pipeline->shader_count;
++pipeline->shader_count;
} else if (result == RT_SHADER_NOT_PRESENT) {
pipeline->compute_shader = UINT_MAX;
} else {
goto out;
}
result = RT_SUCCESS;
out:
return result;
}
static rt_result ParseEffect(rt_file_id fid,
const char *text,
size_t length,
rt_parsed_effect_data *effect,
rt_arena *arena) {
/* This is the grammar for pipeline files:
* <stmt-list> ::= <stmt>*
* <stmt> ::= <attribute> ( ( <value> ';' ) | ( '{' <stmt-list> '}' ) )
* <attribute> ::= [:alnum:]*
* <value>:: = [:alnum:]* */
const char *file_path = rtGetFilePath(fid);
rt_parse_state state;
unsigned int root_list;
rt_result result = rtParseDescription(text, length, file_path, &root_list, &state, arena);
if (result != RT_SUCCESS) {
return result;
}
memset(effect, 0, sizeof(*effect));
rt_shader_optimization_level optimization =
ParseOptimizationLevel(&state, root_list, file_path);
const rt_parsed_stmt *passes_stmt = rtFindStatement(&state, root_list, "passes");
if (!passes_stmt) {
rtLog("AC", "Did not find passes list in %s.", file_path);
return RT_INVALID_VALUE;
}
const rt_parsed_stmt_list *passes_list = &state.statement_lists[passes_stmt->list_index];
if (passes_list->count > RT_MAX_SUBRESOURCES) {
rtLog("AC",
"Too many passes in 'passes'. Maximum supported number is %u. In %s.",
RT_MAX_SUBRESOURCES,
file_path);
return RT_INVALID_VALUE;
}
effect->pass_count = passes_list->count;
const rt_parsed_stmt *pass_stmt = &state.statements[passes_list->first];
for (unsigned int i = 0; i < passes_list->count;
++i, pass_stmt = &state.statements[pass_stmt->next]) {
if (pass_stmt->form != RT_STMT_FORM_LIST) {
rtLog("AC",
"Expected a list as the value of passes.%*s in %s",
pass_stmt->attribute.length,
pass_stmt->attribute.start,
file_path);
return RT_INVALID_VALUE;
}
effect->pass_ids[i] =
rtCalculateRenderPassID(pass_stmt->attribute.start, pass_stmt->attribute.length);
result = ParsePipeline(&state,
pass_stmt->list_index,
file_path,
optimization,
&effect->pipelines[i],
arena);
if (result != RT_SUCCESS)
return result;
}
return result;
}
RT_ASSET_PROCESSOR_FN(EffectProcessor) {
rt_loaded_asset asset = LoadAsset(file);
if (!asset.buffer)
return RT_UNKNOWN_ERROR;
rt_parsed_effect_data effect;
memset(&effect, 0, sizeof(effect));
rt_result result = ParseEffect(file, asset.buffer, asset.size, &effect, arena);
if (result != RT_SUCCESS)
goto out;
rt_effect_info effect_info;
effect_info.pass_count = effect.pass_count;
rt_resource effect_resource = {0};
effect_resource.data = &effect_info;
effect_resource.type = RT_RESOURCE_EFFECT;
effect_resource.subresource_count = effect.pass_count;
*new_resource_count = 0;
const char *name = rtGetFilePath(file);
for (unsigned int i = 0; i < effect.pass_count; ++i) {
effect_info.passes[i].pass_id = effect.pass_ids[i];
effect_info.passes[i].pipeline = RT_INVALID_RESOURCE_ID;
rt_parsed_pipeline_data pipeline;
memcpy(&pipeline, &effect.pipelines[i], sizeof(pipeline));
rt_resource_id shader_resources[3] = {0};
result = rtCreateResources(pipeline.shader_count,
(const char **)pipeline.shader_names,
pipeline.shaders,
shader_resources);
if (result != RT_SUCCESS)
goto out;
rt_resource pipeline_resource = {0};
pipeline_resource.type = RT_RESOURCE_PIPELINE;
pipeline_resource.dependency_count = pipeline.shader_count;
memcpy(pipeline_resource.dependencies, shader_resources, sizeof(shader_resources));
pipeline_resource.subresource_count = 0;
size_t data_size = sizeof(rt_pipeline_info);
pipeline_resource.data = rtArenaPush(arena, data_size);
if (!pipeline_resource.data) {
result = RT_OUT_OF_MEMORY;
goto out;
}
rt_pipeline_info *info = pipeline_resource.data;
memset(info, 0, sizeof(*info));
info->vertex_shader = (pipeline.vertex_shader != UINT_MAX)
? shader_resources[pipeline.vertex_shader]
: RT_INVALID_RESOURCE_ID;
info->fragment_shader = (pipeline.fragment_shader != UINT_MAX)
? shader_resources[pipeline.fragment_shader]
: RT_INVALID_RESOURCE_ID;
info->compute_shader = (pipeline.compute_shader != UINT_MAX)
? shader_resources[pipeline.compute_shader]
: RT_INVALID_RESOURCE_ID;
rt_resource_id pipeline_id;
char pipeline_name[260];
rtSPrint(pipeline_name, sizeof(pipeline_name), "%s:%u", name, i);
char *ppln = &pipeline_name[0];
result = rtCreateResources(1, &ppln, &pipeline_resource, &pipeline_id);
if (result == RT_SUCCESS) {
new_resources[i] = pipeline_id;
memcpy(&new_resources[i + 1], shader_resources, sizeof(shader_resources));
*new_resource_count += 1 + pipeline.shader_count;
effect_resource.subresources[i] = pipeline_id;
effect_info.passes[i].pipeline = pipeline_id;
}
}
rt_resource_id effect_id = 0;
result = rtCreateResources(1, &name, &effect_resource, &effect_id);
if (result == RT_SUCCESS) {
new_resources[*new_resource_count] = effect_id;
*new_resource_count += 1;
}
out:
rtLog("AC", "Released %p", asset.buffer);
rtReleaseBuffer(asset.buffer, asset.size);
return result;
}