1109 lines
34 KiB
C
1109 lines
34 KiB
C
#include "vk_render.h"
|
|
|
|
#include "vk_core.h"
|
|
#include "vk_buffer.h"
|
|
#include "vk_geometry.h"
|
|
#include "vk_const.h"
|
|
#include "vk_common.h"
|
|
#include "vk_cvar.h"
|
|
#include "vk_pipeline.h"
|
|
#include "vk_textures.h"
|
|
#include "vk_math.h"
|
|
#include "vk_rtx.h"
|
|
#include "vk_descriptor.h"
|
|
#include "alolcator.h"
|
|
#include "profiler.h"
|
|
#include "r_speeds.h"
|
|
#include "camera.h"
|
|
|
|
#include "eiface.h"
|
|
#include "xash3d_mathlib.h"
|
|
#include "protocol.h" // MAX_DLIGHTS
|
|
#include "xash3d_types.h"
|
|
|
|
#include <memory.h>
|
|
|
|
#define MODULE_NAME "render"
|
|
|
|
#define MAX_UNIFORM_SLOTS (MAX_SCENE_ENTITIES * 2 /* solid + trans */ + 1)
|
|
|
|
#define PROFILER_SCOPES(X) \
|
|
X(renderbegin, "VK_RenderBegin"); \
|
|
|
|
#define SCOPE_DECLARE(scope, name) APROF_SCOPE_DECLARE(scope)
|
|
PROFILER_SCOPES(SCOPE_DECLARE)
|
|
#undef SCOPE_DECLARE
|
|
|
|
typedef struct {
|
|
matrix4x4 mvp;
|
|
vec4_t color;
|
|
} uniform_data_t;
|
|
|
|
typedef struct {
|
|
matrix4x4 mvp;
|
|
matrix4x4 inv_proj;
|
|
matrix4x4 inv_view;
|
|
vec2_t resolution;
|
|
float pad_[2];
|
|
} sky_uniform_data_t;
|
|
|
|
enum {
|
|
// These correspond to kVkRenderType*
|
|
kVkPipeline_Solid, // no blending, depth RW
|
|
kVkPipeline_A_1mA_RW, // blend: src*a + dst*(1-a), depth: RW
|
|
kVkPipeline_A_1mA_R, // blend: src*a + dst*(1-a), depth test
|
|
kVkPipeline_A_1, // blend: src*a + dst, no depth test or write
|
|
kVkPipeline_A_1_R, // blend: src*a + dst, depth test
|
|
kVkPipeline_AT, // no blend, depth RW, alpha test
|
|
kVkPipeline_1_1_R, // blend: src + dst, depth test
|
|
|
|
// Special pipeline for skybox (tex = TEX_BASE_SKYBOX)
|
|
//kVkPipeline_Sky,
|
|
kVkPipeline_COUNT,
|
|
};
|
|
|
|
typedef struct {
|
|
VkPipeline pipeline;
|
|
#define MAX_CONCURRENT_FRAMES 2
|
|
VkDescriptorSet sets[MAX_CONCURRENT_FRAMES];
|
|
VkDescriptorSetLayoutBinding bindings[2];
|
|
vk_descriptor_value_t values[2];
|
|
vk_descriptors_t descs;
|
|
} r_pipeline_sky_t;
|
|
|
|
static struct {
|
|
VkPipelineLayout pipeline_layout;
|
|
VkPipeline pipelines[kVkPipeline_COUNT];
|
|
|
|
r_pipeline_sky_t pipeline_sky;
|
|
|
|
vk_buffer_t uniform_buffer;
|
|
uint32_t ubo_align;
|
|
|
|
cvar_t *use_material_textures;
|
|
|
|
struct {
|
|
int dynamic_model_count;
|
|
int models_count;
|
|
} stats;
|
|
} g_render;
|
|
|
|
static qboolean createPipeline( VkPipeline* out, const char *name, const vk_pipeline_graphics_create_info_t *ci ) {
|
|
*out = VK_PipelineGraphicsCreate(ci);
|
|
|
|
if (*out == VK_NULL_HANDLE)
|
|
{
|
|
gEngine.Con_Printf(S_ERROR "Cannot create render pipeline \"%s\"\n", name);
|
|
return false;
|
|
}
|
|
|
|
if (vk_core.debug)
|
|
{
|
|
VkDebugUtilsObjectNameInfoEXT debug_name = {
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
.objectHandle = (uint64_t)*out,
|
|
.objectType = VK_OBJECT_TYPE_PIPELINE,
|
|
.pObjectName = name,
|
|
};
|
|
XVK_CHECK(vkSetDebugUtilsObjectNameEXT(vk_core.device, &debug_name));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static qboolean createSkyboxPipeline( void ) {
|
|
const vk_shader_stage_t sky_shaders[] = {
|
|
{
|
|
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
|
.filename = "sky.vert.spv",
|
|
.specialization_info = NULL,
|
|
}, {
|
|
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
.filename = "sky.frag.spv",
|
|
.specialization_info = NULL,
|
|
}};
|
|
|
|
const VkVertexInputAttributeDescription attribs[] = {
|
|
{.binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, pos)},
|
|
};
|
|
|
|
g_render.pipeline_sky.bindings[0] = (VkDescriptorSetLayoutBinding){
|
|
.binding = 0,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
|
.descriptorCount = 1,
|
|
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
.pImmutableSamplers = NULL,
|
|
};
|
|
g_render.pipeline_sky.bindings[1] = (VkDescriptorSetLayoutBinding) {
|
|
.binding = 1,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
.descriptorCount = 1,
|
|
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
.pImmutableSamplers = NULL,
|
|
};
|
|
|
|
g_render.pipeline_sky.descs = (vk_descriptors_t){
|
|
.num_bindings = COUNTOF(g_render.pipeline_sky.bindings),
|
|
.bindings = g_render.pipeline_sky.bindings,
|
|
|
|
.values = g_render.pipeline_sky.values,
|
|
|
|
.push_constants = (VkPushConstantRange){0},
|
|
|
|
.num_sets = COUNTOF(g_render.pipeline_sky.sets),
|
|
.desc_sets = g_render.pipeline_sky.sets,
|
|
};
|
|
|
|
VK_DescriptorsCreate(&g_render.pipeline_sky.descs);
|
|
|
|
vk_pipeline_graphics_create_info_t ci = {
|
|
.layout = g_render.pipeline_sky.descs.pipeline_layout,
|
|
|
|
.attribs = attribs,
|
|
.num_attribs = ARRAYSIZE(attribs),
|
|
|
|
.stages = sky_shaders,
|
|
.num_stages = ARRAYSIZE(sky_shaders),
|
|
|
|
.vertex_stride = sizeof(vk_vertex_t),
|
|
|
|
.depthTestEnable = VK_TRUE,
|
|
.depthWriteEnable = VK_TRUE,
|
|
.depthCompareOp = VK_COMPARE_OP_LESS,
|
|
|
|
.blendEnable = VK_FALSE,
|
|
|
|
.cullMode = VK_CULL_MODE_FRONT_BIT,
|
|
};
|
|
|
|
return createPipeline(&g_render.pipeline_sky.pipeline, "sky", &ci);
|
|
}
|
|
|
|
static qboolean createPipelines( void )
|
|
{
|
|
/* VkPushConstantRange push_const = { */
|
|
/* .offset = 0, */
|
|
/* .size = sizeof(AVec3f), */
|
|
/* .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, */
|
|
/* }; */
|
|
|
|
VkDescriptorSetLayout descriptor_layouts[] = {
|
|
vk_desc_fixme.one_uniform_buffer_layout,
|
|
vk_desc_fixme.one_texture_layout,
|
|
vk_desc_fixme.one_texture_layout,
|
|
vk_desc_fixme.one_uniform_buffer_layout,
|
|
};
|
|
|
|
VkPipelineLayoutCreateInfo plci = {
|
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
|
.setLayoutCount = ARRAYSIZE(descriptor_layouts),
|
|
.pSetLayouts = descriptor_layouts,
|
|
/* .pushConstantRangeCount = 1, */
|
|
/* .pPushConstantRanges = &push_const, */
|
|
};
|
|
|
|
// FIXME store layout separately
|
|
XVK_CHECK(vkCreatePipelineLayout(vk_core.device, &plci, NULL, &g_render.pipeline_layout));
|
|
|
|
{
|
|
struct ShaderSpec {
|
|
float alpha_test_threshold;
|
|
uint32_t max_dlights;
|
|
} spec_data = { .25f, MAX_DLIGHTS };
|
|
const VkSpecializationMapEntry spec_map[] = {
|
|
{.constantID = 0, .offset = offsetof(struct ShaderSpec, alpha_test_threshold), .size = sizeof(float) },
|
|
{.constantID = 1, .offset = offsetof(struct ShaderSpec, max_dlights), .size = sizeof(uint32_t) },
|
|
};
|
|
|
|
VkSpecializationInfo shader_spec = {
|
|
.mapEntryCount = ARRAYSIZE(spec_map),
|
|
.pMapEntries = spec_map,
|
|
.dataSize = sizeof(struct ShaderSpec),
|
|
.pData = &spec_data
|
|
};
|
|
|
|
const VkVertexInputAttributeDescription attribs[] = {
|
|
{.binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, pos)},
|
|
{.binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, normal)},
|
|
{.binding = 0, .location = 2, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(vk_vertex_t, gl_tc)},
|
|
{.binding = 0, .location = 3, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(vk_vertex_t, lm_tc)},
|
|
{.binding = 0, .location = 4, .format = VK_FORMAT_R8G8B8A8_UNORM, .offset = offsetof(vk_vertex_t, color)},
|
|
// Not used {.binding = 0, .location = 6, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vk_vertex_t, prev_pos)},
|
|
};
|
|
|
|
const vk_shader_stage_t shader_stages[] = {
|
|
{
|
|
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
|
.filename = "brush.vert.spv",
|
|
.specialization_info = NULL,
|
|
}, {
|
|
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
.filename = "brush.frag.spv",
|
|
.specialization_info = &shader_spec,
|
|
}};
|
|
|
|
vk_pipeline_graphics_create_info_t ci = {
|
|
.layout = g_render.pipeline_layout,
|
|
.attribs = attribs,
|
|
.num_attribs = ARRAYSIZE(attribs),
|
|
|
|
.stages = shader_stages,
|
|
.num_stages = ARRAYSIZE(shader_stages),
|
|
|
|
.vertex_stride = sizeof(vk_vertex_t),
|
|
|
|
.depthTestEnable = VK_TRUE,
|
|
.depthWriteEnable = VK_TRUE,
|
|
.depthCompareOp = VK_COMPARE_OP_LESS,
|
|
|
|
.blendEnable = VK_FALSE,
|
|
|
|
.cullMode = VK_CULL_MODE_FRONT_BIT,
|
|
};
|
|
|
|
{
|
|
spec_data.alpha_test_threshold = 0.f;
|
|
ci.blendEnable = VK_FALSE;
|
|
ci.depthWriteEnable = VK_TRUE;
|
|
ci.depthTestEnable = VK_TRUE;
|
|
if (!createPipeline(g_render.pipelines + kVkPipeline_Solid, "solid", &ci))
|
|
return false;
|
|
}
|
|
|
|
{
|
|
spec_data.alpha_test_threshold = 0.f;
|
|
ci.depthWriteEnable = VK_TRUE;
|
|
ci.depthTestEnable = VK_TRUE;
|
|
ci.blendEnable = VK_TRUE;
|
|
ci.colorBlendOp = VK_BLEND_OP_ADD;
|
|
ci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
|
ci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
if (!createPipeline(g_render.pipelines + kVkPipeline_A_1mA_RW, "A_1ma_RW", &ci))
|
|
return false;
|
|
}
|
|
|
|
{
|
|
spec_data.alpha_test_threshold = 0.f;
|
|
ci.depthWriteEnable = VK_FALSE;
|
|
ci.depthTestEnable = VK_TRUE;
|
|
ci.blendEnable = VK_TRUE;
|
|
ci.colorBlendOp = VK_BLEND_OP_ADD;
|
|
ci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
|
ci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
if (!createPipeline(g_render.pipelines + kVkPipeline_A_1mA_R, "A_1ma_R", &ci))
|
|
return false;
|
|
}
|
|
|
|
{
|
|
spec_data.alpha_test_threshold = 0.f;
|
|
ci.depthWriteEnable = VK_FALSE;
|
|
ci.depthTestEnable = VK_FALSE; // Fake bloom, should be over geometry too
|
|
ci.blendEnable = VK_TRUE;
|
|
ci.colorBlendOp = VK_BLEND_OP_ADD;
|
|
ci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
|
ci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
if (!createPipeline(g_render.pipelines + kVkPipeline_A_1, "A_1", &ci))
|
|
return false;
|
|
}
|
|
|
|
{
|
|
spec_data.alpha_test_threshold = 0.f;
|
|
ci.depthWriteEnable = VK_FALSE;
|
|
ci.depthTestEnable = VK_TRUE;
|
|
ci.blendEnable = VK_TRUE;
|
|
ci.colorBlendOp = VK_BLEND_OP_ADD;
|
|
ci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
|
ci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
if (!createPipeline(g_render.pipelines + kVkPipeline_A_1_R, "A_1_R", &ci))
|
|
return false;
|
|
}
|
|
|
|
{
|
|
spec_data.alpha_test_threshold = .25f;
|
|
ci.depthWriteEnable = VK_TRUE;
|
|
ci.depthTestEnable = VK_TRUE;
|
|
ci.blendEnable = VK_FALSE;
|
|
if (!createPipeline(g_render.pipelines + kVkPipeline_AT, "AT", &ci))
|
|
return false;
|
|
}
|
|
|
|
{
|
|
spec_data.alpha_test_threshold = 0.f;
|
|
ci.depthWriteEnable = VK_FALSE;
|
|
ci.depthTestEnable = VK_TRUE;
|
|
ci.blendEnable = VK_TRUE;
|
|
ci.colorBlendOp = VK_BLEND_OP_ADD;
|
|
ci.srcAlphaBlendFactor = ci.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
ci.dstAlphaBlendFactor = ci.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
|
if (!createPipeline(g_render.pipelines + kVkPipeline_1_1_R, "1_1_R", &ci))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!createSkyboxPipeline())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
typedef struct {
|
|
uint32_t num_lights;
|
|
uint32_t debug_r_lightmap;
|
|
uint32_t padding_[2];
|
|
struct {
|
|
vec4_t pos_r;
|
|
vec4_t color;
|
|
} light[MAX_DLIGHTS];
|
|
} vk_ubo_lights_t;
|
|
|
|
#define MAX_DRAW_COMMANDS 8192 // TODO estimate
|
|
#define MAX_DEBUG_NAME_LENGTH 32
|
|
|
|
typedef struct render_draw_s {
|
|
uint32_t ubo_offset; // FIXME move this to draw
|
|
int lightmap, texture;
|
|
int pipeline_index;
|
|
uint32_t element_count;
|
|
uint32_t index_offset, vertex_offset;
|
|
} render_draw_t;
|
|
|
|
typedef struct render_draw_sky_s {
|
|
uint32_t element_count;
|
|
uint32_t index_offset, vertex_offset;
|
|
// TODO matrix4x4 model;
|
|
} render_draw_sky_t;
|
|
|
|
enum draw_command_type_e {
|
|
DrawLabelBegin,
|
|
DrawLabelEnd,
|
|
DrawDraw,
|
|
DrawSky,
|
|
};
|
|
|
|
typedef struct {
|
|
enum draw_command_type_e type;
|
|
union {
|
|
char debug_label[MAX_DEBUG_NAME_LENGTH];
|
|
render_draw_t draw;
|
|
render_draw_sky_t draw_sky;
|
|
};
|
|
} draw_command_t;
|
|
|
|
static struct {
|
|
int uniform_data_set_mask;
|
|
uniform_data_t current_uniform_data;
|
|
uniform_data_t dirty_uniform_data;
|
|
|
|
r_flipping_buffer_t uniform_alloc;
|
|
uint32_t current_ubo_offset_FIXME;
|
|
|
|
draw_command_t draw_commands[MAX_DRAW_COMMANDS];
|
|
int num_draw_commands;
|
|
|
|
matrix4x4 vk_projection;
|
|
matrix4x4 projection_view;
|
|
|
|
qboolean current_frame_is_ray_traced;
|
|
} g_render_state;
|
|
|
|
qboolean VK_RenderInit( void ) {
|
|
PROFILER_SCOPES(APROF_SCOPE_INIT);
|
|
|
|
g_render.use_material_textures = gEngine.Cvar_Get( "vk_use_material_textures", "0", FCVAR_GLCONFIG, "Use PBR material textures for traditional rendering too" );
|
|
|
|
g_render.ubo_align = Q_max(4, vk_core.physical_device.properties.limits.minUniformBufferOffsetAlignment);
|
|
|
|
const uint32_t uniform_unit_size = ((sizeof(uniform_data_t) + g_render.ubo_align - 1) / g_render.ubo_align) * g_render.ubo_align;
|
|
const uint32_t uniform_buffer_size = uniform_unit_size * MAX_UNIFORM_SLOTS;
|
|
R_FlippingBuffer_Init(&g_render_state.uniform_alloc, uniform_buffer_size);
|
|
|
|
if (!VK_BufferCreate("render uniform_buffer", &g_render.uniform_buffer, uniform_buffer_size,
|
|
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | (vk_core.rtx ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT : 0)))
|
|
return false;
|
|
|
|
{
|
|
VkDescriptorBufferInfo dbi_uniform_data = {
|
|
.buffer = g_render.uniform_buffer.buffer,
|
|
.offset = 0,
|
|
.range = sizeof(uniform_data_t),
|
|
};
|
|
VkDescriptorBufferInfo dbi_uniform_lights = {
|
|
.buffer = g_render.uniform_buffer.buffer,
|
|
.offset = 0,
|
|
.range = sizeof(vk_ubo_lights_t),
|
|
};
|
|
VkWriteDescriptorSet wds[] = {{
|
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
.dstBinding = 0,
|
|
.dstArrayElement = 0,
|
|
.descriptorCount = 1,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
|
.pBufferInfo = &dbi_uniform_data,
|
|
.dstSet = vk_desc_fixme.ubo_sets[0], // FIXME
|
|
}, {
|
|
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
|
.dstBinding = 0,
|
|
.dstArrayElement = 0,
|
|
.descriptorCount = 1,
|
|
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
|
.pBufferInfo = &dbi_uniform_lights,
|
|
.dstSet = vk_desc_fixme.ubo_sets[1], // FIXME
|
|
}};
|
|
vkUpdateDescriptorSets(vk_core.device, ARRAYSIZE(wds), wds, 0, NULL);
|
|
}
|
|
|
|
if (!createPipelines())
|
|
return false;
|
|
|
|
R_SPEEDS_COUNTER(g_render.stats.dynamic_model_count, "models_dynamic", kSpeedsMetricCount);
|
|
R_SPEEDS_COUNTER(g_render.stats.models_count, "models", kSpeedsMetricCount);
|
|
return true;
|
|
}
|
|
|
|
void VK_RenderShutdown( void )
|
|
{
|
|
for (int i = 0; i < ARRAYSIZE(g_render.pipelines); ++i)
|
|
vkDestroyPipeline(vk_core.device, g_render.pipelines[i], NULL);
|
|
vkDestroyPipelineLayout( vk_core.device, g_render.pipeline_layout, NULL );
|
|
|
|
vkDestroyPipeline(vk_core.device, g_render.pipeline_sky.pipeline, NULL);
|
|
VK_DescriptorsDestroy(&g_render.pipeline_sky.descs);
|
|
|
|
VK_BufferDestroy( &g_render.uniform_buffer );
|
|
}
|
|
|
|
enum {
|
|
UNIFORM_UNSET = 0,
|
|
UNIFORM_UPLOADED = 16,
|
|
};
|
|
|
|
void VK_RenderBegin( qboolean ray_tracing ) {
|
|
APROF_SCOPE_BEGIN(renderbegin);
|
|
|
|
g_render_state.uniform_data_set_mask = UNIFORM_UNSET;
|
|
g_render_state.current_ubo_offset_FIXME = UINT32_MAX;
|
|
memset(&g_render_state.current_uniform_data, 0, sizeof(g_render_state.current_uniform_data));
|
|
memset(&g_render_state.dirty_uniform_data, 0, sizeof(g_render_state.dirty_uniform_data));
|
|
R_FlippingBuffer_Flip(&g_render_state.uniform_alloc);
|
|
|
|
g_render_state.num_draw_commands = 0;
|
|
g_render_state.current_frame_is_ray_traced = ray_tracing;
|
|
|
|
R_GeometryBuffer_Flip();
|
|
|
|
if (ray_tracing)
|
|
VK_RayFrameBegin();
|
|
|
|
APROF_SCOPE_END(renderbegin);
|
|
}
|
|
|
|
// Vulkan has Y pointing down, and z should end up in (0, 1)
|
|
// NOTE this matrix is row-major
|
|
static const matrix4x4 vk_proj_fixup = {
|
|
{1, 0, 0, 0},
|
|
{0, -1, 0, 0},
|
|
{0, 0, .5, .5},
|
|
{0, 0, 0, 1}
|
|
};
|
|
|
|
void VK_RenderSetupCamera( const struct ref_viewpass_s *rvp ) {
|
|
R_SetupCamera(rvp);
|
|
Matrix4x4_Concat(g_render_state.vk_projection, vk_proj_fixup, g_camera.projectionMatrix);
|
|
Matrix4x4_Concat(g_render_state.projection_view, g_render_state.vk_projection, g_camera.viewMatrix);
|
|
}
|
|
|
|
static uint32_t allocUniform( uint32_t size, uint32_t alignment ) {
|
|
// FIXME Q_max is not correct, we need NAIMENSCHEEE OBSCHEEE KRATNOE
|
|
const uint32_t align = Q_max(alignment, g_render.ubo_align);
|
|
const uint32_t offset = R_FlippingBuffer_Alloc(&g_render_state.uniform_alloc, size, align);
|
|
return offset;
|
|
}
|
|
|
|
static draw_command_t *drawCmdAlloc( void ) {
|
|
ASSERT(g_render_state.num_draw_commands < ARRAYSIZE(g_render_state.draw_commands));
|
|
return g_render_state.draw_commands + (g_render_state.num_draw_commands++);
|
|
}
|
|
|
|
static void drawCmdPushDebugLabelBegin( const char *debug_label ) {
|
|
if (vk_core.debug) {
|
|
draw_command_t *draw_command = drawCmdAlloc();
|
|
draw_command->type = DrawLabelBegin;
|
|
Q_strncpy(draw_command->debug_label, debug_label, sizeof draw_command->debug_label);
|
|
}
|
|
}
|
|
|
|
static void drawCmdPushDebugLabelEnd( void ) {
|
|
if (vk_core.debug) {
|
|
draw_command_t *draw_command = drawCmdAlloc();
|
|
draw_command->type = DrawLabelEnd;
|
|
}
|
|
}
|
|
|
|
// FIXME get rid of this garbage
|
|
static uint32_t getUboOffset_FIXME( void ) {
|
|
// Figure out whether we need to update UBO data, and upload new data if we do
|
|
// TODO generally it's not safe to do memcmp for structures comparison
|
|
if (g_render_state.current_ubo_offset_FIXME == UINT32_MAX
|
|
|| ((g_render_state.uniform_data_set_mask & UNIFORM_UPLOADED) == 0)
|
|
|| memcmp(&g_render_state.current_uniform_data, &g_render_state.dirty_uniform_data, sizeof(g_render_state.current_uniform_data)) != 0) {
|
|
g_render_state.current_ubo_offset_FIXME = allocUniform(sizeof(uniform_data_t), 16 /* why 16? vec4? */);
|
|
|
|
if (g_render_state.current_ubo_offset_FIXME == ALO_ALLOC_FAILED)
|
|
return UINT32_MAX;
|
|
|
|
uniform_data_t *const ubo = (uniform_data_t*)((byte*)g_render.uniform_buffer.mapped + g_render_state.current_ubo_offset_FIXME);
|
|
memcpy(&g_render_state.current_uniform_data, &g_render_state.dirty_uniform_data, sizeof(g_render_state.dirty_uniform_data));
|
|
memcpy(ubo, &g_render_state.current_uniform_data, sizeof(*ubo));
|
|
g_render_state.uniform_data_set_mask |= UNIFORM_UPLOADED;
|
|
}
|
|
|
|
return g_render_state.current_ubo_offset_FIXME;
|
|
}
|
|
|
|
static void drawCmdPushDraw( const render_draw_t *draw )
|
|
{
|
|
draw_command_t *draw_command;
|
|
|
|
ASSERT(draw->pipeline_index >= 0);
|
|
ASSERT(draw->pipeline_index < ARRAYSIZE(g_render.pipelines));
|
|
ASSERT(draw->lightmap >= 0);
|
|
ASSERT(draw->texture >= 0);
|
|
ASSERT(draw->texture < MAX_TEXTURES);
|
|
|
|
if (g_render_state.num_draw_commands >= ARRAYSIZE(g_render_state.draw_commands)) {
|
|
gEngine.Con_Printf( S_ERROR "Maximum number of draw commands reached\n" );
|
|
return;
|
|
}
|
|
|
|
const uint32_t ubo_offset = getUboOffset_FIXME();
|
|
if (ubo_offset == ALO_ALLOC_FAILED) {
|
|
// TODO stagger this
|
|
gEngine.Con_Printf( S_ERROR "Ran out of uniform slots\n" );
|
|
return;
|
|
}
|
|
|
|
draw_command = drawCmdAlloc();
|
|
draw_command->draw = *draw;
|
|
draw_command->draw.ubo_offset = ubo_offset;
|
|
draw_command->type = DrawDraw;
|
|
}
|
|
|
|
static void drawCmdPushDrawSky( const render_draw_sky_t *draw_sky )
|
|
{
|
|
draw_command_t *draw_command;
|
|
|
|
if (g_render_state.num_draw_commands >= ARRAYSIZE(g_render_state.draw_commands)) {
|
|
gEngine.Con_Printf( S_ERROR "Maximum number of draw commands reached\n" );
|
|
return;
|
|
}
|
|
|
|
draw_command = drawCmdAlloc();
|
|
draw_command->draw_sky = *draw_sky;
|
|
draw_command->type = DrawSky;
|
|
}
|
|
|
|
// Return offset of dlights data into UBO buffer
|
|
static uint32_t writeDlightsToUBO( void )
|
|
{
|
|
vk_ubo_lights_t* ubo_lights;
|
|
int num_lights = 0;
|
|
const uint32_t ubo_lights_offset = allocUniform(sizeof(*ubo_lights), 4);
|
|
if (ubo_lights_offset == UINT32_MAX) {
|
|
gEngine.Con_Printf(S_ERROR "Cannot allocate UBO for DLights\n");
|
|
return UINT32_MAX;
|
|
}
|
|
ubo_lights = (vk_ubo_lights_t*)((byte*)(g_render.uniform_buffer.mapped) + ubo_lights_offset);
|
|
|
|
// TODO this should not be here (where? vk_scene?)
|
|
for (int i = 0; i < MAX_DLIGHTS && num_lights < ARRAYSIZE(ubo_lights->light); ++i) {
|
|
const dlight_t *l = gEngine.GetDynamicLight(i);
|
|
if( !l || l->die < gpGlobals->time || !l->radius )
|
|
continue;
|
|
Vector4Set(
|
|
ubo_lights->light[num_lights].color,
|
|
l->color.r / 255.f,
|
|
l->color.g / 255.f,
|
|
l->color.b / 255.f,
|
|
1.f);
|
|
Vector4Set(
|
|
ubo_lights->light[num_lights].pos_r,
|
|
l->origin[0],
|
|
l->origin[1],
|
|
l->origin[2],
|
|
l->radius);
|
|
|
|
num_lights++;
|
|
}
|
|
|
|
ubo_lights->num_lights = num_lights;
|
|
ubo_lights->debug_r_lightmap = r_lightmap->value != 0;
|
|
return ubo_lights_offset;
|
|
}
|
|
|
|
/*
|
|
static void debugBarrier( VkCommandBuffer cmdbuf, VkBuffer buf) {
|
|
const VkBufferMemoryBarrier bmb[] = { {
|
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
|
.buffer = buf,
|
|
.offset = 0,
|
|
.size = VK_WHOLE_SIZE,
|
|
} };
|
|
vkCmdPipelineBarrier(cmdbuf,
|
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
|
0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL);
|
|
}
|
|
*/
|
|
|
|
void VK_Render_FIXME_Barrier( VkCommandBuffer cmdbuf ) {
|
|
const VkBuffer geom_buffer = R_GeometryBuffer_Get();
|
|
//debugBarrier(cmdbuf, geom_buffer);
|
|
// FIXME: this should be automatic and dynamically depend on actual usage, resolving this with render graph
|
|
{
|
|
const VkBufferMemoryBarrier bmb[] = { {
|
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
.dstAccessMask
|
|
= VK_ACCESS_INDEX_READ_BIT
|
|
| VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT
|
|
| (vk_core.rtx ? ( VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT) : 0),
|
|
.buffer = geom_buffer,
|
|
.offset = 0,
|
|
.size = VK_WHOLE_SIZE,
|
|
} };
|
|
vkCmdPipelineBarrier(cmdbuf,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | (vk_core.rtx
|
|
? VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR
|
|
| VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR
|
|
| VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
|
|
: 0),
|
|
0, 0, NULL, ARRAYSIZE(bmb), bmb, 0, NULL);
|
|
}
|
|
}
|
|
|
|
void VK_RenderEnd( VkCommandBuffer cmdbuf, qboolean draw, uint32_t width, uint32_t height, int frame_index )
|
|
{
|
|
if (!draw)
|
|
return;
|
|
|
|
// TODO we can sort collected draw commands for more efficient and correct rendering
|
|
// that requires adding info about distance to camera for correct order-dependent blending
|
|
|
|
struct {
|
|
VkPipeline pipeline;
|
|
int texture;
|
|
int lightmap;
|
|
uint32_t ubo_offset;
|
|
} cur = {
|
|
.pipeline = VK_NULL_HANDLE,
|
|
.texture = -1,
|
|
.lightmap = -1,
|
|
.ubo_offset = -1,
|
|
};
|
|
|
|
const uint32_t dlights_ubo_offset = writeDlightsToUBO();
|
|
if (dlights_ubo_offset == UINT32_MAX)
|
|
return;
|
|
|
|
ASSERT(!g_render_state.current_frame_is_ray_traced);
|
|
|
|
{
|
|
const VkBuffer geom_buffer = R_GeometryBuffer_Get();
|
|
const VkDeviceSize offset = 0;
|
|
vkCmdBindVertexBuffers(cmdbuf, 0, 1, &geom_buffer, &offset);
|
|
vkCmdBindIndexBuffer(cmdbuf, geom_buffer, 0, VK_INDEX_TYPE_UINT16);
|
|
}
|
|
|
|
for (int i = 0; i < g_render_state.num_draw_commands; ++i) {
|
|
const draw_command_t *const draw = g_render_state.draw_commands + i;
|
|
|
|
switch (draw->type) {
|
|
case DrawLabelBegin:
|
|
{
|
|
const VkDebugUtilsLabelEXT label = {
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
|
.pLabelName = draw->debug_label,
|
|
};
|
|
vkCmdBeginDebugUtilsLabelEXT(cmdbuf, &label);
|
|
continue;
|
|
}
|
|
|
|
case DrawLabelEnd:
|
|
vkCmdEndDebugUtilsLabelEXT(cmdbuf);
|
|
continue;
|
|
|
|
case DrawSky:
|
|
{
|
|
const render_draw_sky_t *draw_sky = &draw->draw_sky;
|
|
|
|
if (cur.pipeline != g_render.pipeline_sky.pipeline) {
|
|
const uint32_t ubo_offset = allocUniform(sizeof(sky_uniform_data_t), 16 /*?*/);
|
|
if (g_render_state.current_ubo_offset_FIXME == ALO_ALLOC_FAILED)
|
|
continue;
|
|
|
|
// Compute and upload UBO stuff
|
|
{
|
|
sky_uniform_data_t* const sky_ubo = (sky_uniform_data_t*)((byte*)g_render.uniform_buffer.mapped + ubo_offset);
|
|
|
|
// FIXME model matrix
|
|
Matrix4x4_ToArrayFloatGL(g_render_state.projection_view, (float*)sky_ubo->mvp);
|
|
|
|
sky_ubo->resolution[0] = width;
|
|
sky_ubo->resolution[1] = height;
|
|
|
|
// TODO DRY, this is copypasted from vk_rtx.c
|
|
matrix4x4 proj_inv, view_inv;
|
|
Matrix4x4_Invert_Full(proj_inv, g_render_state.vk_projection);
|
|
Matrix4x4_ToArrayFloatGL(proj_inv, (float*)sky_ubo->inv_proj);
|
|
|
|
// TODO there's a more efficient way to construct an inverse view matrix
|
|
// from vforward/right/up vectors and origin in g_camera
|
|
Matrix4x4_Invert_Full(view_inv, g_camera.viewMatrix);
|
|
Matrix4x4_ToArrayFloatGL(view_inv, (float*)sky_ubo->inv_view);
|
|
}
|
|
|
|
cur.pipeline = g_render.pipeline_sky.pipeline;
|
|
vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, cur.pipeline);
|
|
|
|
g_render.pipeline_sky.values[0].buffer = (VkDescriptorBufferInfo){
|
|
.buffer = g_render.uniform_buffer.buffer,
|
|
.offset = 0,
|
|
.range = sizeof(sky_uniform_data_t),
|
|
};
|
|
g_render.pipeline_sky.values[1].image = R_VkTexturesGetSkyboxDescriptorImageInfo( kSkyboxOriginal );
|
|
VK_DescriptorsWrite(&g_render.pipeline_sky.descs, frame_index);
|
|
|
|
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
|
g_render.pipeline_sky.descs.pipeline_layout, 0, 1, g_render.pipeline_sky.sets + frame_index, 1, &ubo_offset);
|
|
}
|
|
|
|
ASSERT(draw_sky->index_offset >= 0);
|
|
vkCmdDrawIndexed(cmdbuf, draw_sky->element_count, 1, draw_sky->index_offset, draw_sky->vertex_offset, 0);
|
|
|
|
// Reset current draw state
|
|
cur.texture = -1;
|
|
cur.lightmap = -1;
|
|
cur.ubo_offset = -1;
|
|
|
|
continue;
|
|
}
|
|
|
|
case DrawDraw:
|
|
// Continue drawing below
|
|
break;
|
|
}
|
|
|
|
ASSERT(draw->draw.pipeline_index >= 0);
|
|
ASSERT(draw->draw.pipeline_index < COUNTOF(g_render.pipelines));
|
|
const VkPipeline pipeline = g_render.pipelines[draw->draw.pipeline_index];
|
|
|
|
if (cur.pipeline != pipeline) {
|
|
cur.pipeline = pipeline;
|
|
vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, cur.pipeline);
|
|
|
|
// Make sure that after pipeline change we have this bound correctly
|
|
// Pipeline change might be due to previous pipeline being skybox, which has
|
|
// incompatible layout
|
|
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 3, 1, vk_desc_fixme.ubo_sets + 1, 1, &dlights_ubo_offset);
|
|
}
|
|
|
|
if (cur.ubo_offset != draw->draw.ubo_offset)
|
|
{
|
|
cur.ubo_offset = draw->draw.ubo_offset;
|
|
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 0, 1, vk_desc_fixme.ubo_sets, 1, &cur.ubo_offset);
|
|
}
|
|
|
|
if (cur.lightmap != draw->draw.lightmap) {
|
|
cur.lightmap = draw->draw.lightmap;
|
|
const VkDescriptorSet lm_unorm = R_VkTextureGetDescriptorUnorm(cur.lightmap);
|
|
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 2, 1, &lm_unorm, 0, NULL);
|
|
}
|
|
|
|
if (cur.texture != draw->draw.texture)
|
|
{
|
|
cur.texture = draw->draw.texture;
|
|
const VkDescriptorSet tex_unorm = R_VkTextureGetDescriptorUnorm(cur.texture);
|
|
// TODO names/enums for binding points
|
|
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_GRAPHICS, g_render.pipeline_layout, 1, 1, &tex_unorm, 0, NULL);
|
|
}
|
|
|
|
// Only indexed mode is supported
|
|
ASSERT(draw->draw.index_offset >= 0);
|
|
vkCmdDrawIndexed(cmdbuf, draw->draw.element_count, 1, draw->draw.index_offset, draw->draw.vertex_offset, 0);
|
|
}
|
|
}
|
|
|
|
void VK_RenderDebugLabelBegin( const char *name )
|
|
{
|
|
drawCmdPushDebugLabelBegin(name);
|
|
}
|
|
|
|
void VK_RenderDebugLabelEnd( void )
|
|
{
|
|
drawCmdPushDebugLabelEnd();
|
|
}
|
|
|
|
void VK_RenderEndRTX( struct vk_combuf_s* combuf, VkImageView img_dst_view, VkImage img_dst, uint32_t w, uint32_t h )
|
|
{
|
|
const VkBuffer geom_buffer = R_GeometryBuffer_Get();
|
|
ASSERT(vk_core.rtx);
|
|
|
|
{
|
|
const vk_ray_frame_render_args_t args = {
|
|
.combuf = combuf,
|
|
.dst = {
|
|
.image_view = img_dst_view,
|
|
.image = img_dst,
|
|
.width = w,
|
|
.height = h,
|
|
},
|
|
|
|
.projection = &g_render_state.vk_projection,
|
|
.view = &g_camera.viewMatrix,
|
|
|
|
.geometry_data = {
|
|
.buffer = geom_buffer,
|
|
.size = VK_WHOLE_SIZE,
|
|
},
|
|
|
|
.fov_angle_y = g_camera.fov_y,
|
|
};
|
|
|
|
VK_RayFrameEnd(&args);
|
|
}
|
|
}
|
|
|
|
qboolean R_RenderModelCreate( vk_render_model_t *model, vk_render_model_init_t args ) {
|
|
memset(model, 0, sizeof(*model));
|
|
Q_strncpy(model->debug_name, args.name, sizeof(model->debug_name));
|
|
|
|
model->geometries = args.geometries;
|
|
model->num_geometries = args.geometries_count;
|
|
|
|
if (!vk_core.rtx)
|
|
return true;
|
|
|
|
model->rt_model = RT_ModelCreate((rt_model_create_t){
|
|
.debug_name = model->debug_name,
|
|
.geometries = args.geometries,
|
|
.geometries_count = args.geometries_count,
|
|
.usage = args.dynamic ? kBlasBuildDynamicUpdate : kBlasBuildStatic,
|
|
});
|
|
return !!model->rt_model;
|
|
}
|
|
|
|
void R_RenderModelDestroy( vk_render_model_t* model ) {
|
|
if (model->rt_model)
|
|
RT_ModelDestroy(model->rt_model);
|
|
}
|
|
|
|
qboolean R_RenderModelUpdate( const vk_render_model_t *model ) {
|
|
// Non-RT rendering doesn't need to update anything, assuming that geometry regions offsets are not changed, and losing intermediate states is fine
|
|
if (!g_render_state.current_frame_is_ray_traced)
|
|
return true;
|
|
|
|
ASSERT(model->rt_model);
|
|
|
|
return RT_ModelUpdate(model->rt_model, model->geometries, model->num_geometries);
|
|
}
|
|
|
|
qboolean R_RenderModelUpdateMaterials( const vk_render_model_t *model, const int *geom_indices, int geom_indices_count) {
|
|
if (!model->rt_model)
|
|
return true;
|
|
|
|
return RT_ModelUpdateMaterials(model->rt_model, model->geometries, model->num_geometries, geom_indices, geom_indices_count);
|
|
}
|
|
|
|
static void uboComputeAndSetMVPFromModel( const matrix4x4 model ) {
|
|
matrix4x4 mvp;
|
|
Matrix4x4_Concat(mvp, g_render_state.projection_view, model);
|
|
Matrix4x4_ToArrayFloatGL(mvp, (float*)g_render_state.dirty_uniform_data.mvp);
|
|
}
|
|
|
|
typedef struct {
|
|
const char *debug_name;
|
|
int lightmap; // TODO per-geometry
|
|
const vk_render_geometry_t *geometries;
|
|
int geometries_count;
|
|
const matrix4x4 *transform;
|
|
const vec4_t *color;
|
|
int render_type;
|
|
int textures_override;
|
|
} trad_submit_t;
|
|
|
|
static void submitToTraditionalRender( trad_submit_t args ) {
|
|
int current_texture = args.textures_override;
|
|
int element_count = 0;
|
|
int index_offset = -1;
|
|
int vertex_offset = 0;
|
|
|
|
// TODO get rid of this dirty ubo thing
|
|
uboComputeAndSetMVPFromModel( *args.transform );
|
|
Vector4Copy(*args.color, g_render_state.dirty_uniform_data.color);
|
|
|
|
ASSERT(args.lightmap <= MAX_LIGHTMAPS);
|
|
const int lightmap = args.lightmap > 0 ? tglob.lightmapTextures[args.lightmap - 1] : tglob.whiteTexture;
|
|
|
|
drawCmdPushDebugLabelBegin( args.debug_name );
|
|
|
|
for (int i = 0; i < args.geometries_count; ++i) {
|
|
const vk_render_geometry_t *geom = args.geometries + i;
|
|
const int tex_mat = geom->material.tex_base_color;
|
|
const int geom_tex = g_render.use_material_textures->value && (tex_mat > 0 && tex_mat < MAX_TEXTURES) ? tex_mat : geom->ye_olde_texture;
|
|
const int tex = args.textures_override > 0 ? args.textures_override : geom_tex;
|
|
const qboolean split =
|
|
current_texture != tex
|
|
|| vertex_offset != geom->vertex_offset
|
|
|| (index_offset + element_count) != geom->index_offset;
|
|
|
|
// We only support indexed geometry
|
|
ASSERT(geom->index_offset >= 0);
|
|
|
|
if (tex < 0)
|
|
continue;
|
|
|
|
// TODO consider tracking contiguousness in drawCmdPushDraw(Sky)()
|
|
// Why: we could easily check that the previous command in the command list
|
|
// is contiguous, and could just increase its counts w/o submitting a new command
|
|
// This would make this code here a bit more readable and single-purpose.
|
|
if (split) {
|
|
if (element_count) {
|
|
if (current_texture == TEX_BASE_SKYBOX) {
|
|
drawCmdPushDrawSky(&(render_draw_sky_t){
|
|
.element_count = element_count,
|
|
.vertex_offset = vertex_offset,
|
|
.index_offset = index_offset,
|
|
});
|
|
} else {
|
|
render_draw_t draw = {
|
|
.lightmap = lightmap,
|
|
.texture = current_texture,
|
|
.pipeline_index = args.render_type,
|
|
.element_count = element_count,
|
|
.vertex_offset = vertex_offset,
|
|
.index_offset = index_offset,
|
|
};
|
|
|
|
drawCmdPushDraw( &draw );
|
|
}
|
|
}
|
|
|
|
current_texture = tex;
|
|
index_offset = geom->index_offset;
|
|
vertex_offset = geom->vertex_offset;
|
|
element_count = 0;
|
|
}
|
|
|
|
// Make sure that all surfaces are concatenated in buffers
|
|
ASSERT(index_offset + element_count == geom->index_offset);
|
|
element_count += geom->element_count;
|
|
}
|
|
|
|
if (element_count) {
|
|
if (current_texture == TEX_BASE_SKYBOX) {
|
|
drawCmdPushDrawSky(&(render_draw_sky_t){
|
|
.element_count = element_count,
|
|
.vertex_offset = vertex_offset,
|
|
.index_offset = index_offset,
|
|
});
|
|
} else {
|
|
const render_draw_t draw = {
|
|
.lightmap = lightmap,
|
|
.texture = current_texture,
|
|
.pipeline_index = args.render_type,
|
|
.element_count = element_count,
|
|
.vertex_offset = vertex_offset,
|
|
.index_offset = index_offset,
|
|
};
|
|
|
|
drawCmdPushDraw( &draw );
|
|
}
|
|
}
|
|
|
|
drawCmdPushDebugLabelEnd();
|
|
}
|
|
|
|
void R_RenderModelDraw(const vk_render_model_t *model, r_model_draw_t args) {
|
|
++g_render.stats.models_count;
|
|
|
|
if (g_render_state.current_frame_is_ray_traced) {
|
|
ASSERT(model->rt_model);
|
|
RT_FrameAddModel(model->rt_model, (rt_frame_add_model_t){
|
|
.material_mode = args.material_mode,
|
|
.material_flags = args.material_flags,
|
|
.transform = (const matrix3x4*)args.transform,
|
|
.prev_transform = (const matrix3x4*)args.prev_transform,
|
|
.color_srgb = args.color,
|
|
.override = {
|
|
.material = args.override.material,
|
|
.geoms = model->geometries,
|
|
.geoms_count = model->num_geometries,
|
|
},
|
|
});
|
|
} else {
|
|
submitToTraditionalRender((trad_submit_t){
|
|
.debug_name = model->debug_name,
|
|
.lightmap = model->lightmap,
|
|
.geometries = model->geometries,
|
|
.geometries_count = model->num_geometries,
|
|
.transform = args.transform,
|
|
.color = args.color,
|
|
.render_type = args.render_type,
|
|
.textures_override = args.override.old_texture,
|
|
});
|
|
}
|
|
}
|
|
|
|
void R_RenderDrawOnce(r_draw_once_t args) {
|
|
r_geometry_buffer_lock_t buffer;
|
|
if (!R_GeometryBufferAllocOnceAndLock( &buffer, args.vertices_count, args.indices_count)) {
|
|
gEngine.Con_Printf(S_ERROR "Cannot allocate geometry for dynamic draw\n");
|
|
return;
|
|
}
|
|
|
|
memcpy(buffer.vertices.ptr, args.vertices, sizeof(vk_vertex_t) * args.vertices_count);
|
|
memcpy(buffer.indices.ptr, args.indices, sizeof(uint16_t) * args.indices_count);
|
|
|
|
R_GeometryBufferUnlock( &buffer );
|
|
|
|
const vk_render_geometry_t geometry = {
|
|
.material = args.material,
|
|
.ye_olde_texture = args.ye_olde_texture,
|
|
|
|
.max_vertex = args.vertices_count,
|
|
.vertex_offset = buffer.vertices.unit_offset,
|
|
|
|
.element_count = args.indices_count,
|
|
.index_offset = buffer.indices.unit_offset,
|
|
|
|
.emissive = { (*args.color)[0], (*args.color)[1], (*args.color)[2] },
|
|
};
|
|
|
|
if (g_render_state.current_frame_is_ray_traced) {
|
|
RT_FrameAddOnce((rt_frame_add_once_t){
|
|
.debug_name = args.name,
|
|
.geometries = &geometry,
|
|
.color_srgb = args.color,
|
|
.geometries_count = 1,
|
|
.render_type = args.render_type,
|
|
});
|
|
} else {
|
|
submitToTraditionalRender((trad_submit_t){
|
|
.debug_name = args.name,
|
|
.lightmap = 0,
|
|
.geometries = &geometry,
|
|
.geometries_count = 1,
|
|
.transform = &m_matrix4x4_identity,
|
|
.color = args.color,
|
|
.render_type = args.render_type,
|
|
.textures_override = -1,
|
|
});
|
|
}
|
|
|
|
g_render.stats.dynamic_model_count++;
|
|
}
|