From 1d8dc0ddced34f6ab7fe7bc9600606e1b8359c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Sat, 14 Oct 2023 17:07:38 +0200 Subject: [PATCH 01/23] Add MSL imageAtomicXor support --- .../gpu/shaders/metal/mtl_shader_defines.msl | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl index 549749237b5..55b44ac4a19 100644 --- a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl +++ b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl @@ -982,6 +982,49 @@ inline void _texture_write_internal_fast(thread _mtl_combined_image_sampler_3d +S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_1d tex, + int coord, + S data) +{ + return tex.texture->atomic_fetch_xor(uint(coord), vec(data)).x; +} + +template +S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_1d_array tex, + int2 coord, + S data) +{ + return tex.texture->atomic_fetch_xor(uint(coord.x), uint(coord.y), vec(data)).x; +} + +template +S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_2d tex, + int2 coord, + S data) +{ + return tex.texture->atomic_fetch_xor(uint2(coord.xy), vec(data)).x; +} + +template +S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_2d_array tex, + int3 coord, + S data) +{ + return tex.texture->atomic_fetch_xor(uint2(coord.xy), uint(coord.z), vec(data)).x; +} + +template +S _texture_image_atomic_xor_internal(thread _mtl_combined_image_sampler_3d tex, + int3 coord, + S data) +{ + return tex.texture->atomic_fetch_xor(uint3(coord), vec(data)).x; +} /* Atomic Min. */ template -- 2.30.2 From 2249d08d1fcc74c8e3e495bed5b4902a59874a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Sat, 14 Oct 2023 17:16:18 +0200 Subject: [PATCH 02/23] EEVEE-Next: Add occupancy map for volumes --- source/blender/draw/CMakeLists.txt | 1 + .../draw/engines/eevee_next/eevee_defines.hh | 1 + .../draw/engines/eevee_next/eevee_material.cc | 16 ++++++--- .../draw/engines/eevee_next/eevee_material.hh | 15 ++++++-- .../draw/engines/eevee_next/eevee_pipeline.cc | 25 ++++++++++--- .../draw/engines/eevee_next/eevee_pipeline.hh | 6 +++- .../draw/engines/eevee_next/eevee_shader.cc | 8 ++--- .../draw/engines/eevee_next/eevee_sync.cc | 6 ++-- .../draw/engines/eevee_next/eevee_volume.cc | 9 ++++- .../draw/engines/eevee_next/eevee_volume.hh | 8 +++++ .../draw/engines/eevee_next/eevee_world.cc | 5 +-- .../shaders/eevee_nodetree_lib.glsl | 2 +- .../eevee_next/shaders/eevee_surf_lib.glsl | 6 ++-- .../shaders/eevee_surf_occupancy_frag.glsl | 36 +++++++++++++++++++ .../shaders/infos/eevee_material_info.hh | 8 +++++ 15 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 6ea1a0aadc3..50cc078f8bc 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -575,6 +575,7 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_surf_depth_frag.glsl engines/eevee_next/shaders/eevee_surf_forward_frag.glsl engines/eevee_next/shaders/eevee_surf_lib.glsl + engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl engines/eevee_next/shaders/eevee_surf_shadow_frag.glsl engines/eevee_next/shaders/eevee_shadow_page_tile_vert.glsl engines/eevee_next/shaders/eevee_shadow_page_tile_frag.glsl diff --git a/source/blender/draw/engines/eevee_next/eevee_defines.hh b/source/blender/draw/engines/eevee_next/eevee_defines.hh index bef61f1848f..0b54d4e2745 100644 --- a/source/blender/draw/engines/eevee_next/eevee_defines.hh +++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh @@ -184,6 +184,7 @@ #define VOLUME_PROP_EXTINCTION_IMG_SLOT 1 #define VOLUME_PROP_EMISSION_IMG_SLOT 2 #define VOLUME_PROP_PHASE_IMG_SLOT 3 +#define VOLUME_OCCUPANCY_SLOT 4 /* Only during shadow rendering. */ #define SHADOW_ATLAS_IMG_SLOT 4 diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 512c45b057e..766211a0f97 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -241,10 +241,11 @@ Material &MaterialModule::material_sync(Object *ob, bool has_motion) { if (geometry_type == MAT_GEOM_VOLUME_OBJECT) { - MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME); + MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME_PREPASS); return material_map_.lookup_or_add_cb(material_key, [&]() { Material mat = {}; - mat.volume = material_pass_get(ob, blender_mat, MAT_PIPE_VOLUME, MAT_GEOM_VOLUME_OBJECT); + mat.volume_prepass = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_PREPASS, MAT_GEOM_VOLUME_OBJECT); return mat; }); } @@ -271,7 +272,8 @@ Material &MaterialModule::material_sync(Object *ob, mat.reflection_probe_shading = MaterialPass(); mat.planar_probe_prepass = MaterialPass(); mat.planar_probe_shading = MaterialPass(); - mat.volume = MaterialPass(); + mat.volume_occupancy = MaterialPass(); + mat.volume_prepass = MaterialPass(); } else { /* Order is important for transparent. */ @@ -297,10 +299,14 @@ Material &MaterialModule::material_sync(Object *ob, } if (GPU_material_has_volume_output(mat.shading.gpumat)) { - mat.volume = material_pass_get(ob, blender_mat, MAT_PIPE_VOLUME, MAT_GEOM_VOLUME_OBJECT); + mat.volume_occupancy = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, MAT_GEOM_VOLUME_OBJECT); + mat.volume_prepass = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_PREPASS, MAT_GEOM_VOLUME_OBJECT); } else { - mat.volume = MaterialPass(); + mat.volume_occupancy = MaterialPass(); + mat.volume_prepass = MaterialPass(); } } diff --git a/source/blender/draw/engines/eevee_next/eevee_material.hh b/source/blender/draw/engines/eevee_next/eevee_material.hh index bc8e109046a..a7ebde305b6 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.hh +++ b/source/blender/draw/engines/eevee_next/eevee_material.hh @@ -32,7 +32,8 @@ enum eMaterialPipeline { MAT_PIPE_DEFERRED_PREPASS_VELOCITY, MAT_PIPE_FORWARD_PREPASS, MAT_PIPE_FORWARD_PREPASS_VELOCITY, - MAT_PIPE_VOLUME, + MAT_PIPE_VOLUME_PREPASS, + MAT_PIPE_VOLUME_OCCUPANCY, MAT_PIPE_SHADOW, MAT_PIPE_CAPTURE, MAT_PIPE_PLANAR_PREPASS, @@ -227,8 +228,16 @@ struct MaterialPass { struct Material { bool is_alpha_blend_transparent; - MaterialPass shadow, shading, prepass, capture, reflection_probe_prepass, - reflection_probe_shading, planar_probe_prepass, planar_probe_shading, volume; + MaterialPass shadow; + MaterialPass shading; + MaterialPass prepass; + MaterialPass capture; + MaterialPass reflection_probe_prepass; + MaterialPass reflection_probe_shading; + MaterialPass planar_probe_prepass; + MaterialPass planar_probe_shading; + MaterialPass volume_occupancy; + MaterialPass volume_prepass; }; struct MaterialArray { diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 0d61eaa71e5..7a694469f43 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -761,11 +761,25 @@ void DeferredPipeline::render(View &main_view, void VolumePipeline::sync() { - volume_ps_.init(); - volume_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); - inst_.bind_uniform_data(&volume_ps_); - inst_.volume.bind_properties_buffers(volume_ps_); - inst_.sampling.bind_resources(volume_ps_); + { + draw::PassMain &pass = occupancy_ps_; + pass.init(); + inst_.bind_uniform_data(&pass); + inst_.volume.bind_properties_buffers(pass); + } + { + draw::PassMain &pass = volume_ps_; + pass.init(); + pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); + inst_.bind_uniform_data(&pass); + inst_.volume.bind_properties_buffers(pass); + inst_.sampling.bind_resources(pass); + } +} + +PassMain::Sub *VolumePipeline::volume_occupancy_add(GPUMaterial *gpumat) +{ + return &occupancy_ps_.sub(GPU_material_get_name(gpumat)); } PassMain::Sub *VolumePipeline::volume_material_add(GPUMaterial *gpumat) @@ -775,6 +789,7 @@ PassMain::Sub *VolumePipeline::volume_material_add(GPUMaterial *gpumat) void VolumePipeline::render(View &view) { + inst_.manager->submit(occupancy_ps_, view); inst_.manager->submit(volume_ps_, view); } diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index 27390a90900..3c2447e1f69 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -278,11 +278,13 @@ class VolumePipeline { private: Instance &inst_; + PassMain occupancy_ps_ = {"Volume.Occupancy"}; PassMain volume_ps_ = {"Volume.Objects"}; public: VolumePipeline(Instance &inst) : inst_(inst){}; + PassMain::Sub *volume_occupancy_add(GPUMaterial *gpumat); PassMain::Sub *volume_material_add(GPUMaterial *gpumat); void sync(); @@ -562,7 +564,9 @@ class PipelineModule { return forward.material_transparent_add(ob, blender_mat, gpumat); } return forward.material_opaque_add(blender_mat, gpumat); - case MAT_PIPE_VOLUME: + case MAT_PIPE_VOLUME_OCCUPANCY: + return volume.volume_occupancy_add(gpumat); + case MAT_PIPE_VOLUME_PREPASS: return volume.volume_material_add(gpumat); case MAT_PIPE_SHADOW: return shadow.surface_material_add(gpumat); diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index d9f5a5c6aac..1d392198aaa 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -461,7 +461,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu std::stringstream vert_gen, frag_gen, comp_gen; - bool is_compute = pipeline_type == MAT_PIPE_VOLUME; + bool is_compute = pipeline_type == MAT_PIPE_VOLUME_PREPASS; if (do_vertex_attrib_load) { vert_gen << global_vars.str() << attr_load.str(); @@ -613,7 +613,7 @@ GPUMaterial *ShaderModule::material_shader_get(::Material *blender_mat, eMaterialGeometry geometry_type, bool deferred_compilation) { - bool is_volume = (pipeline_type == MAT_PIPE_VOLUME); + bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_PREPASS); uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type); @@ -625,7 +625,7 @@ GPUMaterial *ShaderModule::world_shader_get(::World *blender_world, bNodeTree *nodetree, eMaterialPipeline pipeline_type) { - bool is_volume = (pipeline_type == MAT_PIPE_VOLUME); + bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_PREPASS); bool defer_compilation = is_volume; eMaterialGeometry geometry_type = is_volume ? MAT_GEOM_VOLUME_WORLD : MAT_GEOM_WORLD; @@ -647,7 +647,7 @@ GPUMaterial *ShaderModule::material_shader_get(const char *name, { uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type); - bool is_volume = (pipeline_type == MAT_PIPE_VOLUME); + bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_PREPASS); GPUMaterial *gpumat = GPU_material_from_nodetree(nullptr, nullptr, diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.cc b/source/blender/draw/engines/eevee_next/eevee_sync.cc index c6d7f3a68d0..58d82aca879 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sync.cc @@ -153,9 +153,11 @@ void SyncModule::sync_mesh(Object *ob, Material &material = material_array.materials[i]; GPUMaterial *gpu_material = material_array.gpu_materials[i]; - if (material.volume.gpumat && i == 0) { + geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); + + if (material.volume_prepass.gpumat && i == 0) { /* Only support single volume material for now. */ - inst_.volume.sync_object(ob, ob_handle, res_handle, &material.volume); + inst_.volume.sync_object(ob, ob_handle, res_handle, &material.volume_prepass); /* Do not render surface if we are rendering a volume object * and do not have a surface closure. */ if (gpu_material && !GPU_material_has_surface_output(gpu_material)) { diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index f503c98831b..3ed0cac3d98 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -167,7 +167,7 @@ void VolumeModule::sync_object(Object *ob, if (material_pass == nullptr) { Material material = inst_.materials.material_get( ob, false, VOLUME_MATERIAL_NR, MAT_GEOM_VOLUME_OBJECT); - material_pass = &material.volume; + material_pass = &material.volume_prepass; } /* If shader failed to compile or is currently compiling. */ @@ -218,6 +218,7 @@ void VolumeModule::sync_object(Object *ob, void VolumeModule::end_sync() { if (!enabled_) { + occupancy_tx_.free(); prop_scattering_tx_.free(); prop_extinction_tx_.free(); prop_emission_tx_.free(); @@ -233,6 +234,11 @@ void VolumeModule::end_sync() return; } + int occupancy_layers = divide_ceil_u(data_.tex_size.z, 32u); + eGPUTextureUsage occupancy_usage = GPU_TEXTURE_USAGE_SHADER_READ | + GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; + occupancy_tx_.ensure_3d(GPU_R32UI, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage); + eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATTACHMENT; @@ -305,6 +311,7 @@ void VolumeModule::draw_prepass(View &view) if (!enabled_) { return; } + occupancy_tx_.clear(uint4(0)); inst_.pipelines.world_volume.render(view); inst_.pipelines.volume.render(view); diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.hh b/source/blender/draw/engines/eevee_next/eevee_volume.hh index d3dfd4ec07d..ca9111d9bac 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.hh +++ b/source/blender/draw/engines/eevee_next/eevee_volume.hh @@ -49,6 +49,13 @@ class VolumeModule { VolumesInfoData &data_; + /** + * Occupancy map that allows to fill froxels that are inside the geometry. + * It is filled during a pre-pass using atomic operations. + * Using a 3D bitfield, we only allocate one bit per froxel. + */ + Texture occupancy_tx_ = {"occupancy_tx_"}; + /* Material Parameters */ Texture prop_scattering_tx_; Texture prop_extinction_tx_; @@ -114,6 +121,7 @@ class VolumeModule { pass.bind_image(VOLUME_PROP_EXTINCTION_IMG_SLOT, &prop_extinction_tx_); pass.bind_image(VOLUME_PROP_EMISSION_IMG_SLOT, &prop_emission_tx_); pass.bind_image(VOLUME_PROP_PHASE_IMG_SLOT, &prop_phase_tx_); + pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_); } bool needs_shadow_tagging() diff --git a/source/blender/draw/engines/eevee_next/eevee_world.cc b/source/blender/draw/engines/eevee_next/eevee_world.cc index 3c92047213b..0c1ef2681ec 100644 --- a/source/blender/draw/engines/eevee_next/eevee_world.cc +++ b/source/blender/draw/engines/eevee_next/eevee_world.cc @@ -94,7 +94,8 @@ void World::sync() bNodeTree *ntree; world_and_ntree_get(bl_world, ntree); - GPUMaterial *volume_gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME); + GPUMaterial *volume_gpumat = inst_.shaders.world_shader_get( + bl_world, ntree, MAT_PIPE_VOLUME_PREPASS); inst_.pipelines.world_volume.sync(volume_gpumat); if (inst_.lookdev.sync_world()) { @@ -128,7 +129,7 @@ bool World::has_volume() bNodeTree *ntree; world_and_ntree_get(bl_world, ntree); - GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME); + GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME_PREPASS); return GPU_material_has_volume_output(gpumat); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl index 9561b9f673a..d1e29468f2e 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_nodetree_lib.glsl @@ -312,7 +312,7 @@ void brdf_f82_tint_lut(vec3 F0, #ifdef EEVEE_UTILITY_TX vec3 split_sum = utility_tx_sample_lut(utility_tx, cos_theta, roughness, UTIL_BSDF_LAYER).rgb; #else - vec3 split_sum = vec2(1.0, 0.0, 0.0); + vec3 split_sum = vec3(1.0, 0.0, 0.0); #endif reflectance = do_multiscatter ? F_brdf_multi_scatter(F0, vec3(1.0), split_sum.xy) : diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl index ef831a91a6e..6c1c5579bad 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl @@ -59,14 +59,16 @@ void init_globals_curves() float cos_theta = curve_interp.time_width / curve_interp.thickness; # if defined(GPU_FRAGMENT_SHADER) if (hairThicknessRes == 1) { +# ifdef EEVEE_UTILITY_TX /* Random cosine normal distribution on the hair surface. */ float noise = utility_tx_fetch(utility_tx, gl_FragCoord.xy, UTIL_BLUE_NOISE_LAYER).x; -# ifdef EEVEE_SAMPLING_DATA +# ifdef EEVEE_SAMPLING_DATA /* Needs to check for SAMPLING_DATA, * otherwise Surfel and World (?!?!) shader validation fails. */ noise = fract(noise + sampling_rng_1D_get(SAMPLING_CURVES_U)); -# endif +# endif cos_theta = noise * 2.0 - 1.0; +# endif } # endif float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta)); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl new file mode 100644 index 00000000000..66f899ec024 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -0,0 +1,36 @@ +/* SPDX-FileCopyrightText: 2017-2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** + * Prepass that voxelizes an object on frustum aligned voxels. + */ + +#pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_transparency_lib.glsl) + +vec4 closure_to_rgba(Closure cl) +{ + return vec4(0.0); +} + +void main() +{ + ivec2 texel = ivec2(gl_FragCoord.xy); + vec2 uv = gl_FragCoord.xy / vec2(imageSize(occupancy_img).xy); + vec3 ss_P = vec3(uv, gl_FragCoord.z); + + float volume_z = screen_to_volume(drw_ndc_to_screen(ss_P)).z; + /* TODO(fclem): Check if this quatization is good. */ + int volume_bit = int(volume_z * uniform_buf.volumes.tex_size.z); + + for (int i = 0; i < imageSize(occupancy_img).z; i++) { + uint shift = clamp(volume_bit - i * 32u, 0u, 32u); + uint occupancy_bits = 0xFFFFFFFFu << shift; + imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits); + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 5c749096e91..4e85cd1f00f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -306,6 +306,13 @@ GPU_SHADER_CREATE_INFO(eevee_volume_world) .define("MAT_GEOM_VOLUME_WORLD") .additional_info("eevee_volume_material_common"); +GPU_SHADER_CREATE_INFO(eevee_surf_occupancy) + .define("MAT_OCCUPANCY") + .builtins(BuiltinBits::TEXTURE_ATOMIC) + .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_3D, "occupancy_img") + .fragment_source("eevee_surf_occupancy_frag.glsl") + .additional_info("eevee_global_ubo"); + #if 0 /* TODO */ GPU_SHADER_INTERFACE_INFO(eevee_volume_iface, "interp") .smooth(Type::VEC3, "P_start") @@ -356,6 +363,7 @@ GPU_SHADER_CREATE_INFO(eevee_material_stub) EEVEE_MAT_GEOM_VARIATIONS(name##_deferred, "eevee_surf_deferred", __VA_ARGS__) \ EEVEE_MAT_GEOM_VARIATIONS(name##_forward, "eevee_surf_forward", __VA_ARGS__) \ EEVEE_MAT_GEOM_VARIATIONS(name##_capture, "eevee_surf_capture", __VA_ARGS__) \ + EEVEE_MAT_GEOM_VARIATIONS(name##_occupancy, "eevee_surf_occupancy", __VA_ARGS__) \ EEVEE_MAT_GEOM_VARIATIONS(name##_shadow_atomic, "eevee_surf_shadow_atomic", __VA_ARGS__) \ EEVEE_MAT_GEOM_VARIATIONS(name##_shadow_tbdr, "eevee_surf_shadow_tbdr", __VA_ARGS__) -- 2.30.2 From 99afe8aaf2bd6167154bca0a5170ea687fc3375a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sat, 14 Oct 2023 20:06:09 +0200 Subject: [PATCH 03/23] Make the bulk of the algorithm work --- .../draw/engines/eevee_next/eevee_material.cc | 2 +- .../draw/engines/eevee_next/eevee_pipeline.cc | 2 ++ .../draw/engines/eevee_next/eevee_shader.cc | 3 +++ .../draw/engines/eevee_next/eevee_volume.cc | 6 ++++- .../draw/engines/eevee_next/eevee_volume.hh | 2 ++ .../eevee_next/shaders/eevee_surf_lib.glsl | 2 +- .../shaders/eevee_surf_occupancy_frag.glsl | 15 ++++++++---- .../shaders/eevee_volume_material_comp.glsl | 12 ++++++++++ .../shaders/infos/eevee_material_info.hh | 24 +++++-------------- 9 files changed, 42 insertions(+), 26 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 766211a0f97..0a9832238d1 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -300,7 +300,7 @@ Material &MaterialModule::material_sync(Object *ob, if (GPU_material_has_volume_output(mat.shading.gpumat)) { mat.volume_occupancy = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, MAT_GEOM_VOLUME_OBJECT); + ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, geometry_type); mat.volume_prepass = material_pass_get( ob, blender_mat, MAT_PIPE_VOLUME_PREPASS, MAT_GEOM_VOLUME_OBJECT); } diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 7a694469f43..6684a0706fe 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -764,6 +764,8 @@ void VolumePipeline::sync() { draw::PassMain &pass = occupancy_ps_; pass.init(); + /* Double sided without depth test. */ + pass.state_set(DRW_STATE_WRITE_DEPTH); inst_.bind_uniform_data(&pass); inst_.volume.bind_properties_buffers(pass); } diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index 1d392198aaa..cf2fd5a78bc 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -583,6 +583,9 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu } break; } break; + case MAT_PIPE_VOLUME_OCCUPANCY: + info.additional_info("eevee_surf_occupancy"); + break; case MAT_PIPE_CAPTURE: info.additional_info("eevee_surf_capture"); break; diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index 3ed0cac3d98..b2cd5b692c6 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -238,6 +238,8 @@ void VolumeModule::end_sync() eGPUTextureUsage occupancy_usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; occupancy_tx_.ensure_3d(GPU_R32UI, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage); + /* Empty framebuffer. */ + occupancy_fb_.ensure(data_.tex_size.xy()); eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATTACHMENT; @@ -311,9 +313,11 @@ void VolumeModule::draw_prepass(View &view) if (!enabled_) { return; } - occupancy_tx_.clear(uint4(0)); inst_.pipelines.world_volume.render(view); + + occupancy_tx_.clear(uint4(0u)); + occupancy_fb_.bind(); inst_.pipelines.volume.render(view); } diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.hh b/source/blender/draw/engines/eevee_next/eevee_volume.hh index ca9111d9bac..ddee777f374 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.hh +++ b/source/blender/draw/engines/eevee_next/eevee_volume.hh @@ -55,6 +55,8 @@ class VolumeModule { * Using a 3D bitfield, we only allocate one bit per froxel. */ Texture occupancy_tx_ = {"occupancy_tx_"}; + /** Empty framebuffer for occupancy pass. */ + Framebuffer occupancy_fb_ = {"occupancy_fb_"}; /* Material Parameters */ Texture prop_scattering_tx_; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl index 6c1c5579bad..30871cd7d05 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_lib.glsl @@ -21,7 +21,7 @@ vec3 barycentric_distances_get() dists.x = rate_of_change * (1.0 - gpu_BaryCoord.x); dists.y = rate_of_change * (1.0 - gpu_BaryCoord.y); dists.z = rate_of_change * (1.0 - gpu_BaryCoord.z); -# elif +# else /* NOTE: No need to undo perspective divide since it has not been applied. */ vec3 pos0 = (ProjectionMatrixInverse * gpu_position_at_vertex(0)).xyz; vec3 pos1 = (ProjectionMatrixInverse * gpu_position_at_vertex(1)).xyz; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index 66f899ec024..fe71d1c45cf 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -24,13 +24,18 @@ void main() vec2 uv = gl_FragCoord.xy / vec2(imageSize(occupancy_img).xy); vec3 ss_P = vec3(uv, gl_FragCoord.z); - float volume_z = screen_to_volume(drw_ndc_to_screen(ss_P)).z; - /* TODO(fclem): Check if this quatization is good. */ + float volume_z = screen_to_volume(ss_P).z; + /* TODO(fclem): Check if this quantization is good. */ int volume_bit = int(volume_z * uniform_buf.volumes.tex_size.z); for (int i = 0; i < imageSize(occupancy_img).z; i++) { - uint shift = clamp(volume_bit - i * 32u, 0u, 32u); - uint occupancy_bits = 0xFFFFFFFFu << shift; - imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits); + int shift = volume_bit - i * 32; + if (shift < 32) { + uint occupancy_bits = 0xFFFFFFFFu; + if (shift > 0) { + occupancy_bits >>= uint(shift); + } + imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits); + } } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl index bcefa424b31..1f9b26f09d5 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl @@ -49,6 +49,18 @@ void main() return; } +#ifdef MAT_GEOM_VOLUME_OBJECT + /** Check occupancy map. Discard thread if froxel is empty. */ + /* Shift for 32bits per layer. Avoid integer modulo and division. */ + const int shift = 5; + const int mask = int(0xFFFFFFFFu << 5u); + uint occupancy_bits = imageLoad(occupancy_img, ivec3(froxel.xy, froxel.z >> shift)).r; + uint occupancy_bit = occupancy_bits >> (froxel.z & mask); + if (occupancy_bit == 0u) { + return; + } +#endif + vec3 jitter = sampling_rng_3D_get(SAMPLING_VOLUME_U); vec3 ndc_cell = volume_to_screen((vec3(froxel) + jitter) * uniform_buf.volumes.inv_tex_size); diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 4e85cd1f00f..3886e94e28a 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -280,6 +280,7 @@ GPU_SHADER_CREATE_INFO(eevee_volume_object) Qualifier::READ_WRITE, ImageType::FLOAT_3D, "out_phase_img") + .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::READ, ImageType::UINT_3D, "occupancy_img") .additional_info("eevee_volume_material_common", "draw_object_infos_new", "draw_volume_infos"); GPU_SHADER_CREATE_INFO(eevee_volume_world) @@ -309,27 +310,14 @@ GPU_SHADER_CREATE_INFO(eevee_volume_world) GPU_SHADER_CREATE_INFO(eevee_surf_occupancy) .define("MAT_OCCUPANCY") .builtins(BuiltinBits::TEXTURE_ATOMIC) - .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::WRITE, ImageType::UINT_3D, "occupancy_img") + .image(VOLUME_OCCUPANCY_SLOT, + GPU_R32UI, + Qualifier::READ_WRITE, + ImageType::UINT_3D, + "occupancy_img") .fragment_source("eevee_surf_occupancy_frag.glsl") .additional_info("eevee_global_ubo"); -#if 0 /* TODO */ -GPU_SHADER_INTERFACE_INFO(eevee_volume_iface, "interp") - .smooth(Type::VEC3, "P_start") - .smooth(Type::VEC3, "P_end"); - -GPU_SHADER_CREATE_INFO(eevee_volume_deferred) - .sampler(0, ImageType::DEPTH_2D, "depth_max_tx") - .vertex_in(0, Type::VEC3, "pos") - .vertex_out(eevee_volume_iface) - .fragment_out(0, Type::UVEC4, "out_volume_data") - .fragment_out(1, Type::VEC4, "out_transparency_data") - .additional_info("eevee_shared") - .vertex_source("eevee_volume_vert.glsl") - .fragment_source("eevee_volume_deferred_frag.glsl") - .additional_info("draw_fullscreen"); -#endif - /** \} */ /* -------------------------------------------------------------------- */ -- 2.30.2 From 1119b0572defa3038dc47278631bfe6a16aab92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sat, 14 Oct 2023 23:15:41 +0200 Subject: [PATCH 04/23] Finish and add tests --- source/blender/draw/CMakeLists.txt | 2 + .../draw/engines/eevee_next/eevee_pipeline.cc | 1 + .../draw/engines/eevee_next/eevee_volume.cc | 2 + .../shaders/eevee_occupancy_lib.glsl | 32 ++++++++++++++ .../shaders/eevee_occupancy_test.glsl | 44 +++++++++++++++++++ .../shaders/eevee_surf_occupancy_frag.glsl | 20 ++++----- .../shaders/eevee_volume_material_comp.glsl | 12 +++-- .../shaders/infos/eevee_material_info.hh | 2 +- .../gpu/shaders/infos/gpu_shader_test_info.hh | 6 +++ source/blender/gpu/tests/shader_test.cc | 1 + 10 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 50cc078f8bc..3c73919a05b 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -524,6 +524,8 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl engines/eevee_next/shaders/eevee_motion_blur_lib.glsl engines/eevee_next/shaders/eevee_nodetree_lib.glsl + engines/eevee_next/shaders/eevee_occupancy_lib.glsl + engines/eevee_next/shaders/eevee_occupancy_test.glsl engines/eevee_next/shaders/eevee_octahedron_lib.glsl engines/eevee_next/shaders/eevee_ray_denoise_bilateral_comp.glsl engines/eevee_next/shaders/eevee_ray_denoise_spatial_comp.glsl diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 6684a0706fe..82feeef92bf 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -768,6 +768,7 @@ void VolumePipeline::sync() pass.state_set(DRW_STATE_WRITE_DEPTH); inst_.bind_uniform_data(&pass); inst_.volume.bind_properties_buffers(pass); + inst_.sampling.bind_resources(pass); } { draw::PassMain &pass = volume_ps_; diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index b2cd5b692c6..7eef13eb424 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -314,11 +314,13 @@ void VolumeModule::draw_prepass(View &view) return; } + DRW_stats_group_start("Volumes"); inst_.pipelines.world_volume.render(view); occupancy_tx_.clear(uint4(0u)); occupancy_fb_.bind(); inst_.pipelines.volume.render(view); + DRW_stats_group_end(); } void VolumeModule::draw_compute(View &view) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl new file mode 100644 index 00000000000..cd5c1188cd4 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl @@ -0,0 +1,32 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +struct OccupancyBits { + uint bits[8]; +}; + +/** + * Example with for 16bits per layer and 2 layers. + * 0 Layer0 15 16 Layer1 31 < Bits index from LSB to MSB (left to right) + * |--------------| |--------------| + * 0000000001111111 1111111111111111 Surf0 + * 0000000000000000 0000001111111111 < Surf1 : volume_bit = + * 0000000001111111 1111110000000000 < Result occupancy at each froxel depths + * \a depth in [0..1] range. + * \a bit_count in [1..256] range. + */ +OccupancyBits occupancy_from_depth(float depth, int bit_count) +{ + /* We want the occupancy at the center of each range a bit covers. + * So we round the depth to the nearest bit. */ + int depth_bit_index = int(depth * float(bit_count) + 0.5); + + OccupancyBits occupancy; + for (int i = 0; i < 8; i++) { + int shift = clamp(depth_bit_index - i * 32, 0, 32); + /* Cannot bit shift more than 31 positions. */ + occupancy.bits[i] = (shift == 32) ? 0x0u : (~0x0u << uint(shift)); + } + return occupancy; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl new file mode 100644 index 00000000000..62a543f3212 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl @@ -0,0 +1,44 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Directive for resetting the line numbering so the failing tests lines can be printed. + * This conflict with the shader compiler error logging scheme. + * Comment out for correct compilation error line. */ +#line 5 + +#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_test_lib.glsl) + +#define TEST(a, b) if (true) + +uvec4 occupancy_to_uint4(OccupancyBits occupancy) +{ + return uvec4(occupancy.bits[0], occupancy.bits[1], occupancy.bits[2], occupancy.bits[3]); +} + +void main() +{ + TEST(eevee_occupancy, Occupancy) + { + OccupancyBits occup; + + occup = occupancy_from_depth(0.1, 1); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFFu, ~0u, ~0u, ~0u)); + + occup = occupancy_from_depth(0.6, 1); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFEu, ~0u, ~0u, ~0u)); + + occup = occupancy_from_depth(0.5, 32); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFF0000u, ~0u, ~0u, ~0u)); + + occup = occupancy_from_depth(0.5, 64); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, ~0u, ~0u, ~0u)); + + occup = occupancy_from_depth(0.5, 128); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, 0u, ~0u, ~0u)); + + occup = occupancy_from_depth(33.0 / 64.0, 64); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, 0xFFFFFFFEu, ~0u, ~0u)); + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index fe71d1c45cf..8fe2d4bc2e6 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -11,7 +11,7 @@ #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) #pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) -#pragma BLENDER_REQUIRE(eevee_transparency_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) vec4 closure_to_rgba(Closure cl) { @@ -23,19 +23,17 @@ void main() ivec2 texel = ivec2(gl_FragCoord.xy); vec2 uv = gl_FragCoord.xy / vec2(imageSize(occupancy_img).xy); vec3 ss_P = vec3(uv, gl_FragCoord.z); + /* Apply jitter here instead of modifying the projection matrix. + * This is because the depth range and mapping function changes. */ + /* TODO(fclem): Jitter the camera for the other 2 dimension. */ + float jitter = sampling_rng_1D_get(SAMPLING_VOLUME_W) * uniform_buf.volumes.inv_tex_size.z; + float volume_z = screen_to_volume(ss_P).z - jitter; - float volume_z = screen_to_volume(ss_P).z; - /* TODO(fclem): Check if this quantization is good. */ - int volume_bit = int(volume_z * uniform_buf.volumes.tex_size.z); + OccupancyBits occupancy_bits = occupancy_from_depth(volume_z, uniform_buf.volumes.tex_size.z); for (int i = 0; i < imageSize(occupancy_img).z; i++) { - int shift = volume_bit - i * 32; - if (shift < 32) { - uint occupancy_bits = 0xFFFFFFFFu; - if (shift > 0) { - occupancy_bits >>= uint(shift); - } - imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits); + if (occupancy_bits.bits[i] != 0u) { + imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits.bits[i]); } } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl index 1f9b26f09d5..c1af27b6aa4 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl @@ -53,10 +53,14 @@ void main() /** Check occupancy map. Discard thread if froxel is empty. */ /* Shift for 32bits per layer. Avoid integer modulo and division. */ const int shift = 5; - const int mask = int(0xFFFFFFFFu << 5u); - uint occupancy_bits = imageLoad(occupancy_img, ivec3(froxel.xy, froxel.z >> shift)).r; - uint occupancy_bit = occupancy_bits >> (froxel.z & mask); - if (occupancy_bit == 0u) { + const int mask = int(~(0xFFFFFFFFu << 5u)); + /* Divide by 32. */ + int occupancy_layer = froxel.z >> shift; + /* Modulo 32. */ + uint occupancy_shift = froxel.z & mask; + + uint occupancy_bits = imageLoad(occupancy_img, ivec3(froxel.xy, occupancy_layer)).r; + if (((occupancy_bits >> occupancy_shift) & 1u) == 0u) { return; } #endif diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 3886e94e28a..6fa1df53d60 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -316,7 +316,7 @@ GPU_SHADER_CREATE_INFO(eevee_surf_occupancy) ImageType::UINT_3D, "occupancy_img") .fragment_source("eevee_surf_occupancy_frag.glsl") - .additional_info("eevee_global_ubo"); + .additional_info("eevee_global_ubo", "eevee_sampling_data"); /** \} */ diff --git a/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh index 87fd2f30015..7bceb1d76db 100644 --- a/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh +++ b/source/blender/gpu/shaders/infos/gpu_shader_test_info.hh @@ -98,3 +98,9 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_test) .additional_info("gpu_shader_test") .additional_info("eevee_shared") .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(eevee_occupancy_test) + .fragment_source("eevee_occupancy_test.glsl") + .additional_info("gpu_shader_test") + .additional_info("eevee_shared") + .do_static_compilation(true); diff --git a/source/blender/gpu/tests/shader_test.cc b/source/blender/gpu/tests/shader_test.cc index ffbf5778ca0..bf9d17c6dea 100644 --- a/source/blender/gpu/tests/shader_test.cc +++ b/source/blender/gpu/tests/shader_test.cc @@ -461,6 +461,7 @@ GPU_TEST(math_lib) static void test_eevee_lib() { gpu_shader_lib_test("eevee_shadow_test.glsl", "eevee_shared"); + gpu_shader_lib_test("eevee_occupancy_test.glsl"); } GPU_TEST(eevee_lib) -- 2.30.2 From 0754cca81267a3e1f9419bd64a34fb92bcec24f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Sun, 15 Oct 2023 01:28:18 +0200 Subject: [PATCH 05/23] Fix on Metal --- .../draw/engines/eevee_next/eevee_volume.cc | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index 7eef13eb424..82c86855b99 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -234,13 +234,6 @@ void VolumeModule::end_sync() return; } - int occupancy_layers = divide_ceil_u(data_.tex_size.z, 32u); - eGPUTextureUsage occupancy_usage = GPU_TEXTURE_USAGE_SHADER_READ | - GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; - occupancy_tx_.ensure_3d(GPU_R32UI, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage); - /* Empty framebuffer. */ - occupancy_fb_.ensure(data_.tex_size.xy()); - eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATTACHMENT; @@ -249,6 +242,20 @@ void VolumeModule::end_sync() prop_emission_tx_.ensure_3d(GPU_R11F_G11F_B10F, data_.tex_size, usage); prop_phase_tx_.ensure_3d(GPU_RG16F, data_.tex_size, usage); + int occupancy_layers = divide_ceil_u(data_.tex_size.z, 32u); + eGPUTextureUsage occupancy_usage = GPU_TEXTURE_USAGE_SHADER_READ | + GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; + occupancy_tx_.ensure_3d(GPU_R32UI, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage); + if (GPU_backend_get_type() == GPU_BACKEND_METAL) { + /* Metal requires a dummy attachment. */ + occupancy_fb_.ensure(GPU_ATTACHMENT_NONE, + GPU_ATTACHMENT_TEXTURE_LAYER(prop_extinction_tx_, 0)); + } + else { + /* Empty framebuffer. */ + occupancy_fb_.ensure(data_.tex_size.xy()); + } + if (!inst_.pipelines.world_volume.is_valid()) { prop_scattering_tx_.clear(float4(0.0f)); prop_extinction_tx_.clear(float4(0.0f)); @@ -280,6 +287,7 @@ void VolumeModule::end_sync() scatter_ps_.bind_image("in_phase_img", &prop_phase_tx_); scatter_ps_.bind_image("out_scattering_img", &scatter_tx_); scatter_ps_.bind_image("out_extinction_img", &extinction_tx_); + scatter_ps_.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); /* Sync with the property pass. */ scatter_ps_.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS | GPU_BARRIER_TEXTURE_FETCH); scatter_ps_.dispatch(math::divide_ceil(data_.tex_size, int3(VOLUME_GROUP_SIZE))); @@ -303,6 +311,7 @@ void VolumeModule::end_sync() bind_resources(resolve_ps_); resolve_ps_.bind_texture("depth_tx", &inst_.render_buffers.depth_tx); resolve_ps_.bind_image(RBUFS_COLOR_SLOT, &inst_.render_buffers.rp_color_tx); + resolve_ps_.bind_image(RBUFS_VALUE_SLOT, &inst_.render_buffers.rp_value_tx); /* Sync with the integration pass. */ resolve_ps_.barrier(GPU_BARRIER_TEXTURE_FETCH); resolve_ps_.draw_procedural(GPU_PRIM_TRIS, 1, 3); -- 2.30.2 From 23564fb84f850c17e99a223d2b6934ce0335725d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 15 Oct 2023 17:15:01 +0200 Subject: [PATCH 06/23] Invert the occupancy bit befor XORing to fix near geometry --- .../engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index 8fe2d4bc2e6..f729102fdc4 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -32,6 +32,10 @@ void main() OccupancyBits occupancy_bits = occupancy_from_depth(volume_z, uniform_buf.volumes.tex_size.z); for (int i = 0; i < imageSize(occupancy_img).z; i++) { + /* Negate occupancy bits before XORing so that meshes clipped by the near plane fill the space + * betwen the inner part of the mesh and the near plane. + * It doesn't change anything for closed meshes. */ + occupancy_bits.bits[i] = ~occupancy_bits.bits[i]; if (occupancy_bits.bits[i] != 0u) { imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits.bits[i]); } -- 2.30.2 From f54dc53b4ac041397cbc0d253fb839f1911d19d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Sun, 15 Oct 2023 21:44:41 +0200 Subject: [PATCH 07/23] Implement volume layers --- .../draw/engines/eevee_next/eevee_instance.cc | 2 +- .../draw/engines/eevee_next/eevee_material.cc | 57 +++--- .../draw/engines/eevee_next/eevee_material.hh | 6 +- .../draw/engines/eevee_next/eevee_pipeline.cc | 163 ++++++++++++++++-- .../draw/engines/eevee_next/eevee_pipeline.hh | 134 +++++++++++--- .../draw/engines/eevee_next/eevee_shader.cc | 8 +- .../draw/engines/eevee_next/eevee_sync.cc | 32 +++- .../draw/engines/eevee_next/eevee_sync.hh | 1 + .../draw/engines/eevee_next/eevee_volume.cc | 118 +------------ .../draw/engines/eevee_next/eevee_volume.hh | 37 +--- .../draw/engines/eevee_next/eevee_world.cc | 4 +- 11 files changed, 352 insertions(+), 210 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index 80f3ea531cc..99ae711476b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -235,7 +235,7 @@ void Instance::object_sync(Object *ob) sync.sync_point_cloud(ob, ob_handle, res_handle, ob_ref); break; case OB_VOLUME: - volume.sync_object(ob, ob_handle, res_handle); + sync.sync_volume(ob, ob_handle, res_handle); break; case OB_CURVES: sync.sync_curves(ob, ob_handle, res_handle); diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 0a9832238d1..df39c71ddb1 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -203,13 +203,14 @@ MaterialPass MaterialModule::material_pass_get(Object *ob, inst_.sampling.reset(); } - if (ELEM(pipeline_type, - MAT_PIPE_FORWARD, - MAT_PIPE_FORWARD_PREPASS, - MAT_PIPE_FORWARD_PREPASS_VELOCITY) && - GPU_material_flag_get(matpass.gpumat, GPU_MATFLAG_TRANSPARENT)) - { - /* Transparent pass is generated later. */ + const bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_OCCUPANCY, MAT_PIPE_VOLUME_MATERIAL); + const bool is_forward = ELEM(pipeline_type, + MAT_PIPE_FORWARD, + MAT_PIPE_FORWARD_PREPASS, + MAT_PIPE_FORWARD_PREPASS_VELOCITY); + const bool is_transparent = GPU_material_flag_get(matpass.gpumat, GPU_MATFLAG_TRANSPARENT); + if (is_volume || (is_forward && is_transparent)) { + /* Sub pass is generated later. */ matpass.sub_pass = nullptr; } else { @@ -241,11 +242,11 @@ Material &MaterialModule::material_sync(Object *ob, bool has_motion) { if (geometry_type == MAT_GEOM_VOLUME_OBJECT) { - MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME_PREPASS); + MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME_MATERIAL); return material_map_.lookup_or_add_cb(material_key, [&]() { Material mat = {}; - mat.volume_prepass = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_PREPASS, MAT_GEOM_VOLUME_OBJECT); + mat.volume_material = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT); return mat; }); } @@ -273,7 +274,7 @@ Material &MaterialModule::material_sync(Object *ob, mat.planar_probe_prepass = MaterialPass(); mat.planar_probe_shading = MaterialPass(); mat.volume_occupancy = MaterialPass(); - mat.volume_prepass = MaterialPass(); + mat.volume_material = MaterialPass(); } else { /* Order is important for transparent. */ @@ -297,17 +298,18 @@ Material &MaterialModule::material_sync(Object *ob, mat.planar_probe_shading = material_pass_get( ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_PLANAR); } + } - if (GPU_material_has_volume_output(mat.shading.gpumat)) { - mat.volume_occupancy = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, geometry_type); - mat.volume_prepass = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_PREPASS, MAT_GEOM_VOLUME_OBJECT); - } - else { - mat.volume_occupancy = MaterialPass(); - mat.volume_prepass = MaterialPass(); - } + mat.is_volume = GPU_material_has_volume_output(mat.shading.gpumat); + if (mat.is_volume) { + mat.volume_occupancy = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, geometry_type); + mat.volume_material = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT); + } + else { + mat.volume_occupancy = MaterialPass(); + mat.volume_material = MaterialPass(); } if (blender_mat->blend_shadow == MA_BS_NONE) { @@ -338,6 +340,19 @@ Material &MaterialModule::material_sync(Object *ob, ob, blender_mat, mat.shading.gpumat); } + if (mat.is_volume) { + /* Volume needs to use one sub pass per object to support layering. */ + VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(ob); + if (layer) { + mat.volume_occupancy.sub_pass = layer->occupancy_add(mat.volume_occupancy.gpumat); + mat.volume_material.sub_pass = layer->material_add(mat.volume_material.gpumat); + } + else { + /* Culled volumes. */ + mat.volume_occupancy.sub_pass = nullptr; + mat.volume_material.sub_pass = nullptr; + } + } return mat; } diff --git a/source/blender/draw/engines/eevee_next/eevee_material.hh b/source/blender/draw/engines/eevee_next/eevee_material.hh index a7ebde305b6..b6eda40d539 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.hh +++ b/source/blender/draw/engines/eevee_next/eevee_material.hh @@ -32,7 +32,7 @@ enum eMaterialPipeline { MAT_PIPE_DEFERRED_PREPASS_VELOCITY, MAT_PIPE_FORWARD_PREPASS, MAT_PIPE_FORWARD_PREPASS_VELOCITY, - MAT_PIPE_VOLUME_PREPASS, + MAT_PIPE_VOLUME_MATERIAL, MAT_PIPE_VOLUME_OCCUPANCY, MAT_PIPE_SHADOW, MAT_PIPE_CAPTURE, @@ -68,6 +68,7 @@ static inline void material_type_from_shader_uuid(uint64_t shader_uuid, static inline uint64_t shader_uuid_from_material_type(eMaterialPipeline pipeline_type, eMaterialGeometry geometry_type) { + BLI_assert(geometry_type < (1 << 4)); return geometry_type | (pipeline_type << 4); } @@ -228,6 +229,7 @@ struct MaterialPass { struct Material { bool is_alpha_blend_transparent; + bool is_volume; MaterialPass shadow; MaterialPass shading; MaterialPass prepass; @@ -237,7 +239,7 @@ struct Material { MaterialPass planar_probe_prepass; MaterialPass planar_probe_shading; MaterialPass volume_occupancy; - MaterialPass volume_prepass; + MaterialPass volume_material; }; struct MaterialArray { diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 82feeef92bf..996d07608fd 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -17,6 +17,7 @@ #include "eevee_shadow.hh" #include "draw_common.hh" +#include namespace blender::eevee { @@ -331,6 +332,9 @@ PassMain::Sub *ForwardPipeline::prepass_opaque_add(::Material *blender_mat, PassMain::Sub *ForwardPipeline::material_opaque_add(::Material *blender_mat, GPUMaterial *gpumat) { + BLI_assert_msg(GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT) == false, + "Forward Transparent should be registered directly without calling " + "PipelineModule::material_add()"); PassMain::Sub *pass = (blender_mat->blend_flag & MA_BL_CULL_BACKFACE) ? opaque_single_sided_ps_ : opaque_double_sided_ps_; return &pass->sub(GPU_material_get_name(gpumat)); @@ -755,45 +759,178 @@ void DeferredPipeline::render(View &main_view, /** \} */ /* -------------------------------------------------------------------- */ -/** \name Volume Pipeline +/** \name Volume Layer * * \{ */ -void VolumePipeline::sync() +void VolumeLayer::sync() { + object_bounds_.clear(); + + draw::PassMain &layer_pass = volume_layer_ps_; + layer_pass.init(); { - draw::PassMain &pass = occupancy_ps_; - pass.init(); + PassMain::Sub &pass = layer_pass.sub("occupancy_ps"); /* Double sided without depth test. */ pass.state_set(DRW_STATE_WRITE_DEPTH); inst_.bind_uniform_data(&pass); inst_.volume.bind_properties_buffers(pass); inst_.sampling.bind_resources(pass); + occupancy_ps_ = &pass; } { - draw::PassMain &pass = volume_ps_; - pass.init(); + PassMain::Sub &pass = layer_pass.sub("material_ps"); + pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); pass.bind_texture(RBUFS_UTILITY_TEX_SLOT, inst_.pipelines.utility_tx); inst_.bind_uniform_data(&pass); inst_.volume.bind_properties_buffers(pass); inst_.sampling.bind_resources(pass); + material_ps_ = &pass; } } -PassMain::Sub *VolumePipeline::volume_occupancy_add(GPUMaterial *gpumat) +PassMain::Sub *VolumeLayer::occupancy_add(GPUMaterial *gpumat) { - return &occupancy_ps_.sub(GPU_material_get_name(gpumat)); + BLI_assert_msg(GPU_material_has_volume_output(gpumat) == true, + "Only volume material should be added here"); + PassMain::Sub *pass = &occupancy_ps_->sub(GPU_material_get_name(gpumat)); + pass->material_set(*inst_.manager, gpumat); + return pass; } -PassMain::Sub *VolumePipeline::volume_material_add(GPUMaterial *gpumat) +PassMain::Sub *VolumeLayer::material_add(GPUMaterial *gpumat) { - return &volume_ps_.sub(GPU_material_get_name(gpumat)); + BLI_assert_msg(GPU_material_has_volume_output(gpumat) == true, + "Only volume material should be added here"); + PassMain::Sub *pass = &material_ps_->sub(GPU_material_get_name(gpumat)); + pass->material_set(*inst_.manager, gpumat); + return pass; } -void VolumePipeline::render(View &view) +void VolumeLayer::render(View &view) { - inst_.manager->submit(occupancy_ps_, view); - inst_.manager->submit(volume_ps_, view); + inst_.manager->submit(volume_layer_ps_, view); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Volume Pipeline + * \{ */ + +void VolumePipeline::sync() +{ + enabled_ = false; + for (auto &layer : layers_) { + (*layer).sync(); + } +} + +void VolumePipeline::render(View &view, Texture &occupancy_tx) +{ + BLI_assert_msg(enabled_, "Trying to run the volume object pipeline with no actual volume calls"); + + for (auto &layer : layers_) { + /* TODO(fclem): We might want to skip empty layers as the clear overhead is be significant. */ + /* TODO(fclem): Move this clear inside the render pass. */ + occupancy_tx.clear(uint4(0u)); + (*layer).render(view); + } +} + +GridAABB VolumePipeline::grid_aabb_from_object(Object *ob) +{ + const Camera &camera = inst_.camera; + const VolumesInfoData &data = inst_.volume.data_; + /* Returns the unified volume grid cell corner of a world space coordinate. */ + auto to_global_grid_coords = [&](float3 wP) -> int3 { + /* TODO(fclem): Should we use the render view winmat and not the camera one? */ + const float4x4 &view_matrix = camera.data_get().viewmat; + const float4x4 &projection_matrix = camera.data_get().winmat; + + float3 ndc_coords = math::project_point(projection_matrix * view_matrix, wP); + ndc_coords = (ndc_coords * 0.5f) + float3(0.5f); + + float3 grid_coords = screen_to_volume(projection_matrix, + data.depth_near, + data.depth_far, + data.depth_distribution, + data.coord_scale, + ndc_coords); + /* Round to nearest grid corner. */ + return int3(grid_coords * float3(data.tex_size) + 0.5); + }; + + const BoundBox &bbox = *BKE_object_boundbox_get(ob); + int3 min = int3(INT32_MAX); + int3 max = int3(INT32_MIN); + + for (float3 l_corner : bbox.vec) { + float3 w_corner = math::transform_point(float4x4(ob->object_to_world), l_corner); + /* Note that this returns the nearest cell corner coordinate. + * So sub-froxel AABB will effectively return the same coordinate + * for each corner (making it empty and skipped) unless it + * cover the center of the froxel. */ + math::min_max(to_global_grid_coords(w_corner), min, max); + } + return {min, max}; +} + +GridAABB VolumePipeline::grid_aabb_from_view() +{ + return {int3(0), inst_.volume.data_.tex_size}; +} + +VolumeLayer *VolumePipeline::register_and_get_layer(Object *ob) +{ + GridAABB object_aabb = grid_aabb_from_object(ob); + GridAABB view_aabb = grid_aabb_from_view(); + if (object_aabb.intersect(view_aabb).is_empty()) { + /* Skip invisible object with respect to raster grid and bounds density. */ + return nullptr; + } + /* Do linear search in all layers in order. This can be optimized. */ + for (auto &layer : layers_) { + if (!(*layer).bounds_overlaps(object_aabb)) { + (*layer).add_object_bound(object_aabb); + return layer.get(); + } + } + /* No non-overlapping layer found. Create new one. */ + int64_t index = layers_.append_and_get_index(std::make_unique(inst_)); + (*layers_[index]).add_object_bound(object_aabb); + return layers_[index].get(); +} + +void VolumePipeline::material_call(MaterialPass &volume_material_pass, + Object *ob, + ResourceHandle res_handle) +{ + if (volume_material_pass.sub_pass == nullptr) { + /* Can happen if shader is not compiled, or if object has been culled. */ + return; + } + + /* TODO(fclem): This should be revisited, `volume_sub_pass()` should not decide on the volume + * visibility. Instead, we should query visibility upstream and not try to even compile the + * shader. */ + PassMain::Sub *object_pass = volume_sub_pass( + *volume_material_pass.sub_pass, inst_.scene, ob, volume_material_pass.gpumat); + if (object_pass) { + /* Possible double work here. Should be relatively insignificant in practice. */ + GridAABB object_aabb = grid_aabb_from_object(ob); + GridAABB view_aabb = grid_aabb_from_view(); + GridAABB visible_aabb = object_aabb.intersect(view_aabb); + /* Invisible volumes should already have been clipped. */ + BLI_assert(visible_aabb.is_empty() == false); + /* TODO(fclem): Use graphic pipeline instead of compute so we can leverage GPU culling, + * resource indexing and other further optimizations. */ + object_pass->push_constant("drw_ResourceID", int(res_handle.resource_index())); + object_pass->push_constant("grid_coords_min", visible_aabb.min); + object_pass->dispatch(math::divide_ceil(visible_aabb.extent(), int3(VOLUME_GROUP_SIZE))); + /* Notify the volume module to enable itself. */ + enabled_ = true; + } } /** \} */ diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index 3c2447e1f69..f81a920eb3a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -274,21 +274,121 @@ class DeferredPipeline { * * \{ */ +struct GridAABB { + int3 min, max; + + GridAABB(int3 min_, int3 max_) : min(min_), max(max_){}; + + /** Returns the intersection between this AABB and the \a other AABB. */ + GridAABB intersect(const GridAABB &other) const + { + return {math::max(this->min, other.min), math::min(this->max, other.max)}; + } + + /** Returns the extent of the volume. Undefined if AABB is empty. */ + int3 extent() const + { + return max - min; + } + + /** Returns true if volume covers nothing or is negative. */ + bool is_empty() const + { + return math::reduce_min(max - min) <= 0; + } +}; + +/** + * A volume layer contains a list of non-overlapping volume objects. + */ +class VolumeLayer { + private: + Instance &inst_; + + PassMain volume_layer_ps_ = {"Volume.Layer"}; + /* Sub-passes of volume_layer_ps. */ + PassMain::Sub *occupancy_ps_; + PassMain::Sub *material_ps_; + /* List of bounds from all objects contained inside this pass. */ + Vector object_bounds_; + + public: + VolumeLayer(Instance &inst) : inst_(inst) + { + this->sync(); + } + + PassMain::Sub *occupancy_add(GPUMaterial *gpumat); + PassMain::Sub *material_add(GPUMaterial *gpumat); + + /* Return true if the given bounds overlaps any of the contained object in this layer. */ + bool bounds_overlaps(const GridAABB &object_aabb) const + { + for (const GridAABB &other_aabb : object_bounds_) { + if (object_aabb.intersect(other_aabb).is_empty() == false) { + return true; + } + } + return false; + } + + void add_object_bound(const GridAABB &object_aabb) + { + object_bounds_.append(object_aabb); + } + + void sync(); + void render(View &view); +}; + class VolumePipeline { private: Instance &inst_; - PassMain occupancy_ps_ = {"Volume.Occupancy"}; - PassMain volume_ps_ = {"Volume.Objects"}; + Vector> layers_; + + /* True if any volume (any object type) creates a volume draw-call. Enables the volume module. */ + bool enabled_ = false; public: VolumePipeline(Instance &inst) : inst_(inst){}; - PassMain::Sub *volume_occupancy_add(GPUMaterial *gpumat); - PassMain::Sub *volume_material_add(GPUMaterial *gpumat); - void sync(); - void render(View &view); + void render(View &view, Texture &occupancy_tx); + + /** + * Returns correct volume layer for a given object and add the object to the layer. + * Returns nullptr if the object is not visible at all. + */ + VolumeLayer *register_and_get_layer(Object *ob); + + /** + * Creates a volume material call. + * If any call to this function result in a valid draw-call, then the volume module will be + * enabled. + */ + void material_call(MaterialPass &volume_material_pass, Object *ob, ResourceHandle res_handle); + + bool is_enabled() const + { + return enabled_; + } + + private: + /** + * Returns Axis aligned bounding box in the volume grid. + * Used for frustum culling and volumes overlapping detection. + * Represents min and max grid corners covered by a volume. + * So a volume covering the first froxel will have min={0,0,0} and max={1,1,1}. + * A volume with min={0,0,0} and max={0,0,0} covers nothing. + */ + GridAABB grid_aabb_from_object(Object *ob); + + /** + * Returns the view entire AABB. Used for clipping object bounds. + * Remember that these are cells corners, so this extents to `tex_size`. + */ + GridAABB grid_aabb_from_view(); }; /** \} */ @@ -511,7 +611,7 @@ class PipelineModule { deferred.end_sync(); } - PassMain::Sub *material_add(Object *ob, + PassMain::Sub *material_add(Object * /*ob*/ /* TODO remove. */, ::Material *blender_mat, GPUMaterial *gpumat, eMaterialPipeline pipeline_type, @@ -544,35 +644,29 @@ class PipelineModule { case MAT_PIPE_DEFERRED_PREPASS: return deferred.prepass_add(blender_mat, gpumat, false); case MAT_PIPE_FORWARD_PREPASS: - if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) { - return forward.prepass_transparent_add(ob, blender_mat, gpumat); - } return forward.prepass_opaque_add(blender_mat, gpumat, false); case MAT_PIPE_DEFERRED_PREPASS_VELOCITY: return deferred.prepass_add(blender_mat, gpumat, true); case MAT_PIPE_FORWARD_PREPASS_VELOCITY: - if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) { - return forward.prepass_transparent_add(ob, blender_mat, gpumat); - } return forward.prepass_opaque_add(blender_mat, gpumat, true); case MAT_PIPE_DEFERRED: return deferred.material_add(blender_mat, gpumat); case MAT_PIPE_FORWARD: - if (GPU_material_flag_get(gpumat, GPU_MATFLAG_TRANSPARENT)) { - return forward.material_transparent_add(ob, blender_mat, gpumat); - } return forward.material_opaque_add(blender_mat, gpumat); - case MAT_PIPE_VOLUME_OCCUPANCY: - return volume.volume_occupancy_add(gpumat); - case MAT_PIPE_VOLUME_PREPASS: - return volume.volume_material_add(gpumat); case MAT_PIPE_SHADOW: return shadow.surface_material_add(gpumat); case MAT_PIPE_CAPTURE: return capture.surface_material_add(blender_mat, gpumat); + + case MAT_PIPE_VOLUME_OCCUPANCY: + case MAT_PIPE_VOLUME_MATERIAL: + BLI_assert_msg(0, "Volume shaders must register to the volume pipeline directly."); + return nullptr; + case MAT_PIPE_PLANAR_PREPASS: + /* Should be handled by the `probe_capture == MAT_PROBE_PLANAR` case. */ BLI_assert_unreachable(); return nullptr; } diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index cf2fd5a78bc..da83f3976ba 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -461,7 +461,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu std::stringstream vert_gen, frag_gen, comp_gen; - bool is_compute = pipeline_type == MAT_PIPE_VOLUME_PREPASS; + bool is_compute = pipeline_type == MAT_PIPE_VOLUME_MATERIAL; if (do_vertex_attrib_load) { vert_gen << global_vars.str() << attr_load.str(); @@ -616,7 +616,7 @@ GPUMaterial *ShaderModule::material_shader_get(::Material *blender_mat, eMaterialGeometry geometry_type, bool deferred_compilation) { - bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_PREPASS); + bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_MATERIAL, MAT_PIPE_VOLUME_OCCUPANCY); uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type); @@ -628,7 +628,7 @@ GPUMaterial *ShaderModule::world_shader_get(::World *blender_world, bNodeTree *nodetree, eMaterialPipeline pipeline_type) { - bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_PREPASS); + bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_MATERIAL); bool defer_compilation = is_volume; eMaterialGeometry geometry_type = is_volume ? MAT_GEOM_VOLUME_WORLD : MAT_GEOM_WORLD; @@ -650,7 +650,7 @@ GPUMaterial *ShaderModule::material_shader_get(const char *name, { uint64_t shader_uuid = shader_uuid_from_material_type(pipeline_type, geometry_type); - bool is_volume = (pipeline_type == MAT_PIPE_VOLUME_PREPASS); + bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_MATERIAL, MAT_PIPE_VOLUME_OCCUPANCY); GPUMaterial *gpumat = GPU_material_from_nodetree(nullptr, nullptr, diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.cc b/source/blender/draw/engines/eevee_next/eevee_sync.cc index 58d82aca879..bbd7c239ed7 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sync.cc @@ -19,6 +19,8 @@ #include "DNA_gpencil_legacy_types.h" #include "DNA_modifier_types.h" #include "DNA_particle_types.h" +#include "DNA_pointcloud_types.h" +#include "DNA_volume_types.h" #include "draw_common.hh" #include "draw_sculpt.hh" @@ -155,9 +157,9 @@ void SyncModule::sync_mesh(Object *ob, geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); - if (material.volume_prepass.gpumat && i == 0) { + if (material.is_volume && (i == 0)) { /* Only support single volume material for now. */ - inst_.volume.sync_object(ob, ob_handle, res_handle, &material.volume_prepass); + inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); /* Do not render surface if we are rendering a volume object * and do not have a surface closure. */ if (gpu_material && !GPU_material_has_surface_output(gpu_material)) { @@ -280,7 +282,7 @@ void SyncModule::sync_point_cloud(Object *ob, ResourceHandle res_handle, const ObjectRef & /*ob_ref*/) { - int material_slot = 1; + const int material_slot = POINTCLOUD_MATERIAL_NR; bool has_motion = inst_.velocity.step_object_sync( ob, ob_handle.object_key, res_handle, ob_handle.recalc); @@ -314,6 +316,30 @@ void SyncModule::sync_point_cloud(Object *ob, /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Volume Objects + * \{ */ + +void SyncModule::sync_volume(Object *ob, ObjectHandle & /*ob_handle*/, ResourceHandle res_handle) +{ + const int material_slot = VOLUME_MATERIAL_NR; + + /* Motion is not supported on volumes yet. */ + const bool has_motion = false; + + Material &material = inst_.materials.material_get( + ob, has_motion, material_slot - 1, MAT_GEOM_VOLUME_OBJECT); + + /* Use bounding volume geometry to tag empty spaces. */ + GPUBatch *geom = DRW_cache_volume_selection_surface_get(ob); + + geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); + + inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name GPencil * \{ */ diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.hh b/source/blender/draw/engines/eevee_next/eevee_sync.hh index eb47a1f4a89..e455b6b50fc 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.hh +++ b/source/blender/draw/engines/eevee_next/eevee_sync.hh @@ -156,6 +156,7 @@ class SyncModule { ObjectHandle &ob_handle, ResourceHandle res_handle, const ObjectRef &ob_ref); + void sync_volume(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle); void sync_gpencil(Object *ob, ObjectHandle &ob_handle, ResourceHandle res_handle); void sync_curves(Object *ob, ObjectHandle &ob_handle, diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index f839ab86f47..2c7a30798ad 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -22,54 +22,9 @@ namespace blender::eevee { -VolumeModule::GridAABB::GridAABB(Object *ob, const Camera &camera, const VolumesInfoData &data) -{ - /* Returns the unified volume grid cell corner of a world space coordinate. */ - auto to_global_grid_coords = [&](float3 wP) -> int3 { - const float4x4 &view_matrix = camera.data_get().viewmat; - const float4x4 &projection_matrix = camera.data_get().winmat; - - float3 ndc_coords = math::project_point(projection_matrix * view_matrix, wP); - ndc_coords = (ndc_coords * 0.5f) + float3(0.5f); - - float3 grid_coords = screen_to_volume(projection_matrix, - data.depth_near, - data.depth_far, - data.depth_distribution, - data.coord_scale, - ndc_coords); - /* Round to nearest grid corner. */ - return int3(grid_coords * float3(data.tex_size) + 0.5); - }; - - const BoundBox &bbox = *BKE_object_boundbox_get(ob); - min = int3(INT32_MAX); - max = int3(INT32_MIN); - - for (float3 l_corner : bbox.vec) { - float3 w_corner = math::transform_point(float4x4(ob->object_to_world), l_corner); - /* Note that this returns the nearest cell corner coordinate. - * So sub-froxel AABB will effectively return the same coordinate - * for each corner (making it empty and skipped) unless it - * cover the center of the froxel. */ - math::min_max(to_global_grid_coords(w_corner), min, max); - } -} - -bool VolumeModule::GridAABB::is_empty() const -{ - return math::reduce_min(max - min) <= 0; -} - -VolumeModule::GridAABB VolumeModule::GridAABB::intersect(const GridAABB &other) const -{ - return {math::min(this->max, other.max), math::max(this->min, other.min)}; -} - void VolumeModule::init() { enabled_ = false; - subpass_aabbs_.clear(); const Scene *scene_eval = inst_.scene; @@ -143,72 +98,10 @@ void VolumeModule::begin_sync() enabled_ = inst_.world.has_volume(); } -void VolumeModule::sync_object(Object *ob, - ObjectHandle & /*ob_handle*/, - ResourceHandle res_handle, - MaterialPass *material_pass /*=nullptr*/) -{ - float3 size = math::to_scale(float4x4(ob->object_to_world)); - /* Check if any of the axes have 0 length. (see #69070) */ - const float epsilon = 1e-8f; - if (size.x < epsilon || size.y < epsilon || size.z < epsilon) { - return; - } - - if (material_pass == nullptr) { - Material material = inst_.materials.material_get( - ob, false, VOLUME_MATERIAL_NR, MAT_GEOM_VOLUME_OBJECT); - material_pass = &material.volume_prepass; - } - - /* If shader failed to compile or is currently compiling. */ - if (material_pass->gpumat == nullptr) { - return; - } - - GPUShader *shader = GPU_material_get_shader(material_pass->gpumat); - if (shader == nullptr) { - return; - } - - GridAABB object_aabb(ob, inst_.camera, data_); - /* Remember that these are cells corners, so this extents to `tex_size`. */ - GridAABB view_aabb(int3(0), data_.tex_size); - if (object_aabb.intersect(view_aabb).is_empty()) { - /* Skip invisible object with respect to raster grid and bounds density. */ - return; - } - - PassMain::Sub *object_pass = volume_sub_pass( - *material_pass->sub_pass, inst_.scene, ob, material_pass->gpumat); - if (object_pass) { - enabled_ = true; - - /* Add a barrier at the start of a subpass or when 2 volumes overlaps. */ - if (!subpass_aabbs_.contains_as(shader) == false) { - object_pass->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); - subpass_aabbs_.add(shader, {object_aabb}); - } - else { - Vector &aabbs = subpass_aabbs_.lookup(shader); - for (GridAABB &other_aabb : aabbs) { - if (object_aabb.intersect(other_aabb).is_empty() == false) { - object_pass->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); - aabbs.clear(); - break; - } - } - aabbs.append(object_aabb); - } - - object_pass->push_constant("drw_ResourceID", int(res_handle.resource_index())); - object_pass->push_constant("grid_coords_min", object_aabb.min); - object_pass->dispatch(math::divide_ceil(object_aabb.extent(), int3(VOLUME_GROUP_SIZE))); - } -} - void VolumeModule::end_sync() { + enabled_ = enabled_ || inst_.pipelines.volume.is_enabled(); + if (!enabled_) { occupancy_tx_.free(); prop_scattering_tx_.free(); @@ -318,9 +211,10 @@ void VolumeModule::draw_prepass(View &view) DRW_stats_group_start("Volumes"); inst_.pipelines.world_volume.render(view); - occupancy_tx_.clear(uint4(0u)); - occupancy_fb_.bind(); - inst_.pipelines.volume.render(view); + if (inst_.pipelines.volume.is_enabled()) { + occupancy_fb_.bind(); + inst_.pipelines.volume.render(view, occupancy_tx_); + } DRW_stats_group_end(); } diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.hh b/source/blender/draw/engines/eevee_next/eevee_volume.hh index 7986698a164..b22f96afb1a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.hh +++ b/source/blender/draw/engines/eevee_next/eevee_volume.hh @@ -40,8 +40,11 @@ namespace blender::eevee { class Instance; +class VolumePipeline; class VolumeModule { + friend VolumePipeline; + private: Instance &inst_; @@ -85,34 +88,6 @@ class VolumeModule { Texture dummy_scatter_tx_; Texture dummy_transmit_tx_; - /* Axis aligned bounding box in the volume grid. - * Used for frustum culling and volumes overlapping detection. */ - struct GridAABB { - /* Represent min and max grid corners covered by a volume. - * So a volume covering the first froxel will have min={0,0,0} and max={1,1,1}. - * A volume with min={0,0,0} and max={0,0,0} covers nothing. */ - int3 min, max; - - GridAABB(int3 min_, int3 max_) : min(min_), max(max_){}; - GridAABB(Object *ob, const Camera &camera, const VolumesInfoData &data); - - /** Returns the intersection between this AABB and the \a other AABB. */ - GridAABB intersect(const GridAABB &other) const; - - /** Returns true if volume covers no froxel. */ - bool is_empty() const; - - /** Returns the extent of the volume. */ - int3 extent() const - { - return max - min; - } - }; - /* Stores a vector of volume AABBs for each material pass, - * so we can detect overlapping volumes and place GPU barriers where needed - * (Only stores the AABBs for the volumes rendered since the last barrier). */ - Map> subpass_aabbs_; - public: VolumeModule(Instance &inst, VolumesInfoData &data) : inst_(inst), data_(data) { @@ -154,10 +129,8 @@ class VolumeModule { void begin_sync(); void sync_world(); - void sync_object(Object *ob, - ObjectHandle &ob_handle, - ResourceHandle res_handle, - MaterialPass *material_pass = nullptr); + + void material_call(MaterialPass &material_pass, Object *ob, ResourceHandle res_handle); void end_sync(); diff --git a/source/blender/draw/engines/eevee_next/eevee_world.cc b/source/blender/draw/engines/eevee_next/eevee_world.cc index 0c1ef2681ec..bb686d45c09 100644 --- a/source/blender/draw/engines/eevee_next/eevee_world.cc +++ b/source/blender/draw/engines/eevee_next/eevee_world.cc @@ -95,7 +95,7 @@ void World::sync() world_and_ntree_get(bl_world, ntree); GPUMaterial *volume_gpumat = inst_.shaders.world_shader_get( - bl_world, ntree, MAT_PIPE_VOLUME_PREPASS); + bl_world, ntree, MAT_PIPE_VOLUME_MATERIAL); inst_.pipelines.world_volume.sync(volume_gpumat); if (inst_.lookdev.sync_world()) { @@ -129,7 +129,7 @@ bool World::has_volume() bNodeTree *ntree; world_and_ntree_get(bl_world, ntree); - GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME_PREPASS); + GPUMaterial *gpumat = inst_.shaders.world_shader_get(bl_world, ntree, MAT_PIPE_VOLUME_MATERIAL); return GPU_material_has_volume_output(gpumat); } -- 2.30.2 From ad42a6a614326c1ba616d938fe94a2860212c3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 16 Oct 2023 00:03:07 +0200 Subject: [PATCH 08/23] Fix wrong default material --- .../draw/engines/eevee_next/eevee_material.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index df39c71ddb1..443b775ac3b 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -172,6 +172,12 @@ MaterialPass MaterialModule::material_pass_get(Object *ob, matpass.gpumat = inst_.shaders.material_shader_get( blender_mat, ntree, pipeline_type, geometry_type, use_deferred_compilation); + const bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_OCCUPANCY, MAT_PIPE_VOLUME_MATERIAL); + const bool is_forward = ELEM(pipeline_type, + MAT_PIPE_FORWARD, + MAT_PIPE_FORWARD_PREPASS, + MAT_PIPE_FORWARD_PREPASS_VELOCITY); + switch (GPU_material_status(matpass.gpumat)) { case GPU_MAT_SUCCESS: { /* Determine optimization status for remaining compilations counter. */ @@ -183,8 +189,7 @@ MaterialPass MaterialModule::material_pass_get(Object *ob, } case GPU_MAT_QUEUED: queued_shaders_count++; - blender_mat = (geometry_type == MAT_GEOM_VOLUME_OBJECT) ? BKE_material_default_volume() : - BKE_material_default_surface(); + blender_mat = (is_volume) ? BKE_material_default_volume() : BKE_material_default_surface(); matpass.gpumat = inst_.shaders.material_shader_get( blender_mat, blender_mat->nodetree, pipeline_type, geometry_type, false); break; @@ -203,11 +208,6 @@ MaterialPass MaterialModule::material_pass_get(Object *ob, inst_.sampling.reset(); } - const bool is_volume = ELEM(pipeline_type, MAT_PIPE_VOLUME_OCCUPANCY, MAT_PIPE_VOLUME_MATERIAL); - const bool is_forward = ELEM(pipeline_type, - MAT_PIPE_FORWARD, - MAT_PIPE_FORWARD_PREPASS, - MAT_PIPE_FORWARD_PREPASS_VELOCITY); const bool is_transparent = GPU_material_flag_get(matpass.gpumat, GPU_MATFLAG_TRANSPARENT); if (is_volume || (is_forward && is_transparent)) { /* Sub pass is generated later. */ -- 2.30.2 From c33e74aba98b6214ac4f7b2ed83f6748307a20d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Mon, 16 Oct 2023 00:20:45 +0200 Subject: [PATCH 09/23] Fix missing passes for volume objects --- .../draw/engines/eevee_next/eevee_material.cc | 17 ++++++++++++++++- .../draw/engines/eevee_next/eevee_pipeline.cc | 1 - 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 443b775ac3b..2c7efd8d61d 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -243,12 +243,27 @@ Material &MaterialModule::material_sync(Object *ob, { if (geometry_type == MAT_GEOM_VOLUME_OBJECT) { MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME_MATERIAL); - return material_map_.lookup_or_add_cb(material_key, [&]() { + Material &mat = material_map_.lookup_or_add_cb(material_key, [&]() { Material mat = {}; + mat.volume_occupancy = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, MAT_GEOM_MESH); mat.volume_material = material_pass_get( ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT); return mat; }); + + /* Volume needs to use one sub pass per object to support layering. */ + VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(ob); + if (layer) { + mat.volume_occupancy.sub_pass = layer->occupancy_add(mat.volume_occupancy.gpumat); + mat.volume_material.sub_pass = layer->material_add(mat.volume_material.gpumat); + } + else { + /* Culled volumes. */ + mat.volume_occupancy.sub_pass = nullptr; + mat.volume_material.sub_pass = nullptr; + } + return mat; } eMaterialPipeline surface_pipe = (blender_mat->blend_method == MA_BM_BLEND) ? MAT_PIPE_FORWARD : diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 996d07608fd..94eb1994e59 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -17,7 +17,6 @@ #include "eevee_shadow.hh" #include "draw_common.hh" -#include namespace blender::eevee { -- 2.30.2 From 5db36b43673bcac4aea8f2c6bfb7319e17a82768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Tue, 17 Oct 2023 13:14:04 +0200 Subject: [PATCH 10/23] Add occupancy bits and new functions --- .../shaders/eevee_occupancy_lib.glsl | 37 ++++- .../shaders/eevee_occupancy_test.glsl | 129 +++++++++++++++++- .../common/gpu_shader_utildefines_lib.glsl | 2 +- 3 files changed, 163 insertions(+), 5 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl index cd5c1188cd4..5902f462656 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl @@ -6,12 +6,19 @@ struct OccupancyBits { uint bits[8]; }; +int occupancy_bit_index_from_depth(float depth, int bit_count) +{ + /* We want the occupancy at the center of each range a bit covers. + * So we round the depth to the nearest bit. */ + return int(depth * float(bit_count) + 0.5); +} + /** * Example with for 16bits per layer and 2 layers. * 0 Layer0 15 16 Layer1 31 < Bits index from LSB to MSB (left to right) * |--------------| |--------------| - * 0000000001111111 1111111111111111 Surf0 - * 0000000000000000 0000001111111111 < Surf1 : volume_bit = + * 0000000001111111 1111111111111111 < Surf0 + * 0000000000000000 0000001111111111 < Surf1 * 0000000001111111 1111110000000000 < Result occupancy at each froxel depths * \a depth in [0..1] range. * \a bit_count in [1..256] range. @@ -20,7 +27,7 @@ OccupancyBits occupancy_from_depth(float depth, int bit_count) { /* We want the occupancy at the center of each range a bit covers. * So we round the depth to the nearest bit. */ - int depth_bit_index = int(depth * float(bit_count) + 0.5); + int depth_bit_index = occupancy_bit_index_from_depth(depth, bit_count); OccupancyBits occupancy; for (int i = 0; i < 8; i++) { @@ -30,3 +37,27 @@ OccupancyBits occupancy_from_depth(float depth, int bit_count) } return occupancy; } + +/** + * Example with for 16bits per layer and 2 layers. + * 0 Layer0 15 16 Layer1 31 < Bits index from LSB to MSB (left to right) + * |--------------| |--------------| + * 0000000001000000 0000000000000000 < Surf0 + * 0000000000000000 0000001000000000 < Surf1 + * \a depth in [0..1] range. + * \a bit_count in [1..256] range. + */ +OccupancyBits occupancy_bit_from_depth(float depth, int bit_count) +{ + /* We want the occupancy at the center of each range a bit covers. + * So we round the depth to the nearest bit. */ + int depth_bit_index = occupancy_bit_index_from_depth(depth, bit_count); + + OccupancyBits occupancy; + for (int i = 0; i < 8; i++) { + int shift = depth_bit_index - i * 32; + /* Cannot bit shift more than 31 positions. */ + occupancy.bits[i] = (shift >= 0 && shift < 32) ? (0x1u << uint(shift)) : 0x0u; + } + return occupancy; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl index 62a543f3212..cd2370a0ac6 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl @@ -5,24 +5,86 @@ /* Directive for resetting the line numbering so the failing tests lines can be printed. * This conflict with the shader compiler error logging scheme. * Comment out for correct compilation error line. */ -#line 5 +#line 9 #pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_test_lib.glsl) #define TEST(a, b) if (true) +OccupancyBits occupancy_or(OccupancyBits a, OccupancyBits b) +{ + OccupancyBits occupancy; + for (int i = 0; i < 8; i++) { + occupancy.bits[i] = a.bits[i] | b.bits[i]; + } + return occupancy; +} + +int occupancy_find_lsb(OccupancyBits occupancy) +{ + for (int i = 0; i < 8; i++) { + if (occupancy.bits[i] != 0) { + return findLSB(occupancy.bits[i]) + i * 32; + } + } + return -1; +} + uvec4 occupancy_to_uint4(OccupancyBits occupancy) { return uvec4(occupancy.bits[0], occupancy.bits[1], occupancy.bits[2], occupancy.bits[3]); } +bool occupancy_bit_resolve(OccupancyBits entry, OccupancyBits exit, int bit_n, int bit_count) +{ + int first_exit = occupancy_find_lsb(exit); + int first_entry = occupancy_find_lsb(entry); + first_exit = (first_exit == -1) ? 99999 : first_exit; + /* Check if the first surface is an exit. If it is, initialize as inside the volume. */ + bool inside_volume = first_exit < first_entry; + for (int j = 0; j <= bit_n / 32; j++) { + uint entry_word = entry.bits[j]; + uint exit_word = exit.bits[j]; + /* TODO(fclem): Could use fewer iteration using findLSB and/or other intrinsics. */ + for (uint i = 0; i < 32; i++) { + if ((entry_word >> i) & 1u) { + inside_volume = true; + } + if ((exit_word >> i) & 1u) { + inside_volume = false; + } + if (i + j * 32 == bit_n) { + return inside_volume; + } + } + } + return inside_volume; +} + +OccupancyBits occupancy_resolve(OccupancyBits entry, OccupancyBits exit, int bit_count) +{ + OccupancyBits occupancy; + for (int j = 0; j < 8; j++) { + for (int i = 0; i < 32; i++) { + bool test = false; + if (i < bit_count - j * 32) { + test = occupancy_bit_resolve(entry, exit, i + j * 32, bit_count); + } + set_flag_from_test(occupancy.bits[j], test, 1u << uint(i)); + } + } + return occupancy; +} + void main() { TEST(eevee_occupancy, Occupancy) { OccupancyBits occup; + /* occupancy_from_depth */ occup = occupancy_from_depth(0.1, 1); EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFFu, ~0u, ~0u, ~0u)); @@ -40,5 +102,70 @@ void main() occup = occupancy_from_depth(33.0 / 64.0, 64); EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0u, 0xFFFFFFFEu, ~0u, ~0u)); + + /* occupancy_bit_from_depth */ + occup = occupancy_bit_from_depth(0.1, 1); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000001u, 0u, 0u, 0u)); + + occup = occupancy_bit_from_depth(0.6, 1); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000002u, 0u, 0u, 0u)); + + occup = occupancy_bit_from_depth(0.5, 32); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00010000u, 0u, 0u, 0u)); + + occup = occupancy_bit_from_depth(0.5, 64); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000001u, 0u, 0u)); + + occup = occupancy_bit_from_depth(0.5, 128); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000000u, 0x00000001u, 0u)); + + occup = occupancy_bit_from_depth(33.0 / 64.0, 64); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0x00000000u, 0x00000002u, 0u, 0u)); + + /* Test composing occupancy an the expected result. */ + /* Start empty. */ + OccupancyBits entry = occupancy_bit_from_depth(-1.0, 32); + OccupancyBits exit = occupancy_bit_from_depth(-1.0, 32); + entry = occupancy_or(entry, occupancy_bit_from_depth(1.0 / 32.0, 32)); + /* Second entry at the same depth. Should not change anything. */ + entry = occupancy_or(entry, occupancy_bit_from_depth(1.1 / 32.0, 32)); + /* Exit 2 bits later.*/ + exit = occupancy_or(exit, occupancy_bit_from_depth(3.0 / 32.0, 32)); + /* Second exit. Should not change anything. */ + exit = occupancy_or(exit, occupancy_bit_from_depth(5.0 / 32.0, 32)); + /* Third entry is valid. */ + entry = occupancy_or(entry, occupancy_bit_from_depth(7.0 / 32.0, 32)); + /* Third exit is valid. */ + exit = occupancy_or(exit, occupancy_bit_from_depth(9.0 / 32.0, 32)); + /* Fourth entry is valid. */ + entry = occupancy_or(entry, occupancy_bit_from_depth(11.0 / 32.0, 32)); + /* Fourth exit on the same depth. Cancels the occupancy. */ + exit = occupancy_or(exit, occupancy_bit_from_depth(11.0 / 32.0, 32)); + EXPECT_EQ(entry.bits[0], 2178u); /* 1000 1000 0010 */ + EXPECT_EQ(exit.bits[0], 2600u); /* 1010 0010 1000 */ + + occup = occupancy_resolve(entry, exit, 32); + EXPECT_EQ(occup.bits[0], 390u); /* 0001 1000 0110 */ + + /* Start empty. */ + entry = occupancy_bit_from_depth(-1.0, 44); + exit = occupancy_bit_from_depth(-1.0, 44); + /* First exit. Anything prior should be considered in volume. */ + exit = occupancy_or(exit, occupancy_bit_from_depth(33.0 / 44.0, 44)); + /* First entry. */ + entry = occupancy_or(entry, occupancy_bit_from_depth(36.0 / 44.0, 44)); + /* Second exit. Should not change anything. */ + exit = occupancy_or(exit, occupancy_bit_from_depth(40.0 / 44.0, 44)); + /* 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 */ + EXPECT_EQ(occupancy_to_uint4(entry), uvec4(0x00000000u, 0x010u, 0u, 0u)); + /* 0001 0000 0010 0000 0000 0000 0000 0000 0000 0000 0000 */ + EXPECT_EQ(occupancy_to_uint4(exit), uvec4(0x00000000u, 0x102u, 0u, 0u)); + + EXPECT_EQ(occupancy_find_lsb(entry), 36); + EXPECT_EQ(occupancy_find_lsb(exit), 33); + + occup = occupancy_resolve(entry, exit, 44); + /* 0000 1111 0001 1111 1111 1111 1111 1111 1111 1111 1111 */ + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFFu, 0x0F1u, 0u, 0u)); } } diff --git a/source/blender/gpu/shaders/common/gpu_shader_utildefines_lib.glsl b/source/blender/gpu/shaders/common/gpu_shader_utildefines_lib.glsl index b7088ed5f2d..5c97caa72cd 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_utildefines_lib.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_utildefines_lib.glsl @@ -74,7 +74,7 @@ void set_flag_from_test(inout int value, bool test, int flag) } /* Keep define to match C++ implementation. */ -#define SET_FLAG_FROM_TEST(value, test, flag) flag_test(value, test, flag) +#define SET_FLAG_FROM_TEST(value, test, flag) set_flag_from_test(value, test, flag) /** * Pack two 16-bit uint into one 32-bit uint. -- 2.30.2 From a3d855d2b89d42990cc98328bb3b202a65ded472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Tue, 17 Oct 2023 21:27:46 +0200 Subject: [PATCH 11/23] Implement new method for correct intersection or arbitrary geometry --- source/blender/draw/CMakeLists.txt | 1 + .../draw/engines/eevee_next/eevee_defines.hh | 4 + .../draw/engines/eevee_next/eevee_pipeline.cc | 14 ++- .../draw/engines/eevee_next/eevee_pipeline.hh | 2 +- .../draw/engines/eevee_next/eevee_shader.cc | 2 + .../draw/engines/eevee_next/eevee_shader.hh | 1 + .../draw/engines/eevee_next/eevee_volume.cc | 11 +- .../draw/engines/eevee_next/eevee_volume.hh | 18 ++- .../shaders/eevee_occupancy_convert_frag.glsl | 89 ++++++++++++++ .../shaders/eevee_occupancy_lib.glsl | 116 +++++++++++++++++- .../shaders/eevee_occupancy_test.glsl | 81 ++---------- .../shaders/eevee_surf_occupancy_frag.glsl | 10 ++ .../shaders/eevee_volume_material_comp.glsl | 2 +- .../shaders/infos/eevee_material_info.hh | 6 + .../shaders/infos/eevee_volume_info.hh | 17 +++ 15 files changed, 294 insertions(+), 80 deletions(-) create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 3c73919a05b..ccd1ecb2717 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -524,6 +524,7 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_motion_blur_gather_comp.glsl engines/eevee_next/shaders/eevee_motion_blur_lib.glsl engines/eevee_next/shaders/eevee_nodetree_lib.glsl + engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl engines/eevee_next/shaders/eevee_occupancy_lib.glsl engines/eevee_next/shaders/eevee_occupancy_test.glsl engines/eevee_next/shaders/eevee_octahedron_lib.glsl diff --git a/source/blender/draw/engines/eevee_next/eevee_defines.hh b/source/blender/draw/engines/eevee_next/eevee_defines.hh index 0b54d4e2745..a1d50bbd025 100644 --- a/source/blender/draw/engines/eevee_next/eevee_defines.hh +++ b/source/blender/draw/engines/eevee_next/eevee_defines.hh @@ -154,6 +154,7 @@ /* Volumes. */ #define VOLUME_GROUP_SIZE 4 #define VOLUME_INTEGRATION_GROUP_SIZE 8 +#define VOLUME_HIT_DEPTH_MAX 16 /* Resource bindings. */ @@ -185,6 +186,9 @@ #define VOLUME_PROP_EMISSION_IMG_SLOT 2 #define VOLUME_PROP_PHASE_IMG_SLOT 3 #define VOLUME_OCCUPANCY_SLOT 4 +/* Only during volume prepass. */ +#define VOLUME_HIT_DEPTH_SLOT 0 +#define VOLUME_HIT_COUNT_SLOT 1 /* Only during shadow rendering. */ #define SHADOW_ATLAS_IMG_SLOT 4 diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 94eb1994e59..bc7caaf981a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -773,10 +773,19 @@ void VolumeLayer::sync() /* Double sided without depth test. */ pass.state_set(DRW_STATE_WRITE_DEPTH); inst_.bind_uniform_data(&pass); - inst_.volume.bind_properties_buffers(pass); + inst_.volume.bind_occupancy_buffers(pass); inst_.sampling.bind_resources(pass); occupancy_ps_ = &pass; } + { + /* TODO(fclem): Optimize out when not needed. */ + PassMain::Sub &pass = layer_pass.sub("resolve_hits_ps"); + pass.state_set(DRW_STATE_WRITE_DEPTH); + inst_.bind_uniform_data(&pass); + pass.shader_set(inst_.shaders.static_shader_get(VOLUME_OCCUPANCY_CONVERT)); + pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); + pass.draw_procedural(GPU_PRIM_TRIS, 1, 3); + } { PassMain::Sub &pass = layer_pass.sub("material_ps"); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); @@ -825,7 +834,7 @@ void VolumePipeline::sync() } } -void VolumePipeline::render(View &view, Texture &occupancy_tx) +void VolumePipeline::render(View &view, Texture &occupancy_tx, Texture &hit_count_tx) { BLI_assert_msg(enabled_, "Trying to run the volume object pipeline with no actual volume calls"); @@ -833,6 +842,7 @@ void VolumePipeline::render(View &view, Texture &occupancy_tx) /* TODO(fclem): We might want to skip empty layers as the clear overhead is be significant. */ /* TODO(fclem): Move this clear inside the render pass. */ occupancy_tx.clear(uint4(0u)); + hit_count_tx.clear(uint4(0u)); (*layer).render(view); } } diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index f81a920eb3a..5a8ee71e689 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -354,7 +354,7 @@ class VolumePipeline { VolumePipeline(Instance &inst) : inst_(inst){}; void sync(); - void render(View &view, Texture &occupancy_tx); + void render(View &view, Texture &occupancy_tx, Texture &hit_count_tx); /** * Returns correct volume layer for a given object and add the object to the layer. diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index da83f3976ba..dcd70f5e02f 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -262,6 +262,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_ return "eevee_surfel_ray"; case VOLUME_INTEGRATION: return "eevee_volume_integration"; + case VOLUME_OCCUPANCY_CONVERT: + return "eevee_volume_occupancy_convert"; case VOLUME_RESOLVE: return "eevee_volume_resolve"; case VOLUME_SCATTER: diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.hh b/source/blender/draw/engines/eevee_next/eevee_shader.hh index 08abc0de5a6..530e8701db7 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.hh +++ b/source/blender/draw/engines/eevee_next/eevee_shader.hh @@ -130,6 +130,7 @@ enum eShaderType { SURFEL_RAY, VOLUME_INTEGRATION, + VOLUME_OCCUPANCY_CONVERT, VOLUME_RESOLVE, VOLUME_SCATTER, VOLUME_SCATTER_WITH_LIGHTS, diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index 2c7a30798ad..841b5d2d57d 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -131,6 +131,15 @@ void VolumeModule::end_sync() eGPUTextureUsage occupancy_usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; occupancy_tx_.ensure_3d(GPU_R32UI, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage); + + int max_ray_depth = 16; + eGPUTextureUsage hit_count_usage = GPU_TEXTURE_USAGE_SHADER_READ | + GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; + eGPUTextureUsage hit_depth_usage = GPU_TEXTURE_USAGE_SHADER_READ | + GPU_TEXTURE_USAGE_SHADER_WRITE; + hit_count_tx_.ensure_2d(GPU_R32UI, data_.tex_size.xy(), hit_count_usage); + hit_depth_tx_.ensure_3d(GPU_R32F, int3(data_.tex_size.xy(), max_ray_depth), hit_depth_usage); + if (GPU_backend_get_type() == GPU_BACKEND_METAL) { /* Metal requires a dummy attachment. */ occupancy_fb_.ensure(GPU_ATTACHMENT_NONE, @@ -213,7 +222,7 @@ void VolumeModule::draw_prepass(View &view) if (inst_.pipelines.volume.is_enabled()) { occupancy_fb_.bind(); - inst_.pipelines.volume.render(view, occupancy_tx_); + inst_.pipelines.volume.render(view, occupancy_tx_, hit_count_tx_); } DRW_stats_group_end(); } diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.hh b/source/blender/draw/engines/eevee_next/eevee_volume.hh index b22f96afb1a..185ce122454 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.hh +++ b/source/blender/draw/engines/eevee_next/eevee_volume.hh @@ -57,9 +57,16 @@ class VolumeModule { * It is filled during a pre-pass using atomic operations. * Using a 3D bitfield, we only allocate one bit per froxel. */ - Texture occupancy_tx_ = {"occupancy_tx_"}; + Texture occupancy_tx_ = {"occupancy_tx"}; + /** + * List of surface hit for correct occupancy determination. + * One texture holds the number of hit count and the other the depth and + * the facing of each hit. + */ + Texture hit_count_tx_ = {"hit_count_tx"}; + Texture hit_depth_tx_ = {"hit_depth_tx"}; /** Empty framebuffer for occupancy pass. */ - Framebuffer occupancy_fb_ = {"occupancy_fb_"}; + Framebuffer occupancy_fb_ = {"occupancy_fb"}; /* Material Parameters */ Texture prop_scattering_tx_; @@ -114,6 +121,13 @@ class VolumeModule { pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_); } + template void bind_occupancy_buffers(PassType &pass) + { + pass.bind_image(VOLUME_OCCUPANCY_SLOT, &occupancy_tx_); + pass.bind_image(VOLUME_HIT_DEPTH_SLOT, &hit_depth_tx_); + pass.bind_image(VOLUME_HIT_COUNT_SLOT, &hit_count_tx_); + } + bool needs_shadow_tagging() { return enabled_ && data_.use_lights; diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl new file mode 100644 index 00000000000..242cde7b0ce --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl @@ -0,0 +1,89 @@ +/* SPDX-FileCopyrightText: 2022-2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** + * Convert hit list to occupancy bitfield for the material pass. + */ + +#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) + +bool is_front_face_hit(float stored_hit_depth) +{ + return stored_hit_depth < 0.0; +} + +void main() +{ + float hit_depths[VOLUME_HIT_DEPTH_MAX]; + float hit_ordered[VOLUME_HIT_DEPTH_MAX]; + int hit_index[VOLUME_HIT_DEPTH_MAX]; + + ivec2 texel = ivec2(gl_FragCoord.xy); + + int hit_count = int(min(imageLoad(hit_count_img, texel).x, VOLUME_HIT_DEPTH_MAX)); + if (hit_count == 0) { + return; + } + + for (int i = 0; i < hit_count; i++) { + hit_depths[i] = imageLoad(hit_depth_img, ivec3(texel, i)).r; + } + + for (int i = 0; i < hit_count; i++) { + hit_index[i] = 0; + for (int j = 0; j < hit_count; j++) { + hit_index[i] += int(abs(hit_depths[i]) > abs(hit_depths[j])); + } + } + + for (int i = 0; i < hit_count; i++) { + hit_ordered[hit_index[i]] = hit_depths[i]; + } + +#if 1 /* Debug. Need to adjust the qualifier of the texture. */ + for (int i = 0; i < hit_count; i++) { + imageStore(hit_depth_img, ivec3(texel, i), vec4(hit_ordered[i])); + } +#endif + + /* Convert to occupancy bits. */ + OccupancyBits occupancy = occupancy_new(); + /* True if last interface was a volume entry. */ + /* Initialized to front facing if first hit is a backface to support camera inside the volume. */ + bool last_frontfacing = !is_front_face_hit(hit_ordered[0]); + /* Bit index of the last interface. */ + int last_bit = 0; + for (int i = 0; i < hit_count; i++) { + bool frontfacing = is_front_face_hit(hit_ordered[i]); + if (last_frontfacing == frontfacing) { + /* Same facing, do not treat as a volume interface. */ + continue; + } + last_frontfacing = frontfacing; + + int occupancy_bit_n = occupancy_bit_index_from_depth(abs(hit_ordered[i]), + uniform_buf.volumes.tex_size.z); + if (last_bit == occupancy_bit_n) { + /* We did not cross a new voxel center. Do nothing. */ + continue; + } + int bit_start = last_bit; + int bit_count = occupancy_bit_n - last_bit; + last_bit = occupancy_bit_n; + + if (last_frontfacing == false) { + /* OccupancyBits is cleared by default. No need to do anything for empty regions. */ + continue; + } + occupancy = occupancy_set_bits_high(occupancy, bit_start, bit_count); + } + + /* Write the occupancy bits */ + for (int i = 0; i < imageSize(occupancy_img).z; i++) { + if (occupancy.bits[i] != 0u) { + /* Note: Doesn't have to be atomic but we need to blend with other method. */ + imageAtomicOr(occupancy_img, ivec3(texel, i), occupancy.bits[i]); + } + } +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl index 5902f462656..28e4970950d 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl @@ -2,6 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) + struct OccupancyBits { uint bits[8]; }; @@ -38,12 +40,25 @@ OccupancyBits occupancy_from_depth(float depth, int bit_count) return occupancy; } +/** + * Returns an empty structure, cleared to 0. + */ +OccupancyBits occupancy_new() +{ + OccupancyBits occupancy; + for (int i = 0; i < 8; i++) { + occupancy.bits[i] = 0x0u; + } + return occupancy; +} + /** * Example with for 16bits per layer and 2 layers. * 0 Layer0 15 16 Layer1 31 < Bits index from LSB to MSB (left to right) * |--------------| |--------------| - * 0000000001000000 0000000000000000 < Surf0 - * 0000000000000000 0000001000000000 < Surf1 + * 0000000001000010 0001000000000000 < Surf entry points + * 0000000000000000 0100001000000000 < Surf exit points + * 0000000001111111 1001110000000000 < Result occupancy at each froxel depths after resolve * \a depth in [0..1] range. * \a bit_count in [1..256] range. */ @@ -61,3 +76,100 @@ OccupancyBits occupancy_bit_from_depth(float depth, int bit_count) } return occupancy; } + +/** + * Same as binary OR but for the whole OccupancyBits structure. + */ +OccupancyBits occupancy_or(OccupancyBits a, OccupancyBits b) +{ + OccupancyBits occupancy; + for (int i = 0; i < 8; i++) { + occupancy.bits[i] = a.bits[i] | b.bits[i]; + } + return occupancy; +} + +/** + * Set a series of bits high inside the given OccupancyBits. + */ +OccupancyBits occupancy_set_bits_high(OccupancyBits occupancy, int bit_start, int bit_count) +{ + for (int i = 0; i < bit_count; i++) { + int bit = bit_start + i; + occupancy.bits[bit / 32] |= 1u << uint(bit % 32); + } + return occupancy; +} + +/** + * Same as findLSB but for the whole OccupancyBits structure. + */ +int occupancy_find_lsb(OccupancyBits occupancy) +{ + for (int i = 0; i < 8; i++) { + if (occupancy.bits[i] != 0) { + return findLSB(occupancy.bits[i]) + i * 32; + } + } + return -1; +} + +/** + * Converts the first four occupancy words to a uvec4. + */ +uvec4 occupancy_to_uint4(OccupancyBits occupancy) +{ + return uvec4(occupancy.bits[0], occupancy.bits[1], occupancy.bits[2], occupancy.bits[3]); +} + +/** + * From a entry and exit occupancy tuple, returns if a specific bit is inside the volume. + */ +bool occupancy_bit_resolve(OccupancyBits entry, OccupancyBits exit, int bit_n, int bit_count) +{ + int first_exit = occupancy_find_lsb(exit); + int first_entry = occupancy_find_lsb(entry); + first_exit = (first_exit == -1) ? 99999 : first_exit; + /* Check if the first surface is an exit. If it is, initialize as inside the volume. */ + bool inside_volume = first_exit < first_entry; + for (int j = 0; j <= bit_n / 32; j++) { + uint entry_word = entry.bits[j]; + uint exit_word = exit.bits[j]; + /* TODO(fclem): Could use fewer iteration using findLSB and/or other intrinsics. */ + for (uint i = 0; i < 32; i++) { + if (flag_test(exit_word, 1u << i) && flag_test(entry_word, 1u << i)) { + /* Do nothing. */ + } + else { + if (flag_test(exit_word, 1u << i)) { + inside_volume = false; + } + if (flag_test(entry_word, 1u << i)) { + inside_volume = true; + } + } + if (i + j * 32 == bit_n) { + return inside_volume; + } + } + } + return inside_volume; +} + +/** + * From a entry and exit occupancy tuple, returns the full occupancy map. + */ +OccupancyBits occupancy_resolve(OccupancyBits entry, OccupancyBits exit, int bit_count) +{ + OccupancyBits occupancy; + for (int j = 0; j < 8; j++) { + for (int i = 0; i < 32; i++) { + bool test = false; + if (i < bit_count - j * 32) { + test = occupancy_bit_resolve(entry, exit, i + j * 32, bit_count); + } + set_flag_from_test(occupancy.bits[j], test, 1u << uint(i)); + } + } + return occupancy; +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl index cd2370a0ac6..724a9b0df43 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_test.glsl @@ -5,79 +5,13 @@ /* Directive for resetting the line numbering so the failing tests lines can be printed. * This conflict with the shader compiler error logging scheme. * Comment out for correct compilation error line. */ -#line 9 +// #line 9 #pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) -#pragma BLENDER_REQUIRE(gpu_shader_utildefines_lib.glsl) #pragma BLENDER_REQUIRE(gpu_shader_test_lib.glsl) #define TEST(a, b) if (true) -OccupancyBits occupancy_or(OccupancyBits a, OccupancyBits b) -{ - OccupancyBits occupancy; - for (int i = 0; i < 8; i++) { - occupancy.bits[i] = a.bits[i] | b.bits[i]; - } - return occupancy; -} - -int occupancy_find_lsb(OccupancyBits occupancy) -{ - for (int i = 0; i < 8; i++) { - if (occupancy.bits[i] != 0) { - return findLSB(occupancy.bits[i]) + i * 32; - } - } - return -1; -} - -uvec4 occupancy_to_uint4(OccupancyBits occupancy) -{ - return uvec4(occupancy.bits[0], occupancy.bits[1], occupancy.bits[2], occupancy.bits[3]); -} - -bool occupancy_bit_resolve(OccupancyBits entry, OccupancyBits exit, int bit_n, int bit_count) -{ - int first_exit = occupancy_find_lsb(exit); - int first_entry = occupancy_find_lsb(entry); - first_exit = (first_exit == -1) ? 99999 : first_exit; - /* Check if the first surface is an exit. If it is, initialize as inside the volume. */ - bool inside_volume = first_exit < first_entry; - for (int j = 0; j <= bit_n / 32; j++) { - uint entry_word = entry.bits[j]; - uint exit_word = exit.bits[j]; - /* TODO(fclem): Could use fewer iteration using findLSB and/or other intrinsics. */ - for (uint i = 0; i < 32; i++) { - if ((entry_word >> i) & 1u) { - inside_volume = true; - } - if ((exit_word >> i) & 1u) { - inside_volume = false; - } - if (i + j * 32 == bit_n) { - return inside_volume; - } - } - } - return inside_volume; -} - -OccupancyBits occupancy_resolve(OccupancyBits entry, OccupancyBits exit, int bit_count) -{ - OccupancyBits occupancy; - for (int j = 0; j < 8; j++) { - for (int i = 0; i < 32; i++) { - bool test = false; - if (i < bit_count - j * 32) { - test = occupancy_bit_resolve(entry, exit, i + j * 32, bit_count); - } - set_flag_from_test(occupancy.bits[j], test, 1u << uint(i)); - } - } - return occupancy; -} - void main() { TEST(eevee_occupancy, Occupancy) @@ -124,8 +58,8 @@ void main() /* Test composing occupancy an the expected result. */ /* Start empty. */ - OccupancyBits entry = occupancy_bit_from_depth(-1.0, 32); - OccupancyBits exit = occupancy_bit_from_depth(-1.0, 32); + OccupancyBits entry = occupancy_new(); + OccupancyBits exit = occupancy_new(); entry = occupancy_or(entry, occupancy_bit_from_depth(1.0 / 32.0, 32)); /* Second entry at the same depth. Should not change anything. */ entry = occupancy_or(entry, occupancy_bit_from_depth(1.1 / 32.0, 32)); @@ -148,8 +82,8 @@ void main() EXPECT_EQ(occup.bits[0], 390u); /* 0001 1000 0110 */ /* Start empty. */ - entry = occupancy_bit_from_depth(-1.0, 44); - exit = occupancy_bit_from_depth(-1.0, 44); + entry = occupancy_new(); + exit = occupancy_new(); /* First exit. Anything prior should be considered in volume. */ exit = occupancy_or(exit, occupancy_bit_from_depth(33.0 / 44.0, 44)); /* First entry. */ @@ -167,5 +101,10 @@ void main() occup = occupancy_resolve(entry, exit, 44); /* 0000 1111 0001 1111 1111 1111 1111 1111 1111 1111 1111 */ EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFFFFFFu, 0x0F1u, 0u, 0u)); + + occup = occupancy_new(); + occup = occupancy_set_bits_high(occup, 16, 32); + occup = occupancy_set_bits_high(occup, 80, 16); + EXPECT_EQ(occupancy_to_uint4(occup), uvec4(0xFFFF0000u, 0x0000FFFFu, 0xFFFF0000u, 0u)); } } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index f729102fdc4..c9daa9314d7 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -29,6 +29,9 @@ void main() float jitter = sampling_rng_1D_get(SAMPLING_VOLUME_W) * uniform_buf.volumes.inv_tex_size.z; float volume_z = screen_to_volume(ss_P).z - jitter; +#if 0 + /* XOR technique: + * Gives correct result for manifold meshes in and out of view. */ OccupancyBits occupancy_bits = occupancy_from_depth(volume_z, uniform_buf.volumes.tex_size.z); for (int i = 0; i < imageSize(occupancy_img).z; i++) { @@ -40,4 +43,11 @@ void main() imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits.bits[i]); } } +#else + uint hit_id = imageAtomicAdd(hit_count_img, texel, 1u); + if (hit_id < VOLUME_HIT_DEPTH_MAX) { + float value = gl_FrontFacing ? volume_z : -volume_z; + imageStore(hit_depth_img, ivec3(texel, hit_id), vec4(value)); + } +#endif } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl index c1af27b6aa4..7906fe384cc 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_volume_material_comp.glsl @@ -9,6 +9,7 @@ #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) /* Based on Frosbite Unified Volumetric. * https://www.ea.com/frostbite/news/physically-based-unified-volumetric-rendering-in-frostbite */ @@ -58,7 +59,6 @@ void main() int occupancy_layer = froxel.z >> shift; /* Modulo 32. */ uint occupancy_shift = froxel.z & mask; - uint occupancy_bits = imageLoad(occupancy_img, ivec3(froxel.xy, occupancy_layer)).r; if (((occupancy_bits >> occupancy_shift) & 1u) == 0u) { return; diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 6fa1df53d60..0328db8e154 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -310,6 +310,12 @@ GPU_SHADER_CREATE_INFO(eevee_volume_world) GPU_SHADER_CREATE_INFO(eevee_surf_occupancy) .define("MAT_OCCUPANCY") .builtins(BuiltinBits::TEXTURE_ATOMIC) + .image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_3D, "hit_depth_img") + .image(VOLUME_HIT_COUNT_SLOT, + GPU_R32UI, + Qualifier::READ_WRITE, + ImageType::UINT_2D, + "hit_count_img ") .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::READ_WRITE, diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh index d2e44d5f352..63c7c54ab70 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh @@ -60,6 +60,23 @@ GPU_SHADER_CREATE_INFO(eevee_volume_scatter_with_lights) .sampler(0, ImageType::FLOAT_3D, "extinction_tx") .do_static_compilation(true); +GPU_SHADER_CREATE_INFO(eevee_volume_occupancy_convert) + .additional_info("eevee_shared", "eevee_global_ubo", "draw_fullscreen") + .local_group_size(VOLUME_INTEGRATION_GROUP_SIZE, VOLUME_INTEGRATION_GROUP_SIZE, 1) + .image(VOLUME_HIT_DEPTH_SLOT, + GPU_R32F, + Qualifier::READ_WRITE, + ImageType::FLOAT_3D, + "hit_depth_img") + .image(VOLUME_HIT_COUNT_SLOT, GPU_R32UI, Qualifier::READ, ImageType::UINT_2D, "hit_count_img ") + .image(VOLUME_OCCUPANCY_SLOT, + GPU_R32UI, + Qualifier::READ_WRITE, + ImageType::UINT_3D, + "occupancy_img") + .fragment_source("eevee_occupancy_convert_frag.glsl") + .do_static_compilation(true); + GPU_SHADER_CREATE_INFO(eevee_volume_integration) .additional_info("eevee_shared", "eevee_global_ubo", "draw_view") .compute_source("eevee_volume_integration_comp.glsl") -- 2.30.2 From a5caa73975b531748970155379006f1d63510489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Wed, 18 Oct 2023 00:02:28 +0200 Subject: [PATCH 12/23] Metal: Add atomicOr support --- .../gpu/shaders/metal/mtl_shader_defines.msl | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl index 55b44ac4a19..988431404f2 100644 --- a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl +++ b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl @@ -984,6 +984,49 @@ inline void _texture_write_internal_fast(thread _mtl_combined_image_sampler_3d +S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_1d tex, + int coord, + S data) +{ + return tex.texture->atomic_fetch_or(uint(coord), vec(data)).x; +} + +template +S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_1d_array tex, + int2 coord, + S data) +{ + return tex.texture->atomic_fetch_or(uint(coord.x), uint(coord.y), vec(data)).x; +} + +template +S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_2d tex, + int2 coord, + S data) +{ + return tex.texture->atomic_fetch_or(uint2(coord.xy), vec(data)).x; +} + +template +S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_2d_array tex, + int3 coord, + S data) +{ + return tex.texture->atomic_fetch_or(uint2(coord.xy), uint(coord.z), vec(data)).x; +} + +template +S _texture_image_atomic_or_internal(thread _mtl_combined_image_sampler_3d tex, + int3 coord, + S data) +{ + return tex.texture->atomic_fetch_or(uint3(coord), vec(data)).x; +} /* Atomic XOR. */ template -- 2.30.2 From f05798e55ab96f81ac7f1af11e8e9959859ddb67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Wed, 18 Oct 2023 00:02:55 +0200 Subject: [PATCH 13/23] Small fixes for metal --- .../shaders/eevee_occupancy_convert_frag.glsl | 3 +- .../shaders/eevee_occupancy_lib.glsl | 2 +- .../shaders/infos/eevee_material_info.hh | 2 +- .../shaders/infos/eevee_volume_info.hh | 4 +- .../gpu/shaders/metal/mtl_shader_defines.msl | 48 +++++++++++++++++-- 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl index 242cde7b0ce..0cbdf40b7ca 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl @@ -21,7 +21,8 @@ void main() ivec2 texel = ivec2(gl_FragCoord.xy); - int hit_count = int(min(imageLoad(hit_count_img, texel).x, VOLUME_HIT_DEPTH_MAX)); + int hit_count = int(imageLoad(hit_count_img, texel).x); + hit_count = min(hit_count, VOLUME_HIT_DEPTH_MAX); if (hit_count == 0) { return; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl index 28e4970950d..ee39e138c1f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_lib.glsl @@ -148,7 +148,7 @@ bool occupancy_bit_resolve(OccupancyBits entry, OccupancyBits exit, int bit_n, i inside_volume = true; } } - if (i + j * 32 == bit_n) { + if (i + j * 32 == uint(bit_n)) { return inside_volume; } } diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 0328db8e154..61016d9229f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -315,7 +315,7 @@ GPU_SHADER_CREATE_INFO(eevee_surf_occupancy) GPU_R32UI, Qualifier::READ_WRITE, ImageType::UINT_2D, - "hit_count_img ") + "hit_count_img") .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::READ_WRITE, diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh index 63c7c54ab70..9e20a42cca6 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh @@ -62,13 +62,13 @@ GPU_SHADER_CREATE_INFO(eevee_volume_scatter_with_lights) GPU_SHADER_CREATE_INFO(eevee_volume_occupancy_convert) .additional_info("eevee_shared", "eevee_global_ubo", "draw_fullscreen") - .local_group_size(VOLUME_INTEGRATION_GROUP_SIZE, VOLUME_INTEGRATION_GROUP_SIZE, 1) + .builtins(BuiltinBits::TEXTURE_ATOMIC) .image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::READ_WRITE, ImageType::FLOAT_3D, "hit_depth_img") - .image(VOLUME_HIT_COUNT_SLOT, GPU_R32UI, Qualifier::READ, ImageType::UINT_2D, "hit_count_img ") + .image(VOLUME_HIT_COUNT_SLOT, GPU_R32UI, Qualifier::READ, ImageType::UINT_2D, "hit_count_img") .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::READ_WRITE, diff --git a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl index 988431404f2..465aa641481 100644 --- a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl +++ b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl @@ -980,12 +980,11 @@ inline void _texture_write_internal_fast(thread _mtl_combined_image_sampler_3d @@ -1110,6 +1109,47 @@ S _texture_image_atomic_min_internal(thread _mtl_combined_image_sampler_3d return tex.texture->atomic_fetch_min(uint3(coord), vec(data)).x; } +/* Atomic Add. */ +template +S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_1d tex, + int coord, + S data) +{ + return tex.texture->atomic_fetch_add(uint(coord), vec(data)).x; +} + +template +S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_1d_array tex, + int2 coord, + S data) +{ + return tex.texture->atomic_fetch_add(uint(coord.x), uint(coord.y), vec(data)).x; +} + +template +S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_2d tex, + int2 coord, + S data) +{ + return tex.texture->atomic_fetch_add(uint2(coord.xy), vec(data)).x; +} + +template +S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_2d_array tex, + int3 coord, + S data) +{ + return tex.texture->atomic_fetch_add(uint2(coord.xy), uint(coord.z), vec(data)).x; +} + +template +S _texture_image_atomic_add_internal(thread _mtl_combined_image_sampler_3d tex, + int3 coord, + S data) +{ + return tex.texture->atomic_fetch_add(uint3(coord), vec(data)).x; +} + /* Atomic Exchange. */ template S _texture_image_atomic_exchange_internal(thread _mtl_combined_image_sampler_1d tex, -- 2.30.2 From a269802a0f6cc8fb588ee2c2d747965bc309e29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cle=CC=81ment=20Foucault?= Date: Wed, 18 Oct 2023 11:06:43 +0200 Subject: [PATCH 14/23] Add infinite projection matrix --- source/blender/blenlib/BLI_math_matrix.hh | 59 +++++++++++++++++++ source/blender/blenlib/intern/math_matrix.cc | 2 + .../draw/engines/eevee_next/eevee_volume.cc | 14 ++++- .../shaders/eevee_occupancy_convert_frag.glsl | 2 +- .../shaders/eevee_surf_occupancy_frag.glsl | 7 +-- .../shaders/infos/eevee_volume_info.hh | 6 +- 6 files changed, 79 insertions(+), 11 deletions(-) diff --git a/source/blender/blenlib/BLI_math_matrix.hh b/source/blender/blenlib/BLI_math_matrix.hh index fcc8da73c59..78fb886c3e6 100644 --- a/source/blender/blenlib/BLI_math_matrix.hh +++ b/source/blender/blenlib/BLI_math_matrix.hh @@ -405,6 +405,15 @@ template [[nodiscard]] MatBase orthographic( T left, T right, T bottom, T top, T near_clip, T far_clip); +/** + * \brief Create an orthographic projection matrix using OpenGL coordinate convention: + * Maps each axis range to [-1..1] range for all axes except Z. + * The Z axis is collapsed to 0 which eliminates the depth component. So it cannot be used with + * depth testing. + * The resulting matrix can be used with either #project_point or #transform_point. + */ +template MatBase orthographic_infinite(T left, T right, T bottom, T top); + /** * \brief Create a perspective projection matrix using OpenGL coordinate convention: * Maps each axis range to [-1..1] range for all axes. @@ -425,6 +434,16 @@ template [[nodiscard]] MatBase perspective_fov( T angle_left, T angle_right, T angle_bottom, T angle_top, T near_clip, T far_clip); +/** + * \brief Create a perspective projection matrix using OpenGL coordinate convention: + * Maps each axis range to [-1..1] range for all axes except for the Z where [near_clip..inf] is + * mapped to [-1..1]. + * `left`, `right`, `bottom`, `top` are frustum side distances at `z=near_clip`. + * The resulting matrix can be used with #project_point. + */ +template +[[nodiscard]] MatBase perspective_infinite(T left, T right, T bottom, T top, T near_clip); + } // namespace projection /** \} */ @@ -1554,6 +1573,23 @@ MatBase orthographic(T left, T right, T bottom, T top, T near_clip, T f return mat; } +template MatBase orthographic_infinite(T left, T right, T bottom, T top) +{ + const T x_delta = right - left; + const T y_delta = top - bottom; + + MatBase mat = MatBase::identity(); + if (x_delta != 0 && y_delta != 0) { + mat[0][0] = T(2.0) / x_delta; + mat[3][0] = -(right + left) / x_delta; + mat[1][1] = T(2.0) / y_delta; + mat[3][1] = -(top + bottom) / y_delta; + mat[2][2] = 0.0f; + mat[3][2] = 0.0f; + } + return mat; +} + template MatBase perspective(T left, T right, T bottom, T top, T near_clip, T far_clip) { @@ -1575,6 +1611,29 @@ MatBase perspective(T left, T right, T bottom, T top, T near_clip, T fa return mat; } +template +MatBase perspective_infinite(T left, T right, T bottom, T top, T near_clip) +{ + const T x_delta = right - left; + const T y_delta = top - bottom; + + /* From "Projection Matrix Tricks" by Eric Lengyel GDC 2007. */ + MatBase mat = MatBase::identity(); + if (x_delta != 0 && y_delta != 0) { + mat[0][0] = near_clip * T(2.0) / x_delta; + mat[1][1] = near_clip * T(2.0) / y_delta; + mat[2][0] = (right + left) / x_delta; /* NOTE: negate Z. */ + mat[2][1] = (top + bottom) / y_delta; + /* Page 17. Choosing an epsilon for 32 bit floating-point precision. */ + constexpr float eps = 2.4e-7f; + mat[2][2] = -1.0f; + mat[2][3] = (eps - 1.0f); + mat[3][2] = (eps - 2.0f) * near_clip; + mat[3][3] = 0.0f; + } + return mat; +} + template [[nodiscard]] MatBase perspective_fov( T angle_left, T angle_right, T angle_bottom, T angle_top, T near_clip, T far_clip) diff --git a/source/blender/blenlib/intern/math_matrix.cc b/source/blender/blenlib/intern/math_matrix.cc index a52219b98b4..9bba5257aeb 100644 --- a/source/blender/blenlib/intern/math_matrix.cc +++ b/source/blender/blenlib/intern/math_matrix.cc @@ -505,6 +505,8 @@ template float4x4 orthographic( float left, float right, float bottom, float top, float near_clip, float far_clip); template float4x4 perspective( float left, float right, float bottom, float top, float near_clip, float far_clip); +template float4x4 perspective_infinite( + float left, float right, float bottom, float top, float near_clip); } // namespace projection diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index 841b5d2d57d..76d8d119daa 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -220,9 +220,21 @@ void VolumeModule::draw_prepass(View &view) DRW_stats_group_start("Volumes"); inst_.pipelines.world_volume.render(view); + float left, right, bottom, top, near, far; + float4x4 winmat = view.winmat(); + projmat_dimensions(winmat.ptr(), &left, &right, &bottom, &top, &near, &far); + + float4x4 winmat_infinite = view.is_persp() ? + math::projection::perspective_infinite( + left, right, bottom, top, near) : + math::projection::orthographic_infinite(left, right, bottom, top); + + View volume_view = {"Volume View"}; + volume_view.sync(view.viewmat(), winmat_infinite); + if (inst_.pipelines.volume.is_enabled()) { occupancy_fb_.bind(); - inst_.pipelines.volume.render(view, occupancy_tx_, hit_count_tx_); + inst_.pipelines.volume.render(volume_view, occupancy_tx_, hit_count_tx_); } DRW_stats_group_end(); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl index 0cbdf40b7ca..262dc361169 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl @@ -42,7 +42,7 @@ void main() hit_ordered[hit_index[i]] = hit_depths[i]; } -#if 1 /* Debug. Need to adjust the qualifier of the texture. */ +#if 0 /* Debug. Need to adjust the qualifier of the texture adjusted. */ for (int i = 0; i < hit_count; i++) { imageStore(hit_depth_img, ivec3(texel, i), vec4(hit_ordered[i])); } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index c9daa9314d7..cf5f772e0f1 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -21,15 +21,14 @@ vec4 closure_to_rgba(Closure cl) void main() { ivec2 texel = ivec2(gl_FragCoord.xy); - vec2 uv = gl_FragCoord.xy / vec2(imageSize(occupancy_img).xy); - vec3 ss_P = vec3(uv, gl_FragCoord.z); + float vPz = dot(drw_view_forward(), interp.P) - dot(drw_view_forward(), drw_view_position()); /* Apply jitter here instead of modifying the projection matrix. * This is because the depth range and mapping function changes. */ /* TODO(fclem): Jitter the camera for the other 2 dimension. */ float jitter = sampling_rng_1D_get(SAMPLING_VOLUME_W) * uniform_buf.volumes.inv_tex_size.z; - float volume_z = screen_to_volume(ss_P).z - jitter; + float volume_z = view_z_to_volume_z(vPz) - jitter; -#if 0 +#if 1 /* XOR technique: * Gives correct result for manifold meshes in and out of view. */ OccupancyBits occupancy_bits = occupancy_from_depth(volume_z, uniform_buf.volumes.tex_size.z); diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh index 9e20a42cca6..c7303183635 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh @@ -63,11 +63,7 @@ GPU_SHADER_CREATE_INFO(eevee_volume_scatter_with_lights) GPU_SHADER_CREATE_INFO(eevee_volume_occupancy_convert) .additional_info("eevee_shared", "eevee_global_ubo", "draw_fullscreen") .builtins(BuiltinBits::TEXTURE_ATOMIC) - .image(VOLUME_HIT_DEPTH_SLOT, - GPU_R32F, - Qualifier::READ_WRITE, - ImageType::FLOAT_3D, - "hit_depth_img") + .image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::READ, ImageType::FLOAT_3D, "hit_depth_img") .image(VOLUME_HIT_COUNT_SLOT, GPU_R32UI, Qualifier::READ, ImageType::UINT_2D, "hit_count_img") .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, -- 2.30.2 From 158e7114742f6dc0e74ec12df0740da1e5509d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 18 Oct 2023 15:17:10 +0200 Subject: [PATCH 15/23] Add new geometry type for volume object bounds This allow proper tagging of the bounding box froxels --- source/blender/draw/CMakeLists.txt | 1 + .../draw/engines/eevee_next/eevee_material.cc | 4 ++-- .../draw/engines/eevee_next/eevee_material.hh | 6 ++++- .../draw/engines/eevee_next/eevee_shader.cc | 19 ++++++++++++--- .../draw/engines/eevee_next/eevee_sync.cc | 6 ++--- .../shaders/eevee_attributes_lib.glsl | 2 +- .../shaders/eevee_geom_volume_vert.glsl | 24 +++++++++++++++++++ .../shaders/eevee_surf_occupancy_frag.glsl | 1 + .../shaders/infos/eevee_material_info.hh | 14 ++++++++++- 9 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 source/blender/draw/engines/eevee_next/shaders/eevee_geom_volume_vert.glsl diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index ccd1ecb2717..076951c197f 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -500,6 +500,7 @@ set(GLSL_SRC engines/eevee_next/shaders/eevee_geom_gpencil_vert.glsl engines/eevee_next/shaders/eevee_geom_mesh_vert.glsl engines/eevee_next/shaders/eevee_geom_point_cloud_vert.glsl + engines/eevee_next/shaders/eevee_geom_volume_vert.glsl engines/eevee_next/shaders/eevee_geom_world_vert.glsl engines/eevee_next/shaders/eevee_hiz_debug_frag.glsl engines/eevee_next/shaders/eevee_hiz_update_comp.glsl diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 2c7efd8d61d..4df2354ac41 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -241,12 +241,12 @@ Material &MaterialModule::material_sync(Object *ob, eMaterialGeometry geometry_type, bool has_motion) { - if (geometry_type == MAT_GEOM_VOLUME_OBJECT) { + if (geometry_type == MAT_GEOM_VOLUME) { MaterialKey material_key(blender_mat, geometry_type, MAT_PIPE_VOLUME_MATERIAL); Material &mat = material_map_.lookup_or_add_cb(material_key, [&]() { Material mat = {}; mat.volume_occupancy = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, MAT_GEOM_MESH); + ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, MAT_GEOM_VOLUME); mat.volume_material = material_pass_get( ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT); return mat; diff --git a/source/blender/draw/engines/eevee_next/eevee_material.hh b/source/blender/draw/engines/eevee_next/eevee_material.hh index b6eda40d539..8546d41e95d 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.hh +++ b/source/blender/draw/engines/eevee_next/eevee_material.hh @@ -40,10 +40,14 @@ enum eMaterialPipeline { }; enum eMaterialGeometry { + /* These maps directly to object types. */ MAT_GEOM_MESH = 0, MAT_GEOM_POINT_CLOUD, MAT_GEOM_CURVES, MAT_GEOM_GPENCIL, + MAT_GEOM_VOLUME, + + /* These maps to special vertex shader. */ MAT_GEOM_VOLUME_OBJECT, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_WORLD, @@ -110,7 +114,7 @@ static inline eMaterialGeometry to_material_geometry(const Object *ob) case OB_CURVES: return MAT_GEOM_CURVES; case OB_VOLUME: - return MAT_GEOM_VOLUME_OBJECT; + return MAT_GEOM_VOLUME; case OB_GPENCIL_LEGACY: return MAT_GEOM_GPENCIL; case OB_POINTCLOUD: diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index dcd70f5e02f..30a5c4a5be3 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -428,6 +428,7 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu } info.vertex_inputs_.clear(); break; + case MAT_GEOM_VOLUME: case MAT_GEOM_VOLUME_OBJECT: case MAT_GEOM_VOLUME_WORLD: /** Volume grid attributes come from 3D textures. Transfer attributes to samplers. */ @@ -438,8 +439,12 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu break; } - const bool do_vertex_attrib_load = !ELEM( - geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT); + const bool do_vertex_attrib_load = !ELEM(geometry_type, + MAT_GEOM_WORLD, + MAT_GEOM_VOLUME_WORLD, + MAT_GEOM_VOLUME_OBJECT, + MAT_GEOM_VOLUME, + MAT_GEOM_VOLUME); if (!do_vertex_attrib_load && !info.vertex_out_interfaces_.is_empty()) { /* Codegen outputs only one interface. */ @@ -476,7 +481,12 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu } if (!is_compute) { - if (!ELEM(geometry_type, MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT)) { + if (!ELEM(geometry_type, + MAT_GEOM_WORLD, + MAT_GEOM_VOLUME_WORLD, + MAT_GEOM_VOLUME_OBJECT, + MAT_GEOM_VOLUME)) + { vert_gen << "vec3 nodetree_displacement()\n"; vert_gen << "{\n"; vert_gen << ((codegen.displacement) ? codegen.displacement : "return vec3(0);\n"); @@ -549,6 +559,9 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu case MAT_GEOM_POINT_CLOUD: info.additional_info("eevee_geom_point_cloud"); break; + case MAT_GEOM_VOLUME: + info.additional_info("eevee_geom_volume"); + break; } /* Pipeline Info. */ switch (geometry_type) { diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.cc b/source/blender/draw/engines/eevee_next/eevee_sync.cc index bbd7c239ed7..fdf5b3ccf04 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sync.cc @@ -328,10 +328,10 @@ void SyncModule::sync_volume(Object *ob, ObjectHandle & /*ob_handle*/, ResourceH const bool has_motion = false; Material &material = inst_.materials.material_get( - ob, has_motion, material_slot - 1, MAT_GEOM_VOLUME_OBJECT); + ob, has_motion, material_slot - 1, MAT_GEOM_VOLUME); - /* Use bounding volume geometry to tag empty spaces. */ - GPUBatch *geom = DRW_cache_volume_selection_surface_get(ob); + /* Use bounding box tag empty spaces. */ + GPUBatch *geom = DRW_cache_cube_get(); geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl index d30f3ff0c96..0d617ae3974 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_attributes_lib.glsl @@ -230,7 +230,7 @@ float attr_load_float(samplerBuffer cd_buf) /** \} */ -#elif defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD) +#elif defined(MAT_GEOM_VOLUME) || defined(MAT_GEOM_VOLUME_OBJECT) || defined(MAT_GEOM_VOLUME_WORLD) /* -------------------------------------------------------------------- */ /** \name Volume diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_geom_volume_vert.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_volume_vert.glsl new file mode 100644 index 00000000000..d9ccedec578 --- /dev/null +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_geom_volume_vert.glsl @@ -0,0 +1,24 @@ +/* SPDX-FileCopyrightText: 2022-2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma BLENDER_REQUIRE(draw_model_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) +#pragma BLENDER_REQUIRE(common_pointcloud_lib.glsl) + +void main() +{ + DRW_VIEW_FROM_RESOURCE_ID; + + init_interface(); + + /* TODO(fclem): Find a better way? This is reverting what draw_resource_finalize does. */ + vec3 size = safe_rcp(OrcoTexCoFactors[1].xyz * 2.0); /* Box half-extent. */ + vec3 loc = size + (OrcoTexCoFactors[0].xyz / -OrcoTexCoFactors[1].xyz); /* Box center. */ + + /* Use bounding box geometry for now. */ + vec3 lP = loc + pos * size; + interp.P = drw_point_object_to_world(lP); + + gl_Position = drw_point_world_to_homogenous(interp.P); +} diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index cf5f772e0f1..a72ade1abf0 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -9,6 +9,7 @@ #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) #pragma BLENDER_REQUIRE(eevee_nodetree_lib.glsl) #pragma BLENDER_REQUIRE(eevee_surf_lib.glsl) +#pragma BLENDER_REQUIRE(eevee_attributes_lib.glsl) #pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl) #pragma BLENDER_REQUIRE(eevee_volume_lib.glsl) #pragma BLENDER_REQUIRE(eevee_occupancy_lib.glsl) diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index 61016d9229f..d276e31bf93 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -78,6 +78,17 @@ GPU_SHADER_CREATE_INFO(eevee_geom_point_cloud) "draw_resource_id_varying", "draw_view"); +GPU_SHADER_CREATE_INFO(eevee_geom_volume) + .additional_info("eevee_shared") + .define("MAT_GEOM_VOLUME") + .vertex_in(0, Type::VEC3, "pos") + .vertex_out(eevee_surf_iface) + .vertex_source("eevee_geom_volume_vert.glsl") + .additional_info("draw_modelmat_new", + "draw_object_infos_new", + "draw_resource_id_varying", + "draw_view"); + GPU_SHADER_CREATE_INFO(eevee_geom_gpencil) .additional_info("eevee_shared") .define("MAT_GEOM_GPENCIL") @@ -349,7 +360,8 @@ GPU_SHADER_CREATE_INFO(eevee_material_stub) /* EEVEE_MAT_FINAL_VARIATION(prefix##_gpencil, "eevee_geom_gpencil", __VA_ARGS__) */ \ EEVEE_MAT_FINAL_VARIATION(prefix##_curves, "eevee_geom_curves", __VA_ARGS__) \ EEVEE_MAT_FINAL_VARIATION(prefix##_mesh, "eevee_geom_mesh", __VA_ARGS__) \ - EEVEE_MAT_FINAL_VARIATION(prefix##_point_cloud, "eevee_geom_point_cloud", __VA_ARGS__) + EEVEE_MAT_FINAL_VARIATION(prefix##_point_cloud, "eevee_geom_point_cloud", __VA_ARGS__) \ + EEVEE_MAT_FINAL_VARIATION(prefix##_volume, "eevee_geom_volume", __VA_ARGS__) # define EEVEE_MAT_PIPE_VARIATIONS(name, ...) \ EEVEE_MAT_GEOM_VARIATIONS(name##_world, "eevee_surf_world", __VA_ARGS__) \ -- 2.30.2 From 21f08cc0073193102d252ca15bdd7f7a55fc4f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 18 Oct 2023 21:06:17 +0200 Subject: [PATCH 16/23] Add option to switch between modes, avoid clearing the hit count texture between layers --- scripts/startup/bl_ui/properties_material.py | 1 + .../draw/engines/eevee_next/eevee_material.cc | 12 ++++--- .../draw/engines/eevee_next/eevee_pipeline.cc | 25 ++++++++++++-- .../draw/engines/eevee_next/eevee_pipeline.hh | 14 ++++++-- .../shaders/eevee_occupancy_convert_frag.glsl | 4 +++ .../shaders/eevee_surf_occupancy_frag.glsl | 34 +++++++++---------- .../shaders/infos/eevee_material_info.hh | 1 + .../shaders/infos/eevee_volume_info.hh | 6 +++- source/blender/makesdna/DNA_material_types.h | 10 +++++- .../blender/makesrna/intern/rna_material.cc | 24 +++++++++++++ 10 files changed, 102 insertions(+), 29 deletions(-) diff --git a/scripts/startup/bl_ui/properties_material.py b/scripts/startup/bl_ui/properties_material.py index 641b16151d0..e54ed3aa3bd 100644 --- a/scripts/startup/bl_ui/properties_material.py +++ b/scripts/startup/bl_ui/properties_material.py @@ -273,6 +273,7 @@ class EEVEE_NEXT_MATERIAL_PT_settings(MaterialButtonsPanel, Panel): layout.prop(mat, "show_transparent_back") layout.prop(mat, "use_screen_refraction") + layout.prop(mat, "volume_intersection_method") layout.prop(mat, "pass_index") diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 4df2354ac41..110832e987d 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -255,8 +255,10 @@ Material &MaterialModule::material_sync(Object *ob, /* Volume needs to use one sub pass per object to support layering. */ VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(ob); if (layer) { - mat.volume_occupancy.sub_pass = layer->occupancy_add(mat.volume_occupancy.gpumat); - mat.volume_material.sub_pass = layer->material_add(mat.volume_material.gpumat); + mat.volume_occupancy.sub_pass = layer->occupancy_add( + ob, blender_mat, mat.volume_occupancy.gpumat); + mat.volume_material.sub_pass = layer->material_add( + ob, blender_mat, mat.volume_material.gpumat); } else { /* Culled volumes. */ @@ -359,8 +361,10 @@ Material &MaterialModule::material_sync(Object *ob, /* Volume needs to use one sub pass per object to support layering. */ VolumeLayer *layer = inst_.pipelines.volume.register_and_get_layer(ob); if (layer) { - mat.volume_occupancy.sub_pass = layer->occupancy_add(mat.volume_occupancy.gpumat); - mat.volume_material.sub_pass = layer->material_add(mat.volume_material.gpumat); + mat.volume_occupancy.sub_pass = layer->occupancy_add( + ob, blender_mat, mat.volume_occupancy.gpumat); + mat.volume_material.sub_pass = layer->material_add( + ob, blender_mat, mat.volume_material.gpumat); } else { /* Culled volumes. */ diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index bc7caaf981a..56723124986 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -765,6 +765,7 @@ void DeferredPipeline::render(View &main_view, void VolumeLayer::sync() { object_bounds_.clear(); + use_hit_list = false; draw::PassMain &layer_pass = volume_layer_ps_; layer_pass.init(); @@ -797,16 +798,25 @@ void VolumeLayer::sync() } } -PassMain::Sub *VolumeLayer::occupancy_add(GPUMaterial *gpumat) +PassMain::Sub *VolumeLayer::occupancy_add(const Object *ob, + const ::Material *blender_mat, + GPUMaterial *gpumat) { BLI_assert_msg(GPU_material_has_volume_output(gpumat) == true, "Only volume material should be added here"); + bool use_fast_occupancy = (ob->type == OB_VOLUME) || + (blender_mat->volume_intersection_method == MA_VOLUME_ISECT_FAST); + use_hit_list |= !use_fast_occupancy; + PassMain::Sub *pass = &occupancy_ps_->sub(GPU_material_get_name(gpumat)); pass->material_set(*inst_.manager, gpumat); + pass->push_constant("use_fast_method", use_fast_occupancy); return pass; } -PassMain::Sub *VolumeLayer::material_add(GPUMaterial *gpumat) +PassMain::Sub *VolumeLayer::material_add(const Object * /*ob*/, + const ::Material * /*blender_mat*/, + GPUMaterial *gpumat) { BLI_assert_msg(GPU_material_has_volume_output(gpumat) == true, "Only volume material should be added here"); @@ -842,7 +852,6 @@ void VolumePipeline::render(View &view, Texture &occupancy_tx, Texture &hit_coun /* TODO(fclem): We might want to skip empty layers as the clear overhead is be significant. */ /* TODO(fclem): Move this clear inside the render pass. */ occupancy_tx.clear(uint4(0u)); - hit_count_tx.clear(uint4(0u)); (*layer).render(view); } } @@ -942,6 +951,16 @@ void VolumePipeline::material_call(MaterialPass &volume_material_pass, } } +bool VolumePipeline::use_hit_list() const +{ + for (auto &layer : layers_) { + if ((*layer).use_hit_list) { + return true; + } + } + return false; +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index 5a8ee71e689..8ada8d12cdc 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -302,6 +302,9 @@ struct GridAABB { * A volume layer contains a list of non-overlapping volume objects. */ class VolumeLayer { + public: + bool use_hit_list = false; + private: Instance &inst_; @@ -318,8 +321,12 @@ class VolumeLayer { this->sync(); } - PassMain::Sub *occupancy_add(GPUMaterial *gpumat); - PassMain::Sub *material_add(GPUMaterial *gpumat); + PassMain::Sub *occupancy_add(const Object *ob, + const ::Material *blender_mat, + GPUMaterial *gpumat); + PassMain::Sub *material_add(const Object *ob, + const ::Material *blender_mat, + GPUMaterial *gpumat); /* Return true if the given bounds overlaps any of the contained object in this layer. */ bool bounds_overlaps(const GridAABB &object_aabb) const @@ -374,6 +381,9 @@ class VolumePipeline { return enabled_; } + /* Returns true if any volume layer uses the hist list. */ + bool use_hit_list() const; + private: /** * Returns Axis aligned bounding box in the volume grid. diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl index 262dc361169..56eb9055277 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_occupancy_convert_frag.glsl @@ -23,10 +23,14 @@ void main() int hit_count = int(imageLoad(hit_count_img, texel).x); hit_count = min(hit_count, VOLUME_HIT_DEPTH_MAX); + if (hit_count == 0) { return; } + /* Clear the texture for next layer / frame. */ + imageStore(hit_count_img, texel, uvec4(0)); + for (int i = 0; i < hit_count; i++) { hit_depths[i] = imageLoad(hit_depth_img, ivec3(texel, i)).r; } diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index a72ade1abf0..6c5e453caf8 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -29,25 +29,23 @@ void main() float jitter = sampling_rng_1D_get(SAMPLING_VOLUME_W) * uniform_buf.volumes.inv_tex_size.z; float volume_z = view_z_to_volume_z(vPz) - jitter; -#if 1 - /* XOR technique: - * Gives correct result for manifold meshes in and out of view. */ - OccupancyBits occupancy_bits = occupancy_from_depth(volume_z, uniform_buf.volumes.tex_size.z); - - for (int i = 0; i < imageSize(occupancy_img).z; i++) { - /* Negate occupancy bits before XORing so that meshes clipped by the near plane fill the space - * betwen the inner part of the mesh and the near plane. - * It doesn't change anything for closed meshes. */ - occupancy_bits.bits[i] = ~occupancy_bits.bits[i]; - if (occupancy_bits.bits[i] != 0u) { - imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits.bits[i]); + if (use_fast_method) { + OccupancyBits occupancy_bits = occupancy_from_depth(volume_z, uniform_buf.volumes.tex_size.z); + for (int i = 0; i < imageSize(occupancy_img).z; i++) { + /* Negate occupancy bits before XORing so that meshes clipped by the near plane fill the + * space betwen the inner part of the mesh and the near plane. + * It doesn't change anything for closed meshes. */ + occupancy_bits.bits[i] = ~occupancy_bits.bits[i]; + if (occupancy_bits.bits[i] != 0u) { + imageAtomicXor(occupancy_img, ivec3(texel, i), occupancy_bits.bits[i]); + } } } -#else - uint hit_id = imageAtomicAdd(hit_count_img, texel, 1u); - if (hit_id < VOLUME_HIT_DEPTH_MAX) { - float value = gl_FrontFacing ? volume_z : -volume_z; - imageStore(hit_depth_img, ivec3(texel, hit_id), vec4(value)); + else { + uint hit_id = imageAtomicAdd(hit_count_img, texel, 1u); + if (hit_id < VOLUME_HIT_DEPTH_MAX) { + float value = gl_FrontFacing ? volume_z : -volume_z; + imageStore(hit_depth_img, ivec3(texel, hit_id), vec4(value)); + } } -#endif } diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh index d276e31bf93..5742e70bbde 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_material_info.hh @@ -321,6 +321,7 @@ GPU_SHADER_CREATE_INFO(eevee_volume_world) GPU_SHADER_CREATE_INFO(eevee_surf_occupancy) .define("MAT_OCCUPANCY") .builtins(BuiltinBits::TEXTURE_ATOMIC) + .push_constant(Type::BOOL, "use_fast_method") .image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_3D, "hit_depth_img") .image(VOLUME_HIT_COUNT_SLOT, GPU_R32UI, diff --git a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh index c7303183635..13a79bc211f 100644 --- a/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh +++ b/source/blender/draw/engines/eevee_next/shaders/infos/eevee_volume_info.hh @@ -64,7 +64,11 @@ GPU_SHADER_CREATE_INFO(eevee_volume_occupancy_convert) .additional_info("eevee_shared", "eevee_global_ubo", "draw_fullscreen") .builtins(BuiltinBits::TEXTURE_ATOMIC) .image(VOLUME_HIT_DEPTH_SLOT, GPU_R32F, Qualifier::READ, ImageType::FLOAT_3D, "hit_depth_img") - .image(VOLUME_HIT_COUNT_SLOT, GPU_R32UI, Qualifier::READ, ImageType::UINT_2D, "hit_count_img") + .image(VOLUME_HIT_COUNT_SLOT, + GPU_R32UI, + Qualifier::READ_WRITE, + ImageType::UINT_2D, + "hit_count_img") .image(VOLUME_OCCUPANCY_SLOT, GPU_R32UI, Qualifier::READ_WRITE, diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h index 0c9dc3d8e2a..acf9ec28536 100644 --- a/source/blender/makesdna/DNA_material_types.h +++ b/source/blender/makesdna/DNA_material_types.h @@ -215,7 +215,9 @@ typedef struct Material { char blend_method; char blend_shadow; char blend_flag; - char _pad3[1]; + + /* Volume. */ + char volume_intersection_method; /** * Cached slots for texture painting, must be refreshed in @@ -326,6 +328,12 @@ enum { MA_PREVIEW_WORLD = 1 << 0, }; +/** #Material::volume_intersection_method */ +enum { + MA_VOLUME_ISECT_FAST = 0, + MA_VOLUME_ISECT_ACCURATE = 1, +}; + /** #Material::blend_method */ enum { MA_BM_SOLID = 0, diff --git a/source/blender/makesrna/intern/rna_material.cc b/source/blender/makesrna/intern/rna_material.cc index 6de9f3fc226..f0571e517c1 100644 --- a/source/blender/makesrna/intern/rna_material.cc +++ b/source/blender/makesrna/intern/rna_material.cc @@ -781,6 +781,22 @@ void RNA_def_material(BlenderRNA *brna) {0, nullptr, 0, nullptr, nullptr}, }; + static EnumPropertyItem prop_eevee_volume_isect_method_items[] = { + {MA_VOLUME_ISECT_FAST, + "FAST", + 0, + "Fast", + "Each face is considered as a medium interface. Gives correct results for manifold " + "geometry that contains no inner parts"}, + {MA_VOLUME_ISECT_ACCURATE, + "ACCURATE", + 0, + "Accurate", + "Faces are considered as medium interface only when they have different consecutive " + "facing. Gives correct results as long as the max ray depth is not exceeded"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + static EnumPropertyItem prop_eevee_blend_items[] = { {MA_BM_SOLID, "OPAQUE", 0, "Opaque", "Render surface without transparency"}, {MA_BM_CLIP, @@ -885,6 +901,14 @@ void RNA_def_material(BlenderRNA *brna) "events (0 is disabled)"); RNA_def_property_update(prop, 0, "rna_Material_draw_update"); + prop = RNA_def_property(srna, "volume_intersection_method", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, prop_eevee_volume_isect_method_items); + RNA_def_property_ui_text( + prop, + "Volume Intersection Method", + "Determines which inner part of the mesh will produce volumetric effect"); + RNA_def_property_update(prop, 0, "rna_Material_draw_update"); + /* For Preview Render */ prop = RNA_def_property(srna, "preview_render_type", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "pr_type"); -- 2.30.2 From 4e5a12273a967581889758b773b890649ea76146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 18 Oct 2023 21:06:30 +0200 Subject: [PATCH 17/23] Allocate hit list only if needed --- .../draw/engines/eevee_next/eevee_volume.cc | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index 76d8d119daa..17a17d5abd5 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -132,13 +132,22 @@ void VolumeModule::end_sync() GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; occupancy_tx_.ensure_3d(GPU_R32UI, int3(data_.tex_size.xy(), occupancy_layers), occupancy_usage); - int max_ray_depth = 16; - eGPUTextureUsage hit_count_usage = GPU_TEXTURE_USAGE_SHADER_READ | - GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; - eGPUTextureUsage hit_depth_usage = GPU_TEXTURE_USAGE_SHADER_READ | - GPU_TEXTURE_USAGE_SHADER_WRITE; - hit_count_tx_.ensure_2d(GPU_R32UI, data_.tex_size.xy(), hit_count_usage); - hit_depth_tx_.ensure_3d(GPU_R32F, int3(data_.tex_size.xy(), max_ray_depth), hit_depth_usage); + { + eGPUTextureUsage hit_count_usage = GPU_TEXTURE_USAGE_SHADER_READ | + GPU_TEXTURE_USAGE_SHADER_WRITE | GPU_TEXTURE_USAGE_ATOMIC; + eGPUTextureUsage hit_depth_usage = GPU_TEXTURE_USAGE_SHADER_READ | + GPU_TEXTURE_USAGE_SHADER_WRITE; + int2 hit_list_size = int2(1); + int hit_list_layer = 1; + if (inst_.pipelines.volume.use_hit_list()) { + hit_list_layer = 16; /* TODO(fclem): Render option. */ + hit_list_size = data_.tex_size.xy(); + } + hit_depth_tx_.ensure_3d(GPU_R32F, int3(hit_list_size, hit_list_layer), hit_depth_usage); + if (hit_count_tx_.ensure_2d(GPU_R32UI, hit_list_size, hit_count_usage)) { + hit_count_tx_.clear(uint4(0)); + } + } if (GPU_backend_get_type() == GPU_BACKEND_METAL) { /* Metal requires a dummy attachment. */ -- 2.30.2 From 6122e4b2c4a9c8881052c919f53c0ea065551dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 18 Oct 2023 21:26:03 +0200 Subject: [PATCH 18/23] Add volume ray depth as render setting --- scripts/startup/bl_ui/properties_render.py | 1 + source/blender/blenloader/intern/versioning_400.cc | 7 +++++++ source/blender/draw/engines/eevee_next/eevee_pipeline.cc | 2 +- source/blender/draw/engines/eevee_next/eevee_pipeline.hh | 2 +- source/blender/draw/engines/eevee_next/eevee_volume.cc | 6 +++--- source/blender/makesdna/DNA_scene_defaults.h | 1 + source/blender/makesdna/DNA_scene_types.h | 2 +- source/blender/makesrna/intern/rna_scene.cc | 9 +++++++++ 8 files changed, 24 insertions(+), 6 deletions(-) diff --git a/scripts/startup/bl_ui/properties_render.py b/scripts/startup/bl_ui/properties_render.py index c1c5d957820..18c72c9fc46 100644 --- a/scripts/startup/bl_ui/properties_render.py +++ b/scripts/startup/bl_ui/properties_render.py @@ -461,6 +461,7 @@ class RENDER_PT_eevee_next_volumes(RenderButtonsPanel, Panel): col.prop(props, "volumetric_tile_size") col.prop(props, "volumetric_samples") col.prop(props, "volumetric_sample_distribution", text="Distribution") + col.prop(props, "volumetric_ray_depth", text="Max Depth") class RENDER_PT_eevee_next_volumes_lighting(RenderButtonsPanel, Panel): diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index a44f9019703..b5af42d0205 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -1728,5 +1728,12 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) */ { /* Keep this block, even when empty. */ + + if (!DNA_struct_member_exists(fd->filesdna, "SceneEEVEE", "int", "volumetric_ray_depth")) { + SceneEEVEE default_eevee = *DNA_struct_default_get(SceneEEVEE); + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + scene->eevee.volumetric_ray_depth = default_eevee.volumetric_ray_depth; + } + } } } diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 56723124986..5ba828bf773 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -844,7 +844,7 @@ void VolumePipeline::sync() } } -void VolumePipeline::render(View &view, Texture &occupancy_tx, Texture &hit_count_tx) +void VolumePipeline::render(View &view, Texture &occupancy_tx) { BLI_assert_msg(enabled_, "Trying to run the volume object pipeline with no actual volume calls"); diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index 8ada8d12cdc..ed4cff08ba6 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -361,7 +361,7 @@ class VolumePipeline { VolumePipeline(Instance &inst) : inst_(inst){}; void sync(); - void render(View &view, Texture &occupancy_tx, Texture &hit_count_tx); + void render(View &view, Texture &occupancy_tx); /** * Returns correct volume layer for a given object and add the object to the layer. diff --git a/source/blender/draw/engines/eevee_next/eevee_volume.cc b/source/blender/draw/engines/eevee_next/eevee_volume.cc index 17a17d5abd5..e9e2c566561 100644 --- a/source/blender/draw/engines/eevee_next/eevee_volume.cc +++ b/source/blender/draw/engines/eevee_next/eevee_volume.cc @@ -140,12 +140,12 @@ void VolumeModule::end_sync() int2 hit_list_size = int2(1); int hit_list_layer = 1; if (inst_.pipelines.volume.use_hit_list()) { - hit_list_layer = 16; /* TODO(fclem): Render option. */ + hit_list_layer = clamp_i(inst_.scene->eevee.volumetric_ray_depth, 1, 16); hit_list_size = data_.tex_size.xy(); } hit_depth_tx_.ensure_3d(GPU_R32F, int3(hit_list_size, hit_list_layer), hit_depth_usage); if (hit_count_tx_.ensure_2d(GPU_R32UI, hit_list_size, hit_count_usage)) { - hit_count_tx_.clear(uint4(0)); + hit_count_tx_.clear(uint4(0u)); } } @@ -243,7 +243,7 @@ void VolumeModule::draw_prepass(View &view) if (inst_.pipelines.volume.is_enabled()) { occupancy_fb_.bind(); - inst_.pipelines.volume.render(volume_view, occupancy_tx_, hit_count_tx_); + inst_.pipelines.volume.render(volume_view, occupancy_tx_); } DRW_stats_group_end(); } diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index f411f40add5..3e8d53b5f66 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -202,6 +202,7 @@ .volumetric_tile_size = 8, \ .volumetric_samples = 64, \ .volumetric_sample_distribution = 0.8f, \ + .volumetric_ray_depth = 16, \ .volumetric_light_clamp = 0.0f, \ .volumetric_shadow_samples = 16, \ \ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index ded4ddc5caf..922220eb069 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1855,6 +1855,7 @@ typedef struct SceneEEVEE { float volumetric_sample_distribution; float volumetric_light_clamp; int volumetric_shadow_samples; + int volumetric_ray_depth; float gtao_distance; float gtao_factor; @@ -1887,7 +1888,6 @@ typedef struct SceneEEVEE { int shadow_ray_count; int shadow_step_count; float shadow_normal_bias; - char _pad[4]; int ray_split_settings; int ray_tracing_method; diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index d200c822516..b5836e1e003 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -7785,6 +7785,15 @@ static void rna_def_scene_eevee(BlenderRNA *brna) RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); + prop = RNA_def_property(srna, "volumetric_ray_depth", PROP_INT, PROP_NONE); + RNA_def_property_ui_text(prop, + "Volume Max Ray Depth", + "Maximum surface intersection count used by the accurate volume " + "intersection method. Will create artifact if it is exceeded"); + RNA_def_property_range(prop, 1, 16); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); + prop = RNA_def_property(srna, "use_volumetric_lights", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flag", SCE_EEVEE_VOLUMETRIC_LIGHTS); RNA_def_property_ui_text( -- 2.30.2 From 198ff948e00812d5871c66eb5d5d3f5b657c68a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 18 Oct 2023 21:47:26 +0200 Subject: [PATCH 19/23] Use correct dereference for unique_ptr --- .../blender/draw/engines/eevee_next/eevee_pipeline.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 5ba828bf773..8d519421120 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -840,7 +840,7 @@ void VolumePipeline::sync() { enabled_ = false; for (auto &layer : layers_) { - (*layer).sync(); + layer->sync(); } } @@ -852,7 +852,7 @@ void VolumePipeline::render(View &view, Texture &occupancy_tx) /* TODO(fclem): We might want to skip empty layers as the clear overhead is be significant. */ /* TODO(fclem): Move this clear inside the render pass. */ occupancy_tx.clear(uint4(0u)); - (*layer).render(view); + layer->render(view); } } @@ -909,8 +909,8 @@ VolumeLayer *VolumePipeline::register_and_get_layer(Object *ob) } /* Do linear search in all layers in order. This can be optimized. */ for (auto &layer : layers_) { - if (!(*layer).bounds_overlaps(object_aabb)) { - (*layer).add_object_bound(object_aabb); + if (!layer->bounds_overlaps(object_aabb)) { + layer->add_object_bound(object_aabb); return layer.get(); } } @@ -954,7 +954,7 @@ void VolumePipeline::material_call(MaterialPass &volume_material_pass, bool VolumePipeline::use_hit_list() const { for (auto &layer : layers_) { - if ((*layer).use_hit_list) { + if (layer->use_hit_list) { return true; } } -- 2.30.2 From 78a5911afd76e98793da2b462cf9a5444b277126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Wed, 18 Oct 2023 21:52:01 +0200 Subject: [PATCH 20/23] Address review --- .../draw/engines/eevee_next/eevee_material.cc | 23 +++++++++--------- .../draw/engines/eevee_next/eevee_material.hh | 2 +- .../draw/engines/eevee_next/eevee_pipeline.cc | 4 ++-- .../draw/engines/eevee_next/eevee_pipeline.hh | 4 ++-- .../draw/engines/eevee_next/eevee_shader.cc | 1 - .../shaders/eevee_surf_occupancy_frag.glsl | 24 +++++++++++++++++++ 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_material.cc b/source/blender/draw/engines/eevee_next/eevee_material.cc index 110832e987d..fe68afb197e 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.cc +++ b/source/blender/draw/engines/eevee_next/eevee_material.cc @@ -292,6 +292,7 @@ Material &MaterialModule::material_sync(Object *ob, mat.planar_probe_shading = MaterialPass(); mat.volume_occupancy = MaterialPass(); mat.volume_material = MaterialPass(); + mat.is_volume = false; } else { /* Order is important for transparent. */ @@ -315,18 +316,18 @@ Material &MaterialModule::material_sync(Object *ob, mat.planar_probe_shading = material_pass_get( ob, blender_mat, MAT_PIPE_DEFERRED, geometry_type, MAT_PROBE_PLANAR); } - } - mat.is_volume = GPU_material_has_volume_output(mat.shading.gpumat); - if (mat.is_volume) { - mat.volume_occupancy = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, geometry_type); - mat.volume_material = material_pass_get( - ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT); - } - else { - mat.volume_occupancy = MaterialPass(); - mat.volume_material = MaterialPass(); + mat.is_volume = GPU_material_has_volume_output(mat.shading.gpumat); + if (mat.is_volume) { + mat.volume_occupancy = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_OCCUPANCY, geometry_type); + mat.volume_material = material_pass_get( + ob, blender_mat, MAT_PIPE_VOLUME_MATERIAL, MAT_GEOM_VOLUME_OBJECT); + } + else { + mat.volume_occupancy = MaterialPass(); + mat.volume_material = MaterialPass(); + } } if (blender_mat->blend_shadow == MA_BS_NONE) { diff --git a/source/blender/draw/engines/eevee_next/eevee_material.hh b/source/blender/draw/engines/eevee_next/eevee_material.hh index 8546d41e95d..ce948d5fb2a 100644 --- a/source/blender/draw/engines/eevee_next/eevee_material.hh +++ b/source/blender/draw/engines/eevee_next/eevee_material.hh @@ -47,7 +47,7 @@ enum eMaterialGeometry { MAT_GEOM_GPENCIL, MAT_GEOM_VOLUME, - /* These maps to special vertex shader. */ + /* These maps to special shader. */ MAT_GEOM_VOLUME_OBJECT, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_WORLD, diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 8d519421120..04841faf0b2 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -903,7 +903,7 @@ VolumeLayer *VolumePipeline::register_and_get_layer(Object *ob) { GridAABB object_aabb = grid_aabb_from_object(ob); GridAABB view_aabb = grid_aabb_from_view(); - if (object_aabb.intersect(view_aabb).is_empty()) { + if (object_aabb.intersection(view_aabb).is_empty()) { /* Skip invisible object with respect to raster grid and bounds density. */ return nullptr; } @@ -938,7 +938,7 @@ void VolumePipeline::material_call(MaterialPass &volume_material_pass, /* Possible double work here. Should be relatively insignificant in practice. */ GridAABB object_aabb = grid_aabb_from_object(ob); GridAABB view_aabb = grid_aabb_from_view(); - GridAABB visible_aabb = object_aabb.intersect(view_aabb); + GridAABB visible_aabb = object_aabb.intersection(view_aabb); /* Invisible volumes should already have been clipped. */ BLI_assert(visible_aabb.is_empty() == false); /* TODO(fclem): Use graphic pipeline instead of compute so we can leverage GPU culling, diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index ed4cff08ba6..bb069b8d33e 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -280,7 +280,7 @@ struct GridAABB { GridAABB(int3 min_, int3 max_) : min(min_), max(max_){}; /** Returns the intersection between this AABB and the \a other AABB. */ - GridAABB intersect(const GridAABB &other) const + GridAABB intersection(const GridAABB &other) const { return {math::max(this->min, other.min), math::min(this->max, other.max)}; } @@ -332,7 +332,7 @@ class VolumeLayer { bool bounds_overlaps(const GridAABB &object_aabb) const { for (const GridAABB &other_aabb : object_bounds_) { - if (object_aabb.intersect(other_aabb).is_empty() == false) { + if (object_aabb.intersection(other_aabb).is_empty() == false) { return true; } } diff --git a/source/blender/draw/engines/eevee_next/eevee_shader.cc b/source/blender/draw/engines/eevee_next/eevee_shader.cc index 30a5c4a5be3..00b83fc2911 100644 --- a/source/blender/draw/engines/eevee_next/eevee_shader.cc +++ b/source/blender/draw/engines/eevee_next/eevee_shader.cc @@ -443,7 +443,6 @@ void ShaderModule::material_create_info_ammend(GPUMaterial *gpumat, GPUCodegenOu MAT_GEOM_WORLD, MAT_GEOM_VOLUME_WORLD, MAT_GEOM_VOLUME_OBJECT, - MAT_GEOM_VOLUME, MAT_GEOM_VOLUME); if (!do_vertex_attrib_load && !info.vertex_out_interfaces_.is_empty()) { diff --git a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl index 6c5e453caf8..4dc0a6b7d60 100644 --- a/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl +++ b/source/blender/draw/engines/eevee_next/shaders/eevee_surf_occupancy_frag.glsl @@ -4,6 +4,30 @@ /** * Prepass that voxelizes an object on frustum aligned voxels. + * + * There is two method available: + * + * - Fast method: For each fragment we compute the amount of + * froxels center in-front of it. We then convert that + * into occupancy bitmask that we apply to the occupancy + * texture using imageAtomicXor. This flips the bit for each + * surfaces encountered along the camera ray. + * This is straight forward and works well for any manifold + * geometry. + * + * - Accurate method: + * For each fragment we write the fragment depth + * in a list (contained in one array texture). This list + * is then processed by a fullscreen pass (see + * eevee_occupancy_convert_frag.glsl) that sorts and + * converts all the hits to the occupancy bits. This + * emulate Cycles behavior by considering only back-face + * hits as exit events and front-face hits as entry events. + * The result stores it to the occupancy texture using + * bit-wise OR operation to compose it with other non-hit + * list objects. This also decouple the hit-list evaluation + * complexity from the material evaluation shader. + * */ #pragma BLENDER_REQUIRE(eevee_sampling_lib.glsl) -- 2.30.2 From 4715a9d5a03c34189635f042c04d69bb7ab13634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 19 Oct 2023 16:59:42 +0200 Subject: [PATCH 21/23] Fix shadow tagging not being triggered --- source/blender/draw/engines/eevee_next/eevee_instance.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_instance.cc b/source/blender/draw/engines/eevee_next/eevee_instance.cc index 862aef34f25..df7a057be55 100644 --- a/source/blender/draw/engines/eevee_next/eevee_instance.cc +++ b/source/blender/draw/engines/eevee_next/eevee_instance.cc @@ -273,7 +273,8 @@ void Instance::object_sync_render(void *instance_, void Instance::end_sync() { velocity.end_sync(); - shadows.end_sync(); /** \note: Needs to be before lights. */ + volume.end_sync(); /* Needs to be before shadows. */ + shadows.end_sync(); /* Needs to be before lights. */ lights.end_sync(); sampling.end_sync(); subsurface.end_sync(); @@ -283,7 +284,6 @@ void Instance::end_sync() light_probes.end_sync(); reflection_probes.end_sync(); planar_probes.end_sync(); - volume.end_sync(); global_ubo_.push_update(); } -- 2.30.2 From 8a082e0465cf1608dc3af63f64e22948ea94d9c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 19 Oct 2023 17:48:53 +0200 Subject: [PATCH 22/23] Avoid empty layers submitting commands. --- .../draw/engines/eevee_next/eevee_pipeline.cc | 33 +++++++++++-------- .../draw/engines/eevee_next/eevee_pipeline.hh | 4 ++- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc index 5d823156414..37d02e679db 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.cc +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc @@ -766,6 +766,8 @@ void VolumeLayer::sync() { object_bounds_.clear(); use_hit_list = false; + is_empty = true; + finalized = false; draw::PassMain &layer_pass = volume_layer_ps_; layer_pass.init(); @@ -778,15 +780,6 @@ void VolumeLayer::sync() inst_.sampling.bind_resources(pass); occupancy_ps_ = &pass; } - { - /* TODO(fclem): Optimize out when not needed. */ - PassMain::Sub &pass = layer_pass.sub("resolve_hits_ps"); - pass.state_set(DRW_STATE_WRITE_DEPTH); - inst_.bind_uniform_data(&pass); - pass.shader_set(inst_.shaders.static_shader_get(VOLUME_OCCUPANCY_CONVERT)); - pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); - pass.draw_procedural(GPU_PRIM_TRIS, 1, 3); - } { PassMain::Sub &pass = layer_pass.sub("material_ps"); pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); @@ -807,6 +800,7 @@ PassMain::Sub *VolumeLayer::occupancy_add(const Object *ob, bool use_fast_occupancy = (ob->type == OB_VOLUME) || (blender_mat->volume_intersection_method == MA_VOLUME_ISECT_FAST); use_hit_list |= !use_fast_occupancy; + is_empty = false; PassMain::Sub *pass = &occupancy_ps_->sub(GPU_material_get_name(gpumat)); pass->material_set(*inst_.manager, gpumat); @@ -825,8 +819,22 @@ PassMain::Sub *VolumeLayer::material_add(const Object * /*ob*/, return pass; } -void VolumeLayer::render(View &view) +void VolumeLayer::render(View &view, Texture &occupancy_tx) { + if (is_empty) { + return; + } + if (finalized == false) { + finalized = true; + if (use_hit_list) { + /* Add resolve pass only when needed. Insert after occupancy, before material pass. */ + occupancy_ps_->shader_set(inst_.shaders.static_shader_get(VOLUME_OCCUPANCY_CONVERT)); + occupancy_ps_->barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); + occupancy_ps_->draw_procedural(GPU_PRIM_TRIS, 1, 3); + } + } + /* TODO(fclem): Move this clear inside the render pass. */ + occupancy_tx.clear(uint4(0u)); inst_.manager->submit(volume_layer_ps_, view); } @@ -849,10 +857,7 @@ void VolumePipeline::render(View &view, Texture &occupancy_tx) BLI_assert_msg(enabled_, "Trying to run the volume object pipeline with no actual volume calls"); for (auto &layer : layers_) { - /* TODO(fclem): We might want to skip empty layers as the clear overhead is be significant. */ - /* TODO(fclem): Move this clear inside the render pass. */ - occupancy_tx.clear(uint4(0u)); - layer->render(view); + layer->render(view, occupancy_tx); } } diff --git a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh index bb069b8d33e..b0e384a9f0f 100644 --- a/source/blender/draw/engines/eevee_next/eevee_pipeline.hh +++ b/source/blender/draw/engines/eevee_next/eevee_pipeline.hh @@ -304,6 +304,8 @@ struct GridAABB { class VolumeLayer { public: bool use_hit_list = false; + bool is_empty = true; + bool finalized = false; private: Instance &inst_; @@ -345,7 +347,7 @@ class VolumeLayer { } void sync(); - void render(View &view); + void render(View &view, Texture &occupancy_tx); }; class VolumePipeline { -- 2.30.2 From 9396a5a49fac62a65fa441553113253b804ac1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Foucault?= Date: Thu, 19 Oct 2023 18:45:54 +0200 Subject: [PATCH 23/23] Move occupancy geometry call to avoid confusion --- source/blender/draw/engines/eevee_next/eevee_sync.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/blender/draw/engines/eevee_next/eevee_sync.cc b/source/blender/draw/engines/eevee_next/eevee_sync.cc index fdf5b3ccf04..6aaadaa16ac 100644 --- a/source/blender/draw/engines/eevee_next/eevee_sync.cc +++ b/source/blender/draw/engines/eevee_next/eevee_sync.cc @@ -155,10 +155,9 @@ void SyncModule::sync_mesh(Object *ob, Material &material = material_array.materials[i]; GPUMaterial *gpu_material = material_array.gpu_materials[i]; - geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); - if (material.is_volume && (i == 0)) { /* Only support single volume material for now. */ + geometry_call(material.volume_occupancy.sub_pass, geom, res_handle); inst_.pipelines.volume.material_call(material.volume_material, ob, res_handle); /* Do not render surface if we are rendering a volume object * and do not have a surface closure. */ -- 2.30.2