| … | … | @@ -32,31 +32,47 @@ |
| 32 | 32 | |
| 33 | 33 | #include "servers/rendering/renderer_rd/storage_rd/texture_storage.h" |
| 34 | 34 | |
| 35 | void BindlessBlock::initialize(RenderingDevice *p_rd) { | |
| 35 | void BindlessBlock::initialize(RenderingDevice *p_rd, TextureKind p_kind) { | |
| 36 | 36 | ERR_FAIL_NULL(p_rd); |
| 37 | 37 | |
| 38 | 38 | rd = p_rd; |
| 39 | kind = p_kind; | |
| 40 | ||
| 41 | RendererRD::TextureStorage::DefaultRDTexture default_id = RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_WHITE; | |
| 42 | switch (kind) { | |
| 43 | case TEXTURE_2D_ARRAY: | |
| 44 | default_id = RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE; | |
| 45 | break; | |
| 46 | case TEXTURE_3D: | |
| 47 | default_id = RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_3D_WHITE; | |
| 48 | break; | |
| 49 | case TEXTURE_CUBE: | |
| 50 | default_id = RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_CUBEMAP_WHITE; | |
| 51 | break; | |
| 52 | case TEXTURE_CUBE_ARRAY: | |
| 53 | default_id = RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_CUBEMAP_ARRAY_WHITE; | |
| 54 | break; | |
| 55 | default: | |
| 56 | break; | |
| 57 | } | |
| 39 | 58 | |
| 40 | default_texture = RendererRD::TextureStorage::get_singleton()->texture_rd_get_default( | |
| 41 | RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_WHITE); | |
| 59 | default_texture = RendererRD::TextureStorage::get_singleton()->texture_rd_get_default(default_id); | |
| 42 | 60 | textures.push_back(default_texture); |
| 43 | 61 | texture_to_index[default_texture] = 0; |
| 62 | needs_refinalize = true; | |
| 44 | 63 | } |
| 45 | 64 | |
| 46 | void BindlessBlock::begin_frame() { | |
| 65 | void BindlessBlock::begin_frame(bool p_set_valid) { | |
| 47 | 66 | if (!is_initialized()) { |
| 48 | 67 | return; |
| 49 | 68 | } |
| 50 | 69 | |
| 51 | bool set_valid = uniform_set.is_valid() && rd->uniform_set_is_valid(uniform_set); | |
| 52 | if (set_valid) { | |
| 70 | if (p_set_valid) { | |
| 53 | 71 | return; |
| 54 | 72 | } |
| 55 | 73 | |
| 56 | 74 | // Uniform set was externally invalidated (a texture was destroyed/recreated). |
| 57 | 75 | // Scrub dead RIDs: replace with default and add slots to the freelist. |
| 58 | uniform_set = RID(); | |
| 59 | ||
| 60 | 76 | texture_to_index.clear(); |
| 61 | 77 | texture_to_index[default_texture] = 0; |
| 62 | 78 | |
| … | … | @@ -90,14 +106,13 @@ uint32_t BindlessBlock::add_texture(RID p_texture) { |
| 90 | 106 | return it->value; |
| 91 | 107 | } |
| 92 | 108 | |
| 93 | if (textures.size() >= MAX_BINDLESS_TEXTURES && free_indices_count == 0) { | |
| 109 | const uint32_t capacity = is_unbounded(kind) ? MAX_BINDLESS_TEXTURES : TYPED_TABLE_CAPACITY; | |
| 110 | if (textures.size() >= capacity && free_indices_count == 0) { | |
| 94 | 111 | ERR_PRINT_ONCE("BindlessBlock: Maximum texture count exceeded. Using default texture."); |
| 95 | 112 | return 0; |
| 96 | 113 | } |
| 97 | 114 | |
| 98 | if (is_finalized()) { | |
| 99 | needs_refinalize = true; | |
| 100 | } | |
| 115 | needs_refinalize = true; | |
| 101 | 116 | |
| 102 | 117 | uint32_t index; |
| 103 | 118 | if (free_indices_count > 0) { |
| … | … | @@ -113,46 +128,37 @@ uint32_t BindlessBlock::add_texture(RID p_texture) { |
| 113 | 128 | return index; |
| 114 | 129 | } |
| 115 | 130 | |
| 116 | void BindlessBlock::finalize(RID p_shader, uint32_t p_set_index) { | |
| 131 | void BindlessBlock::collect_uniform(Vector<RD::Uniform> &r_uniforms) const { | |
| 117 | 132 | ERR_FAIL_COND_MSG(!is_initialized(), "BindlessBlock not initialized."); |
| 118 | 133 | ERR_FAIL_COND_MSG(textures.is_empty(), "BindlessBlock has no textures."); |
| 119 | 134 | |
| 120 | if (is_finalized() && !needs_refinalize && rd->uniform_set_is_valid(uniform_set)) { | |
| 121 | return; | |
| 122 | } | |
| 123 | ||
| 124 | if (uniform_set.is_valid() && rd->uniform_set_is_valid(uniform_set)) { | |
| 125 | rd->free_rid(uniform_set); | |
| 126 | } | |
| 127 | uniform_set = RID(); | |
| 128 | ||
| 129 | Vector<RD::Uniform> uniforms; | |
| 130 | ||
| 131 | 135 | RD::Uniform u; |
| 132 | 136 | u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; |
| 133 | u.binding = 0; | |
| 134 | u.variable_count = true; | |
| 137 | u.binding = get_binding(kind); | |
| 138 | u.variable_count = is_unbounded(kind); | |
| 139 | ||
| 135 | 140 | for (uint32_t i = 0; i < textures.size(); i++) { |
| 136 | 141 | u.append_id(textures[i]); |
| 137 | 142 | } |
| 138 | uniforms.push_back(u); | |
| 139 | 143 | |
| 140 | uniform_set = rd->uniform_set_create(uniforms, p_shader, p_set_index); | |
| 141 | ERR_FAIL_COND_MSG(!uniform_set.is_valid(), "Failed to create bindless uniform set."); | |
| 142 | rd->set_resource_name(uniform_set, "Bindless Texture Set"); | |
| 144 | // A bounded binding gets no PARTIALLY_BOUND flag, so every descriptor in the | |
| 145 | // declared array must be written; pad the tail with this kind's default. | |
| 146 | if (!is_unbounded(kind)) { | |
| 147 | for (uint32_t i = textures.size(); i < TYPED_TABLE_CAPACITY; i++) { | |
| 148 | u.append_id(default_texture); | |
| 149 | } | |
| 150 | } | |
| 143 | 151 | |
| 144 | needs_refinalize = false; | |
| 152 | r_uniforms.push_back(u); | |
| 145 | 153 | } |
| 146 | 154 | |
| 147 | 155 | void BindlessBlock::clear() { |
| 148 | if (uniform_set.is_valid() && rd && rd->uniform_set_is_valid(uniform_set)) { | |
| 149 | rd->free_rid(uniform_set); | |
| 150 | } | |
| 151 | uniform_set = RID(); | |
| 156 | // The uniform set is shared across kinds and owned by the caller. | |
| 152 | 157 | textures.clear(); |
| 153 | 158 | texture_to_index.clear(); |
| 154 | 159 | free_indices.clear(); |
| 155 | 160 | free_indices_count = 0; |
| 161 | needs_refinalize = true; | |
| 156 | 162 | rd = nullptr; |
| 157 | 163 | } |
| 158 | 164 |
| … | … | @@ -38,29 +38,65 @@ class BindlessBlock { |
| 38 | 38 | public: |
| 39 | 39 | constexpr static uint32_t MAX_BINDLESS_TEXTURES = 128000; |
| 40 | 40 | |
| 41 | // GLSL cannot construct a sampler of one dimensionality from a texture of | |
| 42 | // another, so each texture type needs its OWN bindless table -- a single | |
| 43 | // texture2D[] table makes every sampler2DArray/sampler3D/samplerCube uniform | |
| 44 | // generate sampler2DArray(texture2D, ...), which glslang rejects, failing the | |
| 45 | // whole hit group and rendering nothing. | |
| 46 | // | |
| 47 | // Vulkan permits only one variable-count (unbounded) binding per descriptor | |
| 48 | // set, and it must be the binding with the LARGEST binding number | |
| 49 | // (VUID-VkDescriptorSetLayoutBindingFlagsCreateInfo-pBindingFlags-03004; also | |
| 50 | // enforced in rendering_device_driver_vulkan.cpp). So the rarely-used typed | |
| 51 | // tables are fixed-capacity at the low bindings and the 2D table -- the one | |
| 52 | // that actually needs to scale -- stays unbounded at the highest binding. | |
| 53 | enum TextureKind { | |
| 54 | TEXTURE_2D_ARRAY, | |
| 55 | TEXTURE_3D, | |
| 56 | TEXTURE_CUBE, | |
| 57 | TEXTURE_CUBE_ARRAY, | |
| 58 | TEXTURE_2D, // Unbounded; MUST remain the highest binding. | |
| 59 | TEXTURE_KIND_MAX | |
| 60 | }; | |
| 61 | ||
| 62 | // Fixed capacity of the typed (bounded) tables. Bounded bindings do not get | |
| 63 | // PARTIALLY_BOUND, so these are always written in full, padded with the | |
| 64 | // kind's default texture. | |
| 65 | constexpr static uint32_t TYPED_TABLE_CAPACITY = 256; | |
| 66 | ||
| 67 | static bool is_unbounded(TextureKind p_kind) { return p_kind == TEXTURE_2D; } | |
| 68 | static uint32_t get_binding(TextureKind p_kind) { return (uint32_t)p_kind; } | |
| 69 | ||
| 41 | 70 | uint32_t add_texture(RID p_texture); |
| 42 | 71 | |
| 43 | void initialize(RenderingDevice *p_rd); | |
| 72 | void initialize(RenderingDevice *p_rd, TextureKind p_kind = TEXTURE_2D); | |
| 73 | ||
| 74 | // p_set_valid: whether the shared uniform set this table belongs to is still | |
| 75 | // live. When it is not, dead texture RIDs are scrubbed and a rebuild is | |
| 76 | // requested. The set itself is owned by the caller, since all kinds share one. | |
| 77 | void begin_frame(bool p_set_valid); | |
| 44 | 78 | |
| 45 | void begin_frame(); | |
| 79 | // Appends this table's binding to p_uniforms. Every table in the set must be | |
| 80 | // collected before the set is created, so creation is the caller's job. | |
| 81 | void collect_uniform(Vector<RD::Uniform> &r_uniforms) const; | |
| 46 | 82 | |
| 47 | void finalize(RID p_shader, uint32_t p_set_index); | |
| 83 | bool needs_rebuild() const { return needs_refinalize; } | |
| 84 | void mark_rebuilt() { needs_refinalize = false; } | |
| 48 | 85 | |
| 49 | RID get_uniform_set() const { return uniform_set; } | |
| 50 | 86 | uint32_t get_texture_count() const { return textures.size(); } |
| 87 | TextureKind get_kind() const { return kind; } | |
| 51 | 88 | bool is_initialized() const { return rd != nullptr; } |
| 52 | bool is_finalized() const { return uniform_set.is_valid(); } | |
| 53 | 89 | |
| 54 | 90 | void clear(); |
| 55 | 91 | ~BindlessBlock(); |
| 56 | 92 | |
| 57 | 93 | private: |
| 58 | 94 | RenderingDevice *rd = nullptr; |
| 95 | TextureKind kind = TEXTURE_2D; | |
| 59 | 96 | LocalVector<RID> textures; |
| 60 | 97 | HashMap<RID, uint32_t> texture_to_index; |
| 61 | 98 | LocalVector<uint32_t> free_indices; |
| 62 | 99 | uint32_t free_indices_count = 0; // logical top to avoid resize of free_indices array |
| 63 | 100 | RID default_texture; |
| 64 | RID uniform_set; | |
| 65 | bool needs_refinalize = false; | |
| 101 | bool needs_refinalize = true; | |
| 66 | 102 | }; |
| … | … | @@ -47,9 +47,30 @@ using namespace RendererSceneRenderImplementation; |
| 47 | 47 | // Lifecycle |
| 48 | 48 | // --------------------------------------------------------------------------- |
| 49 | 49 | |
| 50 | BindlessBlock::TextureKind RenderRaytracing::_texture_kind_for_type(ShaderLanguage::DataType p_type) { | |
| 51 | switch (p_type) { | |
| 52 | case ShaderLanguage::TYPE_SAMPLER2DARRAY: | |
| 53 | case ShaderLanguage::TYPE_ISAMPLER2DARRAY: | |
| 54 | case ShaderLanguage::TYPE_USAMPLER2DARRAY: | |
| 55 | return BindlessBlock::TEXTURE_2D_ARRAY; | |
| 56 | case ShaderLanguage::TYPE_SAMPLER3D: | |
| 57 | case ShaderLanguage::TYPE_ISAMPLER3D: | |
| 58 | case ShaderLanguage::TYPE_USAMPLER3D: | |
| 59 | return BindlessBlock::TEXTURE_3D; | |
| 60 | case ShaderLanguage::TYPE_SAMPLERCUBE: | |
| 61 | return BindlessBlock::TEXTURE_CUBE; | |
| 62 | case ShaderLanguage::TYPE_SAMPLERCUBEARRAY: | |
| 63 | return BindlessBlock::TEXTURE_CUBE_ARRAY; | |
| 64 | default: | |
| 65 | return BindlessBlock::TEXTURE_2D; | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 50 | 69 | void RenderRaytracing::initialize(RenderForwardClustered *p_owner) { |
| 51 | 70 | owner = p_owner; |
| 52 | bindless_block = memnew(BindlessBlock); | |
| 71 | for (uint32_t k = 0; k < BindlessBlock::TEXTURE_KIND_MAX; k++) { | |
| 72 | bindless_blocks[k] = memnew(BindlessBlock); | |
| 73 | } | |
| 53 | 74 | } |
| 54 | 75 | |
| 55 | 76 | RenderRaytracing::~RenderRaytracing() { |
| … | … | @@ -65,9 +86,16 @@ RenderRaytracing::~RenderRaytracing() { |
| 65 | 86 | mat_ubo_pool_buffer = RID(); |
| 66 | 87 | } |
| 67 | 88 | |
| 68 | if (bindless_block) { | |
| 69 | memdelete(bindless_block); | |
| 70 | bindless_block = nullptr; | |
| 89 | if (bindless_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(bindless_uniform_set)) { | |
| 90 | RD::get_singleton()->free_rid(bindless_uniform_set); | |
| 91 | } | |
| 92 | bindless_uniform_set = RID(); | |
| 93 | ||
| 94 | for (uint32_t k = 0; k < BindlessBlock::TEXTURE_KIND_MAX; k++) { | |
| 95 | if (bindless_blocks[k]) { | |
| 96 | memdelete(bindless_blocks[k]); | |
| 97 | bindless_blocks[k] = nullptr; | |
| 98 | } | |
| 71 | 99 | } |
| 72 | 100 | if (shader) { |
| 73 | 101 | memdelete(shader); |
| … | … | @@ -391,10 +419,21 @@ void RenderRaytracing::prepare_frame() { |
| 391 | 419 | cache_hits = 0; |
| 392 | 420 | cache_misses = 0; |
| 393 | 421 | |
| 394 | if (!bindless_block->is_initialized()) { | |
| 395 | bindless_block->initialize(RD::get_singleton()); | |
| 422 | // The whole set is rebuilt as a unit, so its liveness is evaluated once and | |
| 423 | // handed to every table. | |
| 424 | const bool bindless_set_valid = bindless_uniform_set.is_valid() && | |
| 425 | RD::get_singleton()->uniform_set_is_valid(bindless_uniform_set); | |
| 426 | if (!bindless_set_valid) { | |
| 427 | bindless_uniform_set = RID(); | |
| 428 | } | |
| 429 | ||
| 430 | for (uint32_t k = 0; k < BindlessBlock::TEXTURE_KIND_MAX; k++) { | |
| 431 | BindlessBlock *block = bindless_blocks[k]; | |
| 432 | if (!block->is_initialized()) { | |
| 433 | block->initialize(RD::get_singleton(), (BindlessBlock::TextureKind)k); | |
| 434 | } | |
| 435 | block->begin_frame(bindless_set_valid); | |
| 396 | 436 | } |
| 397 | bindless_block->begin_frame(); | |
| 398 | 437 | } |
| 399 | 438 | |
| 400 | 439 | // --------------------------------------------------------------------------- |
| … | … | @@ -1190,12 +1229,12 @@ RTMaterialData *RenderRaytracing::process_material(RID p_material_rid, uint16_t |
| 1190 | 1229 | // Albedo is a color texture - needs sRGB->linear conversion |
| 1191 | 1230 | RID albedo_rd = get_material_texture("texture_albedo", true); |
| 1192 | 1231 | if (albedo_rd.is_valid()) { |
| 1193 | mat.albedo_texture_idx = bindless_block->add_texture(albedo_rd); | |
| 1232 | mat.albedo_texture_idx = _bindless(BindlessBlock::TEXTURE_2D)->add_texture(albedo_rd); | |
| 1194 | 1233 | } |
| 1195 | 1234 | |
| 1196 | 1235 | RID normal_rd = get_material_texture("texture_normal"); |
| 1197 | 1236 | if (normal_rd.is_valid()) { |
| 1198 | mat.normal_texture_idx = bindless_block->add_texture(normal_rd); | |
| 1237 | mat.normal_texture_idx = _bindless(BindlessBlock::TEXTURE_2D)->add_texture(normal_rd); | |
| 1199 | 1238 | mat.flags |= RT_MAT_FLAG_HAS_NORMAL_MAP; |
| 1200 | 1239 | |
| 1201 | 1240 | Variant normal_scale_var = material_storage->material_get_param(p_material_rid, "normal_scale"); |
| … | … | @@ -1206,18 +1245,18 @@ RTMaterialData *RenderRaytracing::process_material(RID p_material_rid, uint16_t |
| 1206 | 1245 | |
| 1207 | 1246 | RID orm_rd = get_material_texture("texture_orm"); |
| 1208 | 1247 | if (orm_rd.is_valid()) { |
| 1209 | mat.orm_texture_idx = bindless_block->add_texture(orm_rd); | |
| 1248 | mat.orm_texture_idx = _bindless(BindlessBlock::TEXTURE_2D)->add_texture(orm_rd); | |
| 1210 | 1249 | } else { |
| 1211 | 1250 | RID roughness_rd = get_material_texture("texture_roughness"); |
| 1212 | 1251 | if (roughness_rd.is_valid()) { |
| 1213 | mat.orm_texture_idx = bindless_block->add_texture(roughness_rd); | |
| 1252 | mat.orm_texture_idx = _bindless(BindlessBlock::TEXTURE_2D)->add_texture(roughness_rd); | |
| 1214 | 1253 | } |
| 1215 | 1254 | } |
| 1216 | 1255 | |
| 1217 | 1256 | // Emission is a color texture - needs sRGB->linear conversion |
| 1218 | 1257 | RID emission_rd = get_material_texture("texture_emission", true); |
| 1219 | 1258 | if (emission_rd.is_valid()) { |
| 1220 | mat.emission_texture_idx = bindless_block->add_texture(emission_rd); | |
| 1259 | mat.emission_texture_idx = _bindless(BindlessBlock::TEXTURE_2D)->add_texture(emission_rd); | |
| 1221 | 1260 | mat.flags |= RT_MAT_FLAG_HAS_EMISSION_TEX; |
| 1222 | 1261 | // Set sensible defaults for emission when texture is present |
| 1223 | 1262 | mat.emission_color[0] = 1.0f; |
| … | … | @@ -1282,12 +1321,18 @@ RTMaterialData *RenderRaytracing::process_material(RID p_material_rid, uint16_t |
| 1282 | 1321 | const SceneShaderRaytracing::TextureUniformInfo &tui = cse->texture_uniforms[ti]; |
| 1283 | 1322 | uint32_t bindless_idx = 0; |
| 1284 | 1323 | |
| 1324 | // The index is looked up in the table matching the uniform's sampler | |
| 1325 | // type; the generated GLSL indexes that same table (see | |
| 1326 | // SceneShaderRaytracing tex_defines), so the two must agree. | |
| 1327 | const BindlessBlock::TextureKind kind = _texture_kind_for_type(tui.type); | |
| 1328 | BindlessBlock *block = _bindless(kind); | |
| 1329 | ||
| 1285 | 1330 | if (tui.is_global) { |
| 1286 | 1331 | RID tex_rid = material_storage->global_shader_uniform_get_texture(tui.name); |
| 1287 | 1332 | if (tex_rid.is_valid()) { |
| 1288 | 1333 | RID rd_tex = ts->texture_get_rd_texture(tex_rid, tui.use_color); |
| 1289 | 1334 | if (rd_tex.is_valid()) { |
| 1290 | bindless_idx = bindless_block->add_texture(rd_tex); | |
| 1335 | bindless_idx = block->add_texture(rd_tex); | |
| 1291 | 1336 | } |
| 1292 | 1337 | } |
| 1293 | 1338 | } else { |
| … | … | @@ -1297,13 +1342,16 @@ RTMaterialData *RenderRaytracing::process_material(RID p_material_rid, uint16_t |
| 1297 | 1342 | if (tex_rid.is_valid()) { |
| 1298 | 1343 | RID rd_tex = ts->texture_get_rd_texture(tex_rid, tui.use_color); |
| 1299 | 1344 | if (rd_tex.is_valid()) { |
| 1300 | bindless_idx = bindless_block->add_texture(rd_tex); | |
| 1345 | bindless_idx = block->add_texture(rd_tex); | |
| 1301 | 1346 | } |
| 1302 | 1347 | } |
| 1303 | 1348 | } |
| 1304 | 1349 | } |
| 1305 | 1350 | |
| 1306 | if (bindless_idx == 0 && tui.hint != ShaderLanguage::ShaderNode::Uniform::HINT_NONE) { | |
| 1351 | // The hint defaults below are all 2D textures. For any other kind, | |
| 1352 | // index 0 already IS that table's default of the correct type. | |
| 1353 | if (bindless_idx == 0 && kind == BindlessBlock::TEXTURE_2D && | |
| 1354 | tui.hint != ShaderLanguage::ShaderNode::Uniform::HINT_NONE) { | |
| 1307 | 1355 | using Hint = ShaderLanguage::ShaderNode::Uniform::Hint; |
| 1308 | 1356 | RID default_tex; |
| 1309 | 1357 | switch (tui.hint) { |
| … | … | @@ -1324,7 +1372,7 @@ RTMaterialData *RenderRaytracing::process_material(RID p_material_rid, uint16_t |
| 1324 | 1372 | break; |
| 1325 | 1373 | } |
| 1326 | 1374 | if (default_tex.is_valid()) { |
| 1327 | bindless_idx = bindless_block->add_texture(default_tex); | |
| 1375 | bindless_idx = block->add_texture(default_tex); | |
| 1328 | 1376 | } |
| 1329 | 1377 | } |
| 1330 | 1378 | |
| … | … | @@ -2263,9 +2311,37 @@ RID RenderRaytracing::update_uniform_set(RTViewportState *p_state, const RenderD |
| 2263 | 2311 | RD::get_singleton()->set_resource_name(p_state->uniform_set, "RT Uniform Set"); |
| 2264 | 2312 | |
| 2265 | 2313 | // === SET 1: Bindless textures === |
| 2266 | if (bindless_block && bindless_block->is_initialized()) { | |
| 2267 | bindless_block->finalize(shader_rd, 1); | |
| 2268 | bindless_uniform_set = bindless_block->get_uniform_set(); | |
| 2314 | // All texture kinds share set 1, so the set is rebuilt as a unit whenever | |
| 2315 | // any table changed. | |
| 2316 | bool bindless_ready = true; | |
| 2317 | bool bindless_dirty = !bindless_uniform_set.is_valid(); | |
| 2318 | for (uint32_t k = 0; k < BindlessBlock::TEXTURE_KIND_MAX; k++) { | |
| 2319 | if (!bindless_blocks[k] || !bindless_blocks[k]->is_initialized()) { | |
| 2320 | bindless_ready = false; | |
| 2321 | break; | |
| 2322 | } | |
| 2323 | bindless_dirty = bindless_dirty || bindless_blocks[k]->needs_rebuild(); | |
| 2324 | } | |
| 2325 | ||
| 2326 | if (bindless_ready && bindless_dirty) { | |
| 2327 | Vector<RD::Uniform> bindless_uniforms; | |
| 2328 | for (uint32_t k = 0; k < BindlessBlock::TEXTURE_KIND_MAX; k++) { | |
| 2329 | bindless_blocks[k]->collect_uniform(bindless_uniforms); | |
| 2330 | } | |
| 2331 | ||
| 2332 | if (bindless_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(bindless_uniform_set)) { | |
| 2333 | RD::get_singleton()->free_rid(bindless_uniform_set); | |
| 2334 | } | |
| 2335 | bindless_uniform_set = RD::get_singleton()->uniform_set_create(bindless_uniforms, shader_rd, 1); | |
| 2336 | ||
| 2337 | if (bindless_uniform_set.is_valid()) { | |
| 2338 | RD::get_singleton()->set_resource_name(bindless_uniform_set, "Bindless Texture Set"); | |
| 2339 | for (uint32_t k = 0; k < BindlessBlock::TEXTURE_KIND_MAX; k++) { | |
| 2340 | bindless_blocks[k]->mark_rebuilt(); | |
| 2341 | } | |
| 2342 | } else { | |
| 2343 | ERR_PRINT_ONCE("RT: failed to create the bindless texture uniform set."); | |
| 2344 | } | |
| 2269 | 2345 | } |
| 2270 | 2346 | } |
| 2271 | 2347 |
| … | … | @@ -37,6 +37,7 @@ |
| 37 | 37 | #include "core/templates/vector.h" |
| 38 | 38 | #include "servers/rendering/renderer_rd/bindless_block.h" |
| 39 | 39 | #include "servers/rendering/rendering_device.h" |
| 40 | #include "servers/rendering/shader_language.h" | |
| 40 | 41 | |
| 41 | 42 | #define RB_TEX_RAYTRACING SNAME("raytracing") |
| 42 | 43 | #define RB_TEX_RT_DEPTH SNAME("rt_depth") |
| … | … | @@ -271,10 +272,19 @@ class RenderRaytracing { |
| 271 | 272 | RenderForwardClustered *owner = nullptr; |
| 272 | 273 | |
| 273 | 274 | SceneShaderRaytracing *shader = nullptr; |
| 274 | BindlessBlock *bindless_block = nullptr; | |
| 275 | ||
| 276 | // One bindless table per texture type, all sharing descriptor set 1. | |
| 277 | BindlessBlock *bindless_blocks[BindlessBlock::TEXTURE_KIND_MAX] = {}; | |
| 275 | 278 | |
| 276 | 279 | RID bindless_uniform_set; |
| 277 | 280 | |
| 281 | // Routes a shader uniform's sampler type to the table that can serve it. | |
| 282 | static BindlessBlock::TextureKind _texture_kind_for_type(ShaderLanguage::DataType p_type); | |
| 283 | ||
| 284 | _FORCE_INLINE_ BindlessBlock *_bindless(BindlessBlock::TextureKind p_kind) const { | |
| 285 | return bindless_blocks[p_kind]; | |
| 286 | } | |
| 287 | ||
| 278 | 288 | // Caching (chunked sparse caches indexed by RID low bits / 256). |
| 279 | 289 | Vector<RTCacheEntry *> surface_chunks; |
| 280 | 290 | Vector<RTMaterialCacheEntry *> material_chunks; |
| … | … | @@ -42,7 +42,13 @@ |
| 42 | 42 | |
| 43 | 43 | using namespace RendererSceneRenderImplementation; |
| 44 | 44 | |
| 45 | static void _dump_failed_shader(const String &p_source, const String &p_label) { | |
| 45 | static void _dump_failed_shader(const String &p_source, const String &p_label, const String &p_error) { | |
| 46 | // The compiler error is the only thing that identifies WHY the hit group failed; | |
| 47 | // without it a failed hit group is indistinguishable from one that was never | |
| 48 | // registered, and the level silently renders nothing. | |
| 49 | if (!p_error.is_empty()) { | |
| 50 | ERR_PRINT("RT shader compile failed (" + p_label + "): " + p_error); | |
| 51 | } | |
| 46 | 52 | String tmp_dir = OS::get_singleton()->get_temp_path(); |
| 47 | 53 | String path = tmp_dir.path_join("rt_shader_" + p_label + ".glsl"); |
| 48 | 54 | Ref<FileAccess> f = FileAccess::open(path, FileAccess::WRITE); |
| … | … | @@ -413,6 +419,26 @@ struct SceneShaderRaytracing::PipelineBuildTask { |
| 413 | 419 | WorkerThreadPool::TaskID worker_id = WorkerThreadPool::INVALID_TASK_ID; |
| 414 | 420 | }; |
| 415 | 421 | |
| 422 | const char *SceneShaderRaytracing::_bindless_table_name(ShaderLanguage::DataType p_type) { | |
| 423 | // Names must match the declarations in scene_raytracing_raygen.glsl. | |
| 424 | switch (p_type) { | |
| 425 | case ShaderLanguage::TYPE_SAMPLER2DARRAY: | |
| 426 | case ShaderLanguage::TYPE_ISAMPLER2DARRAY: | |
| 427 | case ShaderLanguage::TYPE_USAMPLER2DARRAY: | |
| 428 | return "bindless_textures_2d_array"; | |
| 429 | case ShaderLanguage::TYPE_SAMPLER3D: | |
| 430 | case ShaderLanguage::TYPE_ISAMPLER3D: | |
| 431 | case ShaderLanguage::TYPE_USAMPLER3D: | |
| 432 | return "bindless_textures_3d"; | |
| 433 | case ShaderLanguage::TYPE_SAMPLERCUBE: | |
| 434 | return "bindless_textures_cube"; | |
| 435 | case ShaderLanguage::TYPE_SAMPLERCUBEARRAY: | |
| 436 | return "bindless_textures_cube_array"; | |
| 437 | default: | |
| 438 | return "bindless_textures"; | |
| 439 | } | |
| 440 | } | |
| 441 | ||
| 416 | 442 | void SceneShaderRaytracing::_strip_texture_globals(String &r_globals, const String &p_tex_name) { |
| 417 | 443 | const String decl_marker = " m_" + p_tex_name + ";"; |
| 418 | 444 | int pos = r_globals.find(decl_marker); |
| … | … | @@ -469,6 +495,7 @@ void SceneShaderRaytracing::_finalize_uniforms_with_textures( |
| 469 | 495 | |
| 470 | 496 | TextureUniformInfo tui; |
| 471 | 497 | tui.name = tex.name; |
| 498 | tui.type = tex.type; | |
| 472 | 499 | tui.hint = tex.hint; |
| 473 | 500 | tui.use_color = tex.use_color; |
| 474 | 501 | tui.is_global = tex.global; |
| … | … | @@ -957,7 +984,12 @@ SceneShaderRaytracing::PipelineBuildTask *SceneShaderRaytracing::_make_pipeline_ |
| 957 | 984 | String tex_defines; |
| 958 | 985 | for (int ti = 0; ti < entry.texture_uniforms.size(); ti++) { |
| 959 | 986 | const TextureUniformInfo &tui = entry.texture_uniforms[ti]; |
| 960 | tex_defines += "#define m_" + tui.name + " bindless_textures[nonuniformEXT(material.m_" + tui.name + ")]\n"; | |
| 987 | // Must name the table whose element type matches the uniform's sampler | |
| 988 | // type. Pointing everything at bindless_textures (texture2D) makes a | |
| 989 | // sampler2DArray uniform generate sampler2DArray(texture2D, ...), which | |
| 990 | // glslang rejects -- taking the whole hit group down with it. | |
| 991 | tex_defines += "#define m_" + tui.name + " " + _bindless_table_name(tui.type) + | |
| 992 | "[nonuniformEXT(material.m_" + tui.name + ")]\n"; | |
| 961 | 993 | } |
| 962 | 994 | String uniform_members = entry.uniform_members.is_empty() ? String("float _rt_pad;") : entry.uniform_members; |
| 963 | 995 | |
| … | … | @@ -1090,11 +1122,12 @@ void SceneShaderRaytracing::_build_pipeline_worker(PipelineBuildTask *p_task) { |
| 1090 | 1122 | Vector<uint8_t> ch_spirv; |
| 1091 | 1123 | { |
| 1092 | 1124 | MutexLock lock(spirv_compile_mutex); |
| 1125 | error = String(); | |
| 1093 | 1126 | ch_spirv = RD::get_singleton()->shader_compile_spirv_from_source( |
| 1094 | 1127 | RD::SHADER_STAGE_CLOSEST_HIT, si.ch_src, RD::SHADER_LANGUAGE_GLSL, &error); |
| 1095 | 1128 | } |
| 1096 | 1129 | if (ch_spirv.is_empty()) { |
| 1097 | _dump_failed_shader(si.ch_src, vformat("hg%d_v%x_closest_hit", i, p_task->rt_flags)); | |
| 1130 | _dump_failed_shader(si.ch_src, vformat("hg%d_v%x_closest_hit", i, p_task->rt_flags), error); | |
| 1098 | 1131 | p_task->new_per_hg_shaders[i] = RID(); |
| 1099 | 1132 | p_task->new_ready_mask[i] = false; |
| 1100 | 1133 | continue; |
| … | … | @@ -1110,11 +1143,12 @@ void SceneShaderRaytracing::_build_pipeline_worker(PipelineBuildTask *p_task) { |
| 1110 | 1143 | Vector<uint8_t> ah_spirv; |
| 1111 | 1144 | { |
| 1112 | 1145 | MutexLock lock(spirv_compile_mutex); |
| 1146 | error = String(); | |
| 1113 | 1147 | ah_spirv = RD::get_singleton()->shader_compile_spirv_from_source( |
| 1114 | 1148 | RD::SHADER_STAGE_ANY_HIT, si.ah_src, RD::SHADER_LANGUAGE_GLSL, &error); |
| 1115 | 1149 | } |
| 1116 | 1150 | if (ah_spirv.is_empty()) { |
| 1117 | _dump_failed_shader(si.ah_src, vformat("hg%d_v%x_any_hit", i, p_task->rt_flags)); | |
| 1151 | _dump_failed_shader(si.ah_src, vformat("hg%d_v%x_any_hit", i, p_task->rt_flags), error); | |
| 1118 | 1152 | p_task->new_per_hg_shaders[i] = RID(); |
| 1119 | 1153 | p_task->new_ready_mask[i] = false; |
| 1120 | 1154 | continue; |
| … | … | @@ -1131,11 +1165,12 @@ void SceneShaderRaytracing::_build_pipeline_worker(PipelineBuildTask *p_task) { |
| 1131 | 1165 | Vector<uint8_t> is_spirv; |
| 1132 | 1166 | { |
| 1133 | 1167 | MutexLock lock(spirv_compile_mutex); |
| 1168 | error = String(); | |
| 1134 | 1169 | is_spirv = RD::get_singleton()->shader_compile_spirv_from_source( |
| 1135 | 1170 | RD::SHADER_STAGE_INTERSECTION, si.is_src, RD::SHADER_LANGUAGE_GLSL, &error); |
| 1136 | 1171 | } |
| 1137 | 1172 | if (is_spirv.is_empty()) { |
| 1138 | _dump_failed_shader(si.is_src, vformat("hg%d_v%x_intersection", i, p_task->rt_flags)); | |
| 1173 | _dump_failed_shader(si.is_src, vformat("hg%d_v%x_intersection", i, p_task->rt_flags), error); | |
| 1139 | 1174 | p_task->new_per_hg_shaders[i] = RID(); |
| 1140 | 1175 | p_task->new_ready_mask[i] = false; |
| 1141 | 1176 | continue; |
| … | … | @@ -343,6 +343,9 @@ public: |
| 343 | 343 | |
| 344 | 344 | struct TextureUniformInfo { |
| 345 | 345 | StringName name; |
| 346 | // Selects which bindless table the index refers to. A sampler2DArray | |
| 347 | // cannot be constructed from a texture2D, so the table must match. | |
| 348 | ShaderLanguage::DataType type = ShaderLanguage::TYPE_SAMPLER2D; | |
| 346 | 349 | ShaderLanguage::ShaderNode::Uniform::Hint hint = ShaderLanguage::ShaderNode::Uniform::HINT_NONE; |
| 347 | 350 | bool use_color = false; |
| 348 | 351 | bool is_global = false; |
| … | … | @@ -360,7 +363,7 @@ public: |
| 360 | 363 | uint32_t uniform_total_size = 0; |
| 361 | 364 | Vector<uint32_t> uniform_offsets; |
| 362 | 365 | HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms; |
| 363 | Vector<TextureUniformInfo> texture_uniforms; // Sampler2D packed as bindless indices after UBO | |
| 366 | Vector<TextureUniformInfo> texture_uniforms; // Samplers packed as bindless indices after UBO | |
| 364 | 367 | bool uses_alpha_clip = false; // Writes ALPHA_SCISSOR_THRESHOLD; needs per-HG any-hit |
| 365 | 368 | bool is_procedural = false; // Uses intersection shader instead of triangle geometry |
| 366 | 369 | }; |
| … | … | @@ -448,6 +451,7 @@ private: |
| 448 | 451 | // Source-hash slot management. |
| 449 | 452 | uint32_t _register_slot(uint32_t p_shader_id, RID p_material, bool p_is_procedural); |
| 450 | 453 | bool _preprocess_shader(RID p_material, bool p_is_procedural, CustomShaderEntry &r_entry); |
| 454 | static const char *_bindless_table_name(ShaderLanguage::DataType p_type); | |
| 451 | 455 | void _strip_texture_globals(String &r_globals, const String &p_tex_name); |
| 452 | 456 | void _finalize_uniforms_with_textures(CustomShaderEntry &r_entry, const ShaderCompiler::GeneratedCode &p_gen_code, const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms, bool p_strip_intersection_globals); |
| 453 | 457 |
| … | … | @@ -259,7 +259,21 @@ void main() { |
| 259 | 259 | layout(set = 0, binding = 1) uniform accelerationStructureEXT tlas; |
| 260 | 260 | layout(location = 0) rayPayloadInEXT PathPayload payload; |
| 261 | 261 | |
| 262 | layout(set = 1, binding = 0) uniform texture2D bindless_textures[]; | |
| 262 | // Bindless texture tables. One per texture type: GLSL cannot build a | |
| 263 | // sampler2DArray/sampler3D/samplerCube from a texture2D, so a single 2D table | |
| 264 | // makes every array/volume/cube uniform generate an invalid sampler constructor | |
| 265 | // and fails the whole hit group. | |
| 266 | // | |
| 267 | // Only ONE binding per set may be variable-count, and it must be the one with | |
| 268 | // the LARGEST binding number, so the typed tables are fixed-capacity here and | |
| 269 | // the 2D table -- the only one that needs to scale -- stays unbounded and last. | |
| 270 | // Keep these sizes in sync with BindlessBlock::TYPED_TABLE_CAPACITY, and the | |
| 271 | // binding numbers with BindlessBlock::TextureKind. | |
| 272 | layout(set = 1, binding = 0) uniform texture2DArray bindless_textures_2d_array[256]; | |
| 273 | layout(set = 1, binding = 1) uniform texture3D bindless_textures_3d[256]; | |
| 274 | layout(set = 1, binding = 2) uniform textureCube bindless_textures_cube[256]; | |
| 275 | layout(set = 1, binding = 3) uniform textureCubeArray bindless_textures_cube_array[256]; | |
| 276 | layout(set = 1, binding = 4) uniform texture2D bindless_textures[]; | |
| 263 | 277 | |
| 264 | 278 | #include "raytracing_samplers_inc.glsl" |
| 265 | 279 | |
| … | … | @@ -449,7 +463,21 @@ layout(set = 0, binding = 5, std430) readonly buffer MaterialBuffer { |
| 449 | 463 | }; |
| 450 | 464 | // clang-format on |
| 451 | 465 | |
| 452 | layout(set = 1, binding = 0) uniform texture2D bindless_textures[]; | |
| 466 | // Bindless texture tables. One per texture type: GLSL cannot build a | |
| 467 | // sampler2DArray/sampler3D/samplerCube from a texture2D, so a single 2D table | |
| 468 | // makes every array/volume/cube uniform generate an invalid sampler constructor | |
| 469 | // and fails the whole hit group. | |
| 470 | // | |
| 471 | // Only ONE binding per set may be variable-count, and it must be the one with | |
| 472 | // the LARGEST binding number, so the typed tables are fixed-capacity here and | |
| 473 | // the 2D table -- the only one that needs to scale -- stays unbounded and last. | |
| 474 | // Keep these sizes in sync with BindlessBlock::TYPED_TABLE_CAPACITY, and the | |
| 475 | // binding numbers with BindlessBlock::TextureKind. | |
| 476 | layout(set = 1, binding = 0) uniform texture2DArray bindless_textures_2d_array[256]; | |
| 477 | layout(set = 1, binding = 1) uniform texture3D bindless_textures_3d[256]; | |
| 478 | layout(set = 1, binding = 2) uniform textureCube bindless_textures_cube[256]; | |
| 479 | layout(set = 1, binding = 3) uniform textureCubeArray bindless_textures_cube_array[256]; | |
| 480 | layout(set = 1, binding = 4) uniform texture2D bindless_textures[]; | |
| 453 | 481 | |
| 454 | 482 | #include "raytracing_samplers_inc.glsl" |
| 455 | 483 | |
| … | … | @@ -568,7 +596,21 @@ layout(set = 0, binding = 5, std430) readonly buffer MaterialBuffer { |
| 568 | 596 | }; |
| 569 | 597 | // clang-format on |
| 570 | 598 | |
| 571 | layout(set = 1, binding = 0) uniform texture2D bindless_textures[]; | |
| 599 | // Bindless texture tables. One per texture type: GLSL cannot build a | |
| 600 | // sampler2DArray/sampler3D/samplerCube from a texture2D, so a single 2D table | |
| 601 | // makes every array/volume/cube uniform generate an invalid sampler constructor | |
| 602 | // and fails the whole hit group. | |
| 603 | // | |
| 604 | // Only ONE binding per set may be variable-count, and it must be the one with | |
| 605 | // the LARGEST binding number, so the typed tables are fixed-capacity here and | |
| 606 | // the 2D table -- the only one that needs to scale -- stays unbounded and last. | |
| 607 | // Keep these sizes in sync with BindlessBlock::TYPED_TABLE_CAPACITY, and the | |
| 608 | // binding numbers with BindlessBlock::TextureKind. | |
| 609 | layout(set = 1, binding = 0) uniform texture2DArray bindless_textures_2d_array[256]; | |
| 610 | layout(set = 1, binding = 1) uniform texture3D bindless_textures_3d[256]; | |
| 611 | layout(set = 1, binding = 2) uniform textureCube bindless_textures_cube[256]; | |
| 612 | layout(set = 1, binding = 3) uniform textureCubeArray bindless_textures_cube_array[256]; | |
| 613 | layout(set = 1, binding = 4) uniform texture2D bindless_textures[]; | |
| 572 | 614 | |
| 573 | 615 | #include "raytracing_samplers_inc.glsl" |
| 574 | 616 |