#include "description_parser.h" #include "processor.h" #if 0 #include "gfx/gfx.h" #include "runtime/buffer_manager.h" #include #include static int RenderTargetExists(const rt_framegraph_info *framegraph, rt_render_target_id id) { const rt_render_target_info *render_targets = rtResolveConstRelptr(&framegraph->render_targets); for (uint32_t i = 0; i < framegraph->render_target_count; ++i) { if (render_targets[i].id == id) return 1; } return 0; } static rt_result ParseFramegraph(const char *text, size_t size, const char *file_path, rt_framegraph_info **p_framegraph, rt_arena *arena) { rt_parse_state state; unsigned int root_list; rt_result result = rtParseDescription(text, size, file_path, &root_list, &state, arena); if (result != RT_SUCCESS) return result; /* Push on the arena to ensure a continous memory layout */ rt_framegraph_info *framegraph = RT_ARENA_PUSH_STRUCT_ZERO(arena, rt_framegraph_info); if (!framegraph) return RT_OUT_OF_MEMORY; /* ~~~~ Render Targets ~~~~ */ const rt_parsed_stmt *rt_list_stmt = rtFindStatement(&state, root_list, "render_targets"); if (!rt_list_stmt) { rtLog("AC", "Framegraph %s does not contain a list of render targets. (\"render_targets\")", file_path); return RT_INVALID_VALUE; } if (rt_list_stmt->form != RT_STMT_FORM_LIST) { rtLog("AC", "Expected a list as the value of \"render_targets\" in %s", file_path); return RT_INVALID_VALUE; } const rt_parsed_stmt_list *rt_list = &state.statement_lists[rt_list_stmt->list_index]; framegraph->render_target_count = rt_list->count; rt_render_target_info *render_targets = RT_ARENA_PUSH_ARRAY_ZERO(arena, rt_render_target_info, framegraph->render_target_count); if (!render_targets) return RT_OUT_OF_MEMORY; rtSetRelptr(&framegraph->render_targets, render_targets); const rt_parsed_stmt *rendertarget_stmt = &state.statements[rt_list->first]; for (uint32_t i = 0; i < rt_list->count; ++i, rendertarget_stmt = &state.statements[rendertarget_stmt->next]) { if (rendertarget_stmt->form != RT_STMT_FORM_LIST) { rtLog("AC", "Expected a list as the value of \"render_targets.%.*s\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } rt_render_target_info rt = {0}; rt.id = rtCalculateRenderTargetID(rendertarget_stmt->attribute.start, rendertarget_stmt->attribute.length); framegraph->names_size += rendertarget_stmt->attribute.length; const rt_parsed_stmt *width_stmt = rtFindStatement(&state, rendertarget_stmt->list_index, "width"); if (!width_stmt) { rtLog("AC", "Could not find \"render_targets.%.*s.width\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (width_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a simple value for \"render_targets.%.*s.width\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (rtCompareSpanToString(width_stmt->value, "SWAPCHAIN") == 0) rt.width = RT_RENDER_TARGET_SIZE_SWAPCHAIN; else sscanf(width_stmt->value.start, "%u", &rt.width); const rt_parsed_stmt *height_stmt = rtFindStatement(&state, rendertarget_stmt->list_index, "height"); if (!height_stmt) { rtLog("AC", "Could not find \"render_targets.%.*s.height\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (height_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a simple value for \"render_targets.%.*s.height\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (rtCompareSpanToString(height_stmt->value, "SWAPCHAIN") == 0) rt.height = RT_RENDER_TARGET_SIZE_SWAPCHAIN; else sscanf(height_stmt->value.start, "%u", &rt.height); const rt_parsed_stmt *samples_stmt = rtFindStatement(&state, rendertarget_stmt->list_index, "sample_count"); if (!samples_stmt) { rtLog("AC", "Could not find \"render_targets.%.*s.sample_count\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (samples_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a simple value for \"render_targets.%.*s.sample_count\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } sscanf(samples_stmt->value.start, "%u", &rt.sample_count); const rt_parsed_stmt *format_stmt = rtFindStatement(&state, rendertarget_stmt->list_index, "format"); if (!format_stmt) { rtLog("AC", "Could not find \"render_targets.%.*s.format\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (format_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a simple value for \"render_targets.%.*s.format\" in %s", rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (rtCompareSpanToString(format_stmt->value, "R8G8B8A8_UNORM") == 0) rt.format = RT_PIXEL_FORMAT_R8G8B8A8_UNORM; else if (rtCompareSpanToString(format_stmt->value, "B8G8R8A8_UNORM") == 0) rt.format = RT_PIXEL_FORMAT_B8G8R8A8_UNORM; else if (rtCompareSpanToString(format_stmt->value, "R8G8B8A8_SRGB") == 0) rt.format = RT_PIXEL_FORMAT_R8G8B8A8_SRGB; else if (rtCompareSpanToString(format_stmt->value, "B8G8R8A8_SRGB") == 0) rt.format = RT_PIXEL_FORMAT_B8G8R8A8_SRGB; else if (rtCompareSpanToString(format_stmt->value, "R8G8B8_UNORM") == 0) rt.format = RT_PIXEL_FORMAT_R8G8B8_UNORM; else if (rtCompareSpanToString(format_stmt->value, "B8G8R8_UNORM") == 0) rt.format = RT_PIXEL_FORMAT_B8G8R8_UNORM; else if (rtCompareSpanToString(format_stmt->value, "R8G8B8_SRGB") == 0) rt.format = RT_PIXEL_FORMAT_R8G8B8_SRGB; else if (rtCompareSpanToString(format_stmt->value, "B8G8R8_SRGB") == 0) rt.format = RT_PIXEL_FORMAT_B8G8R8_SRGB; else if (rtCompareSpanToString(format_stmt->value, "DEPTH24_STENCIL8") == 0) rt.format = RT_PIXEL_FORMAT_DEPTH24_STENCIL8; else if (rtCompareSpanToString(format_stmt->value, "DEPTH32") == 0) rt.format = RT_PIXEL_FORMAT_DEPTH32; else if (rtCompareSpanToString(format_stmt->value, "SWAPCHAIN") == 0) rt.format = RT_PIXEL_FORMAT_SWAPCHAIN; else { rt.format = RT_PIXEL_FORMAT_INVALID; rtLog("AC", "Invalid format value \"%.*s\" in \"render_targets.%.*s.format\" in %s", format_stmt->value.length, format_stmt->value.start, rendertarget_stmt->attribute.length, rendertarget_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } render_targets[i] = rt; } /* ~~~~ Render Passes ~~~~ */ /* First count the total number of reads and writes */ uint32_t total_reads = 0, total_writes = 0; const rt_parsed_stmt *pass_list_stmt = rtFindStatement(&state, root_list, "passes"); if (!pass_list_stmt) { rtLog("Framegraph %s does not contain a list of passes (\"passes\")", file_path); return RT_INVALID_VALUE; } if (pass_list_stmt->form != RT_STMT_FORM_LIST) { rtLog("AC", "Expected a list as the value of \"passes\" in %s", file_path); return RT_INVALID_VALUE; } const rt_parsed_stmt_list *pass_list = &state.statement_lists[pass_list_stmt->list_index]; rt_render_pass_info *passes = RT_ARENA_PUSH_ARRAY_ZERO(arena, rt_render_pass_info, pass_list->count); if (!passes) return RT_OUT_OF_MEMORY; rtSetRelptr(&framegraph->render_passes, passes); framegraph->render_pass_count = pass_list->count; const rt_parsed_stmt *pass_stmt = &state.statements[pass_list->first]; for (uint32_t i = 0; i < pass_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; } framegraph->names_size += pass_stmt->attribute.length + 1; passes[i].id = rtCalculateRenderPassID(pass_stmt->attribute.start, pass_stmt->attribute.length); passes[i].type = RT_RENDER_PASS_TYPE_GRAPHICS; const rt_parsed_stmt *type_stmt = rtFindStatement(&state, pass_stmt->list_index, "type"); if (type_stmt) { if (type_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected GRAPHICS or COMPUTE as the value of \"passes.%.*s.type\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (rtCompareSpanToString(type_stmt->value, "GRAPHICS") == 0) { passes[i].type = RT_RENDER_PASS_TYPE_GRAPHICS; } else if (rtCompareSpanToString(type_stmt->value, "COMPUTE") == 0) { passes[i].type = RT_RENDER_PASS_TYPE_COMPUTE; } else { rtLog("AC", "Expected GRAPHICS or COMPUTE as the value of \"passes.%.*s.type\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } } const rt_parsed_stmt *write_list_stmt = rtFindStatement(&state, pass_stmt->list_index, "writes"); if (write_list_stmt) { if (write_list_stmt->form != RT_STMT_FORM_LIST) { rtLog("AC", "Expected a list as the value of \"passes.%.*s.writes\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } const rt_parsed_stmt_list *write_list = &state.statement_lists[write_list_stmt->list_index]; passes[i].write_render_target_count = write_list->count; total_writes += write_list->count; } else { passes[i].write_render_target_count = 0; rtSetRelptr(&passes[i].write_render_targets, NULL); } const rt_parsed_stmt *read_list_stmt = rtFindStatement(&state, pass_stmt->list_index, "reads"); if (read_list_stmt) { if (read_list_stmt->form != RT_STMT_FORM_LIST) { rtLog("AC", "Expected a list as the value of \"passes.%.*s.reads\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } const rt_parsed_stmt_list *read_list = &state.statement_lists[read_list_stmt->list_index]; passes[i].read_render_target_count = read_list->count; total_reads += read_list->count; } else { passes[i].read_render_target_count = 0; rtSetRelptr(&passes[i].read_render_targets, NULL); } } /* Now fill the read & write lists */ rt_render_target_read *reads = RT_ARENA_PUSH_ARRAY_ZERO(arena, rt_render_target_read, total_reads); rt_render_target_write *writes = RT_ARENA_PUSH_ARRAY_ZERO(arena, rt_render_target_write, total_writes); if (!reads && total_reads != 0) return RT_OUT_OF_MEMORY; if (!writes && total_writes != 0) return RT_OUT_OF_MEMORY; uint32_t reads_at = 0; uint32_t writes_at = 0; pass_stmt = &state.statements[pass_list->first]; for (uint32_t i = 0; i < pass_list->count; ++i, pass_stmt = &state.statements[pass_stmt->next]) { RT_ASSERT(pass_stmt->form == RT_STMT_FORM_LIST, ""); const rt_parsed_stmt *write_list_stmt = rtFindStatement(&state, pass_stmt->list_index, "writes"); if (write_list_stmt) { RT_ASSERT(write_list_stmt->form == RT_STMT_FORM_LIST, ""); const rt_parsed_stmt_list *write_list = &state.statement_lists[write_list_stmt->list_index]; rtSetRelptr(&passes[i].write_render_targets, &writes[writes_at]); const rt_parsed_stmt *write_stmt = &state.statements[write_list->first]; for (uint32_t j = 0; j < write_list->count; ++j, write_stmt = &state.statements[write_stmt->next]) { if (write_stmt->form != RT_STMT_FORM_LIST) { rtLog("AC", "Expected a list as the value of \"passes.%.*s.writes.%.*s\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } rt_render_target_write *write = &writes[writes_at++]; memset(write, 0, sizeof(*write)); write->render_target = rtCalculateRenderTargetID(write_stmt->attribute.start, write_stmt->attribute.length); if (!RenderTargetExists(framegraph, write->render_target)) { rtLog("AC", "Referenced unknown render target \"%.s\" in \"passes.%.*s.writes.%.*s\" " "in %s", write_stmt->attribute.length, write_stmt->attribute.start, pass_stmt->attribute.length, pass_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } const rt_parsed_stmt *clear_value_stmt = rtFindStatement(&state, write_stmt->list_index, "clear_value"); if (clear_value_stmt) { if (clear_value_stmt->form != RT_STMT_FORM_LIST) { rtLog("AC", "Expected a list as the value of " "\"passes.%.*s.writes.%.*s.clear_value\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } const rt_parsed_stmt *r_stmt = rtFindStatement(&state, clear_value_stmt->list_index, "r"); if (r_stmt) { if (r_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a float as the value of " "\"passes.%.*s.writes.%.*s.clear_value.r\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } sscanf(r_stmt->value.start, "%f", &write->clear.color.r); } const rt_parsed_stmt *g_stmt = rtFindStatement(&state, clear_value_stmt->list_index, "g"); if (g_stmt) { if (g_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a float as the value of " "\"passes.%.*s.writes.%.*s.clear_value.g\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } sscanf(g_stmt->value.start, "%f", &write->clear.color.g); } const rt_parsed_stmt *b_stmt = rtFindStatement(&state, clear_value_stmt->list_index, "b"); if (b_stmt) { if (b_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a float as the value of " "\"passes.%.*s.writes.%.*s.clear_value.b\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } sscanf(b_stmt->value.start, "%f", &write->clear.color.b); } const rt_parsed_stmt *a_stmt = rtFindStatement(&state, clear_value_stmt->list_index, "a"); if (a_stmt) { if (a_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a float as the value of " "\"passes.%.*s.writes.%.*s.clear_value.a\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } sscanf(a_stmt->value.start, "%f", &write->clear.color.a); } const rt_parsed_stmt *d_stmt = rtFindStatement(&state, clear_value_stmt->list_index, "d"); if (d_stmt) { if (d_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a float as the value of " "\"passes.%.*s.writes.%.*s.clear_value.d\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } sscanf(d_stmt->value.start, "%f", &write->clear.depth_stencil.depth); } const rt_parsed_stmt *s_stmt = rtFindStatement(&state, clear_value_stmt->list_index, "s"); if (s_stmt) { if (s_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected an integer as the value of " "\"passes.%.*s.writes.%.*s.clear_value.s\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } sscanf(s_stmt->value.start, "%d", &write->clear.depth_stencil.stencil); } } const rt_parsed_stmt *clear_stmt = rtFindStatement(&state, write_stmt->list_index, "clear"); if (clear_stmt) { if (clear_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected a YES or NO as the value of " "\"passes.%.*s.writes.%.*s.clear\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (rtCompareSpanToString(clear_stmt->value, "YES") == 0) { write->flags |= RT_RENDER_TARGET_WRITE_CLEAR; } else if (rtCompareSpanToString(clear_stmt->value, "NO") != 0) { rtLog("AC", "Expected a YES or NO as the value of " "\"passes.%.*s.writes.%.*s.clear\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } } const rt_parsed_stmt *discard_stmt = rtFindStatement(&state, write_stmt->list_index, "discard"); if (discard_stmt) { if (discard_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected YES or NO as the value of " "\"passes.%.*s.writes.%.*s.clear\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (rtCompareSpanToString(discard_stmt->value, "YES") == 0) { write->flags |= RT_RENDER_TARGET_WRITE_DISCARD; } else if (rtCompareSpanToString(discard_stmt->value, "NO") != 0) { rtLog("AC", "Expected YES or NO as the value of " "\"passes.%.*s.writes.%.*s.discard\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, write_stmt->attribute.length, write_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } } } } const rt_parsed_stmt *read_list_stmt = rtFindStatement(&state, pass_stmt->list_index, "reads"); if (read_list_stmt) { RT_ASSERT(read_list_stmt->form == RT_STMT_FORM_LIST, ""); const rt_parsed_stmt_list *read_list = &state.statement_lists[read_list_stmt->list_index]; rtSetRelptr(&passes[i].read_render_targets, &reads[reads_at]); const rt_parsed_stmt *read_stmt = &state.statements[read_list->first]; for (uint32_t j = 0; j < read_list->count; ++j, read_stmt = &state.statements[read_stmt->next]) { if (read_stmt->form != RT_STMT_FORM_LIST) { rtLog("AC", "Expected a list as the value of \"passes.%.*s.reads.%.*s\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, read_stmt->attribute.length, read_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } rt_render_target_read *read = &reads[reads_at++]; memset(read, 0, sizeof(*read)); read->render_target = rtCalculateRenderTargetID(read_stmt->attribute.start, read_stmt->attribute.length); if (!RenderTargetExists(framegraph, read->render_target)) { rtLog("AC", "Referenced unknown render target \"%.s\" in \"passes.%.*s.reads.%.*s\" " "in %s", read_stmt->attribute.length, read_stmt->attribute.start, pass_stmt->attribute.length, pass_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } const rt_parsed_stmt *mode_stmt = rtFindStatement(&state, read_stmt->list_index, "mode"); if (mode_stmt) { if (mode_stmt->form != RT_STMT_FORM_VALUE) { rtLog("AC", "Expected SAMPLED or INPUT_ATTACHMENT as the value of " "\"passes.%.*s.writes.%.*s.mode\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, read_stmt->attribute.length, read_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } if (rtCompareSpanToString(mode_stmt->value, "SAMPLED") == 0) { read->mode = RT_RENDER_TARGET_READ_SAMPLED; } else if (rtCompareSpanToString(mode_stmt->value, "DIRECT") == 0) { read->mode = RT_RENDER_TARGET_READ_DIRECT; } else { rtLog("AC", "Expected SAMPLED or DIRECT as the value of " "\"passes.%.*s.writes.%.*s.mode\" in %s", pass_stmt->attribute.length, pass_stmt->attribute.start, read_stmt->attribute.length, read_stmt->attribute.start, file_path); return RT_INVALID_VALUE; } } } } } /* ~~~~ Names ~~~~ */ char *names = rtArenaPush(arena, framegraph->names_size); rtSetRelptr(&framegraph->names, names); char *names_at = names; pass_stmt = &state.statements[pass_list->first]; for (uint32_t i = 0; i < pass_list->count; ++i, pass_stmt = &state.statements[pass_stmt->next]) { if (names) { memcpy(names_at, pass_stmt->attribute.start, pass_stmt->attribute.length); names_at[pass_stmt->attribute.length] = '\0'; rtSetRelptr(&passes[i].name, names_at); passes[i].name_len = pass_stmt->attribute.length + 1; names_at += pass_stmt->attribute.length + 1; } else { rtSetRelptr(&passes[i].name, NULL); passes[i].name_len = 0; } } rendertarget_stmt = &state.statements[rt_list->first]; for (uint32_t i = 0; i < rt_list->count; ++i, rendertarget_stmt = &state.statements[rendertarget_stmt->next]) { if (names) { memcpy(names_at, rendertarget_stmt->attribute.start, rendertarget_stmt->attribute.length); names_at[rendertarget_stmt->attribute.length] = '\0'; rtSetRelptr(&render_targets[i].name, names_at); render_targets[i].name_len = rendertarget_stmt->attribute.length + 1; names_at += rendertarget_stmt->attribute.length + 1; } else { rtSetRelptr(&render_targets[i].name, NULL); render_targets[i].name_len = 0; } } *p_framegraph = framegraph; return result; } RT_ASSET_PROCESSOR_FN(FramegraphProcessor) { rt_loaded_asset asset = LoadAsset(file); if (!asset.buffer) return RT_UNKNOWN_ERROR; rt_framegraph_info *framegraph = NULL; rt_result result = ParseFramegraph(asset.buffer, asset.size, rtGetFilePath(file), &framegraph, arena); if (result != RT_SUCCESS) goto out; rt_resource resource = {0}; resource.type = RT_RESOURCE_FRAMEGRAPH; resource.data = framegraph; rt_resource_id id; const char *name = rtGetFilePath(file); result = rtCreateResources(1, &name, &resource, &id); if (result != RT_SUCCESS) goto out; new_resources[0] = id; *new_resource_count = 1; out: rtReleaseBuffer(asset.buffer, asset.size); return result; } #endif