rt: extract per-model data from kusochki

This commit is contained in:
Ivan 'provod' Avdeev 2023-05-01 15:34:13 -07:00 committed by Ivan Avdeev
parent 0d8a7f76f5
commit 1cfb183cbd
16 changed files with 110 additions and 68 deletions

View File

@ -30,7 +30,8 @@ void main() {
// TODO mips
const uint tex_index = kusok.material.tex_base_color;
const vec4 texture_color = texture(textures[nonuniformEXT(tex_index)], texture_uv);
const vec3 color = texture_color.rgb * kusok.model.color.rgb * texture_color.a * kusok.model.color.a;
const vec4 mm_color = getModelHeader(gl_InstanceID).color * kusok.material.base_color;
const vec3 color = texture_color.rgb * mm_color.rgb * texture_color.a * mm_color.a;
const float overshoot = gl_HitTEXT - payload_additive.ray_distance;

View File

@ -72,6 +72,7 @@ bool getHit(vec3 origin, vec3 direction, inout RayPayloadPrimary payload) {
// 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha
const MiniGeometry geom = readCandidateMiniGeometry(rq);
const uint tex_base_color = getKusok(geom.kusok_index).material.tex_base_color;
// Should never happen: skybox is opaque if (tex_base_color == TEX_BASE_SKYBOX)
const vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv);
const float alpha_mask_threshold = .1f;

View File

@ -140,7 +140,7 @@ bool shadowedSky(vec3 pos, vec3 dir) {
const uint kusok_index = instance_kusochki_offset + geometry_index;
const Kusok kusok = getKusok(kusok_index);
if (kusok.material.mode != MATERIAL_MODE_SKYBOX)
if (kusok.material.tex_base_color != TEX_BASE_SKYBOX)
return true;
}

View File

@ -69,15 +69,10 @@ LIST_SPECIALIZATION_CONSTANTS(DECLARE_SPECIALIZATION_CONSTANT)
#define MATERIAL_MODE_BLEND_ADD 3
#define MATERIAL_MODE_BLEND_MIX 4
#define MATERIAL_MODE_BLEND_GLOW 5
#define MATERIAL_MODE_SKYBOX 6
#define TEX_BASE_SKYBOX 0xffffffffu
struct Material {
// TODO this should be moved to ModelMetadata, as granularity of this is per-model
// EXCEPT for skybox. Sky surfaces are all over the place in worldbrush.
// Maybe tex_base_color should have a special value for skybox.
// Moving this to model would allow us to make kusochki mostly static (except for animated textures)
uint mode;
uint tex_base_color;
// TODO can be combined into a single texture
@ -92,11 +87,16 @@ struct Material {
float roughness;
float metalness;
float normal_scale;
PAD(1)
vec4 base_color;
};
struct ModelMetadata {
vec4 color;
struct ModelHeader {
mat4 prev_transform;
vec4 color;
uint mode;
PAD(3)
};
struct Kusok {
@ -109,15 +109,12 @@ struct Kusok {
// Alignt it here to vec4 explicitly, so that later vector fields are properly aligned (for simplicity).
uint _padding0;
// TODO reference into material table
STRUCT Material material;
// Per-kusok because individual surfaces can be patched
vec3 emissive;
PAD(1)
// TODO move into a separate model array, and reference it by gl_GeometryIndexEXT/rayQueryGetIntersectionGeometryIndexEXT
STRUCT ModelMetadata model;
// TODO reference into material table
STRUCT Material material;
};
struct PointLight {

View File

@ -16,12 +16,14 @@ struct Vertex {
uint color;
};
layout(std430, binding = 3, set = 0) readonly buffer Kusochki { Kusok a[]; } kusochki;
layout(std430, binding = 4, set = 0) readonly buffer Indices { uint16_t a[]; } indices;
layout(std430, binding = 5, set = 0) readonly buffer Vertices { Vertex a[]; } vertices;
layout(std430, binding = 30, set = 0) readonly buffer ModelHeaders { ModelHeader a[]; } model_headers;
layout(std430, binding = 31, set = 0) readonly buffer Kusochki { Kusok a[]; } kusochki;
layout(std430, binding = 32, set = 0) readonly buffer Indices { uint16_t a[]; } indices;
layout(std430, binding = 33, set = 0) readonly buffer Vertices { Vertex a[]; } vertices;
Kusok getKusok(uint index) { return kusochki.a[index]; }
uint16_t getIndex(uint index) { return indices.a[index]; }
ModelHeader getModelHeader(uint index) { return model_headers.a[index]; }
#define GET_VERTEX(index) (vertices.a[index])
#endif //ifndef RAY_KUSOCHKI_GLSL_INCLUDED

View File

@ -87,6 +87,7 @@ void main() {
// 2. Same as the above, but also with a completely independent TLAS. Why: no need to mask-check geometry for opaque-vs-alpha
const MiniGeometry geom = readCandidateMiniGeometry(rq);
const uint tex_base_color = getKusok(geom.kusok_index).material.tex_base_color;
// Should never happen: skybox is opaque if (tex_base_color == TEX_BASE_SKYBOX)
const vec4 texture_color = texture(textures[nonuniformEXT(tex_base_color)], geom.uv);
const float alpha_mask_threshold = .1f;

View File

@ -29,11 +29,12 @@ void main() {
const Kusok kusok = getKusok(geom.kusok_index);
if (kusok.material.mode == MATERIAL_MODE_SKYBOX) {
if (kusok.material.tex_base_color == TEX_BASE_SKYBOX) {
payload.emissive.rgb = SRGBtoLINEAR(texture(skybox, gl_WorldRayDirectionEXT).rgb);
return;
} else {
payload.base_color_a = sampleTexture(kusok.material.tex_base_color, geom.uv, geom.uv_lods) * kusok.model.color;
const vec4 color = getModelHeader(gl_InstanceID).color * kusok.material.base_color;
payload.base_color_a = sampleTexture(kusok.material.tex_base_color, geom.uv, geom.uv_lods) * color;
payload.material_rmxx.r = sampleTexture(kusok.material.tex_roughness, geom.uv, geom.uv_lods).r * kusok.material.roughness;
payload.material_rmxx.g = sampleTexture(kusok.material.tex_metalness, geom.uv, geom.uv_lods).r * kusok.material.metalness;

View File

@ -30,7 +30,7 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {
const Kusok kusok = getKusok(geom.kusok_index);
const Material material = kusok.material;
if (kusok.material.mode == MATERIAL_MODE_SKYBOX) {
if (kusok.material.tex_base_color == TEX_BASE_SKYBOX) {
payload.emissive.rgb = SRGBtoLINEAR(texture(skybox, rayDirection).rgb);
return;
} else {
@ -69,8 +69,12 @@ void primaryRayHit(rayQueryEXT rq, inout RayPayloadPrimary payload) {
payload.emissive.rgb *= payload.base_color_a.rgb;
#endif
payload.base_color_a *= kusok.model.color;
payload.emissive.rgb *= kusok.model.color.rgb;
const int model_index = rayQueryGetIntersectionInstanceIdEXT(rq, true);
const ModelHeader model = getModelHeader(model_index);
const vec4 color = model.color * kusok.material.base_color;
payload.base_color_a *= color;
payload.emissive.rgb *= color.rgb;
}
#endif // ifndef RAY_PRIMARY_HIT_GLSL_INCLUDED

View File

@ -12,5 +12,5 @@ void main() {
const Kusok kusok = getKusok(kusok_index);
const uint tex_base_color = kusok.material.tex_base_color;
payload_shadow.hit_type = (kusok.material.mode != MATERIAL_MODE_SKYBOX) ? SHADOW_HIT : SHADOW_SKY ;
payload_shadow.hit_type = (kusok.material.tex_base_color != TEX_BASE_SKYBOX) ? SHADOW_HIT : SHADOW_SKY ;
}

View File

@ -56,6 +56,7 @@ struct Geometry {
#ifdef RAY_QUERY
Geometry readHitGeometry(rayQueryEXT rq, float ray_cone_width, vec2 bary) {
const int model_index = rayQueryGetIntersectionInstanceIdEXT(rq, true);
const int instance_kusochki_offset = rayQueryGetIntersectionInstanceCustomIndexEXT(rq, true);
const int geometry_index = rayQueryGetIntersectionGeometryIndexEXT(rq, true);
const int primitive_index = rayQueryGetIntersectionPrimitiveIndexEXT(rq, true);
@ -64,6 +65,7 @@ Geometry readHitGeometry(rayQueryEXT rq, float ray_cone_width, vec2 bary) {
const float hit_t = rayQueryGetIntersectionTEXT(rq, true);
#else
Geometry readHitGeometry(vec2 bary, float ray_cone_width) {
const int model_index = gl_InstanceID;
const int instance_kusochki_offset = gl_InstanceCustomIndexEXT;
const int geometry_index = gl_GeometryIndexEXT;
const int primitive_index = gl_PrimitiveID;
@ -88,10 +90,11 @@ Geometry readHitGeometry(vec2 bary, float ray_cone_width) {
objectToWorld * vec4(GET_VERTEX(vi3).pos, 1.f),
};
const ModelHeader model = getModelHeader(model_index);
const vec3 prev_pos[3] = {
(kusok.model.prev_transform * vec4(GET_VERTEX(vi1).prev_pos, 1.f)).xyz,
(kusok.model.prev_transform * vec4(GET_VERTEX(vi2).prev_pos, 1.f)).xyz,
(kusok.model.prev_transform * vec4(GET_VERTEX(vi3).prev_pos, 1.f)).xyz,
(model.prev_transform * vec4(GET_VERTEX(vi1).prev_pos, 1.f)).xyz,
(model.prev_transform * vec4(GET_VERTEX(vi2).prev_pos, 1.f)).xyz,
(model.prev_transform * vec4(GET_VERTEX(vi3).prev_pos, 1.f)).xyz,
};
const vec2 uvs[3] = {

View File

@ -27,44 +27,43 @@ void traceSimpleBlending(vec3 pos, vec3 dir, float L, inout vec3 emissive, inout
rayQueryInitializeEXT(rq, tlas, flags, GEOMETRY_BIT_BLEND, pos, 0., dir, L + glow_soft_overshoot);
while (rayQueryProceedEXT(rq)) {
const MiniGeometry geom = readCandidateMiniGeometry(rq);
const int model_index = rayQueryGetIntersectionInstanceIdEXT(rq, false);
const ModelHeader model = getModelHeader(model_index);
const Kusok kusok = getKusok(geom.kusok_index);
const float hit_t = rayQueryGetIntersectionTEXT(rq, false);
const float overshoot = hit_t - L;
if (overshoot > 0. && kusok.material.mode != MATERIAL_MODE_BLEND_GLOW)
if (overshoot > 0. && model.mode != MATERIAL_MODE_BLEND_GLOW)
continue;
//#define DEBUG_BLEND_MODES
#ifdef DEBUG_BLEND_MODES
if (kusok.material.mode == MATERIAL_MODE_BLEND_GLOW) {
if (model.mode == MATERIAL_MODE_BLEND_GLOW) {
emissive += vec3(1., 0., 0.);
//ret += color * smoothstep(glow_soft_overshoot, 0., overshoot);
} else if (kusok.material.mode == MATERIAL_MODE_BLEND_ADD) {
} else if (model.mode == MATERIAL_MODE_BLEND_ADD) {
emissive += vec3(0., 1., 0.);
} else if (kusok.material.mode == MATERIAL_MODE_BLEND_MIX) {
} else if (model.mode == MATERIAL_MODE_BLEND_MIX) {
emissive += vec3(0., 0., 1.);
} else if (kusok.material.mode == MATERIAL_MODE_TRANSLUCENT) {
} else if (model.mode == MATERIAL_MODE_TRANSLUCENT) {
emissive += vec3(0., 1., 1.);
} else if (kusok.material.mode == MATERIAL_MODE_OPAQUE) {
} else if (model.mode == MATERIAL_MODE_OPAQUE) {
emissive += vec3(1., 1., 1.);
}
#else
const vec4 texture_color = texture(textures[nonuniformEXT(kusok.material.tex_base_color)], geom.uv);
const vec4 mm_color = model.color * kusok.material.base_color;
float alpha = mm_color.a * texture_color.a * geom.vertex_color.a;
vec3 color = mm_color.rgb * SRGBtoLINEAR(texture_color.rgb) * geom.vertex_color.rgb * alpha;
// "Linearizing" alpha, while looking weird conceptually in the code, is necessary to make it look close to the original.
// TODO figure out whether texture alpha needs to be linearized too. Don't have good examples to look at.
// TODO this also makes sprites look dull, so likely kusok.model.color linearization should only apply to brush models, not sprites. This is better done when passing brush model to ray tracer.
float alpha = SRGBtoLINEAR(texture_color.a * kusok.model.color.a) * geom.vertex_color.a;
vec3 color = kusok.model.color.rgb * SRGBtoLINEAR(texture_color.rgb) * geom.vertex_color.rgb * alpha;
if (kusok.material.mode == MATERIAL_MODE_BLEND_GLOW) {
if (model.mode == MATERIAL_MODE_BLEND_GLOW) {
// Glow is additive + small overshoot
const float overshoot_factor = smoothstep(glow_soft_overshoot, 0., overshoot);
color *= overshoot_factor;
alpha = 0.;
} else if (kusok.material.mode == MATERIAL_MODE_BLEND_ADD) {
} else if (model.mode == MATERIAL_MODE_BLEND_ADD) {
// Additive doesn't attenuate what's behind
alpha = 0.;
} else if (kusok.material.mode == MATERIAL_MODE_BLEND_MIX) {
} else if (model.mode == MATERIAL_MODE_BLEND_MIX) {
// Handled in composite step below
} else {
// Signal unhandled blending type

View File

@ -5,6 +5,10 @@
#include "vk_ray_internal.h"
#include "r_speeds.h"
#include "vk_combuf.h"
#include "vk_staging.h"
#include "vk_math.h"
#include "xash3d_mathlib.h"
#define MAX_SCRATCH_BUFFER (32*1024*1024)
#define MAX_ACCELS_BUFFER (64*1024*1024)
@ -178,6 +182,15 @@ void RT_VkAccelPrepareTlas(vk_combuf_t *combuf) {
// Upload all blas instances references to GPU mem
{
const vk_staging_region_t headers_lock = R_VkStagingLockForBuffer((vk_staging_buffer_args_t){
.buffer = g_ray_model_state.model_headers_buffer.buffer,
.offset = 0,
.size = g_ray_model_state.frame.num_models * sizeof(struct ModelHeader),
.alignment = 16,
});
ASSERT(headers_lock.ptr);
VkAccelerationStructureInstanceKHR* inst = ((VkAccelerationStructureInstanceKHR*)g_accel.tlas_geom_buffer.mapped) + instance_offset;
for (int i = 0; i < g_ray_model_state.frame.num_models; ++i) {
const vk_ray_draw_model_t* const model = g_ray_model_state.frame.models + i;
@ -190,7 +203,6 @@ void RT_VkAccelPrepareTlas(vk_combuf_t *combuf) {
};
switch (model->material_mode) {
case MATERIAL_MODE_OPAQUE:
case MATERIAL_MODE_SKYBOX:
inst[i].mask = GEOMETRY_BIT_OPAQUE;
inst[i].instanceShaderBindingTableRecordOffset = SHADER_OFFSET_HIT_REGULAR,
inst[i].flags = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR;
@ -217,7 +229,14 @@ void RT_VkAccelPrepareTlas(vk_combuf_t *combuf) {
break;
}
memcpy(&inst[i].transform, model->transform_row, sizeof(VkTransformMatrixKHR));
struct ModelHeader *const header = ((struct ModelHeader*)headers_lock.ptr) + i;
header->mode = model->material_mode;
Vector4Copy(model->model->color, header->color);
Matrix4x4_ToArrayFloatGL(model->model->prev_transform, (float*)header->prev_transform);
}
R_VkStagingUnlock(headers_lock.handle);
}
g_accel_.stats.blas_count = g_ray_model_state.frame.num_models;
@ -225,14 +244,14 @@ void RT_VkAccelPrepareTlas(vk_combuf_t *combuf) {
// Barrier for building all BLASes
// BLAS building is now in cmdbuf, need to synchronize with results
{
VkBufferMemoryBarrier bmb[] = { {
VkBufferMemoryBarrier bmb[] = {{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, // | VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR,
.buffer = g_accel.accels_buffer.buffer,
.offset = instance_offset * sizeof(VkAccelerationStructureInstanceKHR),
.size = g_ray_model_state.frame.num_models * sizeof(VkAccelerationStructureInstanceKHR),
} };
}};
vkCmdPipelineBarrier(combuf->cmdbuf,
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,

View File

@ -60,6 +60,10 @@ typedef struct {
vk_buffer_t kusochki_buffer;
r_debuffer_t kusochki_alloc;
// Model header
// Array of struct ModelHeader: color, material_mode, prev_transform
vk_buffer_t model_headers_buffer;
// Per-frame data that is accumulated between RayFrameBegin and End calls
struct {
int num_models;

View File

@ -155,7 +155,7 @@ void XVK_RayModel_Validate( void ) {
}
}
static void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometry_t *geom, const vec4_t model_color, uint32_t mode) {
static void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometry_t *geom) {
const xvk_material_t *const mat = XVK_GetMaterialForTextureIndex( geom->texture );
ASSERT(mat);
@ -165,9 +165,8 @@ static void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometr
kusok->index_offset = geom->index_offset;
kusok->triangles = geom->element_count / 3;
// Material data itself is mostly static. Except for animated textures, which just get a new material slot for each frame.
kusok->material = (struct Material){
.mode = mode,
.tex_base_color = mat->tex_base_color,
.tex_roughness = mat->tex_roughness,
.tex_metalness = mat->tex_metalness,
@ -178,24 +177,18 @@ static void applyMaterialToKusok(vk_kusok_data_t* kusok, const vk_render_geometr
.normal_scale = mat->normal_scale,
};
// TODO emissive is potentially "dynamic", not tied to the material directly, as it is specified per-surface in rad files
VectorCopy(geom->emissive, kusok->emissive);
Vector4Copy(mat->base_color, kusok->material.base_color);
// TODO should be patched by the Chrome material source itself to generate a static chrome material
const qboolean HACK_chrome = geom->material == kXVkMaterialChrome;
if (!mat->set && HACK_chrome)
kusok->material.tex_roughness = tglob.grayTexture;
// Purely static. Once a sky forever a sky.
if (geom->material == kXVkMaterialSky)
kusok->material.mode = MATERIAL_MODE_SKYBOX;
// FIXME modulates model_color with material->base_color which has different frequency
{
vec4_t gcolor;
gcolor[0] = model_color[0] * mat->base_color[0];
gcolor[1] = model_color[1] * mat->base_color[1];
gcolor[2] = model_color[2] * mat->base_color[2];
gcolor[3] = model_color[3] * mat->base_color[3];
Vector4Copy(gcolor, kusok->model.color);
}
VectorCopy(geom->emissive, kusok->emissive);
kusok->material.tex_base_color = TEX_BASE_SKYBOX;
}
vk_ray_model_t* VK_RayModelCreate( vk_ray_model_init_t args ) {
@ -329,7 +322,7 @@ void VK_RayModelDestroy( struct vk_ray_model_s *model ) {
}
}
// TODO move this to some common place with traditional renderer
// TODO move this to vk_brush
static void computeConveyorSpeed(const color24 rendercolor, int tex_index, vec2_t speed) {
float sy, cy;
float flConveyorSpeed = 0.0f;
@ -358,6 +351,7 @@ static void computeConveyorSpeed(const color24 rendercolor, int tex_index, vec2_
speed[1] = sy * flRate;
}
// TODO utilize uploadKusochki([1]) to avoid 2 copies of staging code
static qboolean uploadKusochkiSubset(const vk_ray_model_t *const model, const vk_render_model_t *const render_model, uint32_t material_mode, const int *geom_indexes, int geom_indexes_count) {
// TODO can we sort all animated geometries (in brush) to have only a single range here?
for (int i = 0; i < geom_indexes_count; ++i) {
@ -379,8 +373,7 @@ static qboolean uploadKusochkiSubset(const vk_ray_model_t *const model, const vk
vk_kusok_data_t *const kusochki = kusok_staging.ptr;
vk_render_geometry_t *geom = render_model->geometries + index;
applyMaterialToKusok(kusochki + 0, geom, render_model->color, material_mode);
Matrix4x4_ToArrayFloatGL(render_model->prev_transform, (float*)(kusochki + 0)->model.prev_transform);
applyMaterialToKusok(kusochki + 0, geom);
/* gEngine.Con_Reportf("model %s: geom=%d kuoffs=%d kustoff=%d kustsz=%d sthndl=%d\n", */
/* render_model->debug_name, */
@ -413,8 +406,7 @@ static qboolean uploadKusochki(const vk_ray_model_t *const model, const vk_rende
for (int i = 0; i < render_model->num_geometries; ++i) {
vk_render_geometry_t *geom = render_model->geometries + i;
applyMaterialToKusok(kusochki + i, geom, render_model->color, material_mode);
Matrix4x4_ToArrayFloatGL(render_model->prev_transform, (float*)(kusochki + i)->model.prev_transform);
applyMaterialToKusok(kusochki + i, geom);
}
/* gEngine.Con_Reportf("model %s: geom=%d kuoffs=%d kustoff=%d kustsz=%d sthndl=%d\n", */

View File

@ -25,7 +25,7 @@ typedef enum {
kXVkMaterialRegular = 0,
// Set for SURF_DRAWSKY surfaces in vk_brush.c.
// Used: for setting MATERIAL_MODE_SKYBOX for skybox texture sampling and environment shadows.
// Used: for setting TEX_BASE_SKYBOX for skybox texture sampling and environment shadows.
// Remove: pass it as a special texture/material index (e.g. -2).
kXVkMaterialSky,

View File

@ -45,6 +45,7 @@
X(TLAS, tlas) \
X(Buffer, ubo) \
X(Buffer, kusochki) \
X(Buffer, model_headers) \
X(Buffer, indices) \
X(Buffer, vertices) \
X(Buffer, lights) \
@ -201,6 +202,7 @@ static void performTracing( vk_combuf_t *combuf, const perform_tracing_args_t* a
// TODO move this to ray model producer
RES_SET_SBUFFER_FULL(kusochki, g_ray_model_state.kusochki_buffer);
RES_SET_SBUFFER_FULL(model_headers, g_ray_model_state.model_headers_buffer);
// TODO move these to vk_geometry
RES_SET_SBUFFER_FULL(indices, args->render_args->geometry_data);
@ -221,6 +223,13 @@ static void performTracing( vk_combuf_t *combuf, const perform_tracing_args_t* a
.buffer = g_ray_model_state.kusochki_buffer.buffer,
.offset = 0,
.size = VK_WHOLE_SIZE,
}, {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.buffer = g_ray_model_state.model_headers_buffer.buffer,
.offset = 0,
.size = VK_WHOLE_SIZE,
} };
vkCmdPipelineBarrier(cmdbuf,
@ -631,6 +640,14 @@ qboolean VK_RayInit( void )
// FIXME complain, handle
return false;
}
if (!VK_BufferCreate("model headers", &g_ray_model_state.model_headers_buffer, sizeof(struct ModelHeader) * MAX_ACCELS,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
// FIXME complain, handle
return false;
}
RT_RayModel_Clear();
gEngine.Cmd_AddCommand("vk_rtx_reload", reloadPipeline, "Reload RTX shader");
@ -643,6 +660,7 @@ void VK_RayShutdown( void ) {
destroyMainpipe();
VK_BufferDestroy(&g_ray_model_state.model_headers_buffer);
VK_BufferDestroy(&g_ray_model_state.kusochki_buffer);
VK_BufferDestroy(&g_rtx.uniform_buffer);