Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,996 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
# WebEngine is a data/modeling module. Rendering is implemented by the local
# Three.js adapter, so expose Blender GPU headers but do not compile a second
# renderer into WebAssembly.
if(WITH_WEB)
add_library(bf_gpu INTERFACE)
target_include_directories(bf_gpu INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/intern
)
add_library(bf::gpu ALIAS bf_gpu)
return()
endif()
set(INC
PUBLIC .
dummy
intern
metal
opengl
vulkan
../makesrna
# For theme color access.
../editors/include
# For `*_info.hh` includes.
../compositor/shaders/infos
../draw/engines/eevee
../draw/engines/eevee/shaders/infos
../draw/engines/gpencil
../draw/engines/gpencil/shaders/infos
../draw/engines/image/shaders/infos
../draw/engines/overlay/shaders/infos
../draw/engines/select
../draw/engines/select/shaders/infos
../draw/engines/workbench
../draw/engines/workbench/shaders/infos
../draw/intern
../draw/intern/shaders
metal/kernels
vulkan/shaders
shaders/infos
# For shader includes
shaders/common
shaders
../../../intern/ghost
../../../intern/mantaflow/extern
../../../intern/opensubdiv
)
if(WITH_BUILDINFO)
add_definitions(-DWITH_BUILDINFO)
endif()
if(WITH_RENDERDOC)
list(APPEND INC
../../../extern/renderdoc/include
../../../intern/renderdoc_dynload/include
)
add_definitions(-DWITH_RENDERDOC)
endif()
if(WITH_GPU_SHADER_ASSERT)
add_definitions(-DWITH_GPU_SHADER_ASSERT)
endif()
if(WITH_XR_OPENXR)
add_definitions(-DWITH_XR_OPENXR)
endif()
set(INC_SYS
)
set(SRC
intern/gpu_batch.cc
intern/gpu_batch_presets.cc
intern/gpu_batch_utils.cc
intern/gpu_capabilities.cc
intern/gpu_codegen.cc
intern/gpu_compute.cc
intern/gpu_context.cc
intern/gpu_debug.cc
intern/gpu_framebuffer.cc
intern/gpu_immediate.cc
intern/gpu_immediate_util.cc
intern/gpu_index_buffer.cc
intern/gpu_init_exit.cc
intern/gpu_material.cc
intern/gpu_matrix.cc
intern/gpu_node_graph.cc
intern/gpu_pass.cc
intern/gpu_platform.cc
intern/gpu_query.cc
intern/gpu_select.cc
intern/gpu_select_next.cc
intern/gpu_select_pick.cc
intern/gpu_select_sample_query.cc
intern/gpu_shader.cc
intern/gpu_shader_builtin.cc
intern/gpu_shader_create_info.cc
intern/gpu_shader_dependency.cc
intern/gpu_shader_interface.cc
intern/gpu_shader_log.cc
intern/gpu_shader_preprocessor.cc
intern/gpu_state.cc
intern/gpu_storage_buffer.cc
intern/gpu_texture.cc
intern/gpu_texture_mipmap.cc
intern/gpu_texture_pool.cc
intern/gpu_uniform_buffer.cc
intern/gpu_vertex_buffer.cc
intern/gpu_vertex_format.cc
intern/gpu_vertex_format_normals.cc
intern/gpu_viewport.cc
intern/gpu_worker.cc
shader_tool/attribute.cc
shader_tool/enum.cc
shader_tool/flow_control.cc
shader_tool/function.cc
shader_tool/grammar.cc
shader_tool/intermediate.cc
shader_tool/metadata.cc
shader_tool/namespace.cc
shader_tool/processor.cc
shader_tool/resource_table.cc
shader_tool/string.cc
shader_tool/struct.cc
shader_tool/template.cc
shader_tool/union.cc
shader_tool/lexit/lexit.cc
GPU_attribute_convert.hh
GPU_batch.hh
GPU_batch_presets.hh
GPU_batch_utils.hh
GPU_capabilities.hh
GPU_common.hh
GPU_common_types.hh
GPU_compilation_subprocess.hh
GPU_compute.hh
GPU_context.hh
GPU_debug.hh
GPU_format.hh
GPU_framebuffer.hh
GPU_immediate.hh
GPU_immediate_util.hh
GPU_index_buffer.hh
GPU_init_exit.hh
GPU_material.hh
GPU_matrix.hh
GPU_pass.hh
GPU_platform.hh
GPU_platform_backend_enum.h
GPU_primitive.hh
GPU_select.hh
GPU_shader.hh
GPU_shader_builtin.hh
GPU_shader_shared.hh
GPU_state.hh
GPU_storage_buffer.hh
GPU_texture.hh
GPU_texture_pool.hh
GPU_uniform_buffer.hh
GPU_vertex_buffer.hh
GPU_vertex_format.hh
GPU_viewport.hh
GPU_worker.hh
intern/gpu_backend.hh
intern/gpu_capabilities_private.hh
intern/gpu_codegen.hh
intern/gpu_context_private.hh
intern/gpu_debug_private.hh
intern/gpu_framebuffer_private.hh
intern/gpu_immediate_private.hh
intern/gpu_material_library.hh
intern/gpu_matrix_private.hh
intern/gpu_node_graph.hh
intern/gpu_platform_private.hh
intern/gpu_private.hh
intern/gpu_profile_report.hh
intern/gpu_query.hh
intern/gpu_select_private.hh
intern/gpu_shader_create_info.hh
intern/gpu_shader_create_info_pipeline.hh
intern/gpu_shader_create_info_private.hh
intern/gpu_shader_dead_code_elimination.hh
intern/gpu_shader_dependency_private.hh
intern/gpu_shader_interface.hh
intern/gpu_shader_metadata_private.hh
intern/gpu_shader_private.hh
intern/gpu_state_private.hh
intern/gpu_storage_buffer_private.hh
intern/gpu_texture_pool_private.hh
intern/gpu_texture_private.hh
intern/gpu_uniform_buffer_private.hh
intern/gpu_vertex_format_private.hh
dummy/dummy_backend.hh
dummy/dummy_batch.hh
dummy/dummy_context.hh
dummy/dummy_framebuffer.hh
dummy/dummy_vertex_buffer.hh
)
set(OPENGL_SRC
opengl/gl_backend.cc
opengl/gl_batch.cc
opengl/gl_compilation_subprocess.cc
opengl/gl_compute.cc
opengl/gl_context.cc
opengl/gl_debug.cc
opengl/gl_framebuffer.cc
opengl/gl_immediate.cc
opengl/gl_index_buffer.cc
opengl/gl_query.cc
opengl/gl_shader.cc
opengl/gl_shader_interface.cc
opengl/gl_shader_log.cc
opengl/gl_state.cc
opengl/gl_storage_buffer.cc
opengl/gl_texture.cc
opengl/gl_texture_pool.cc
opengl/gl_uniform_buffer.cc
opengl/gl_vertex_array.cc
opengl/gl_vertex_buffer.cc
opengl/gl_backend.hh
opengl/gl_batch.hh
opengl/gl_compilation_subprocess.hh
opengl/gl_compute.hh
opengl/gl_context.hh
opengl/gl_debug.hh
opengl/gl_framebuffer.hh
opengl/gl_immediate.hh
opengl/gl_index_buffer.hh
opengl/gl_primitive.hh
opengl/gl_query.hh
opengl/gl_shader.hh
opengl/gl_shader_interface.hh
opengl/gl_state.hh
opengl/gl_storage_buffer.hh
opengl/gl_texture.hh
opengl/gl_texture_pool.hh
opengl/gl_uniform_buffer.hh
opengl/gl_vertex_array.hh
opengl/gl_vertex_buffer.hh
)
set(VULKAN_SRC
vulkan/vk_backend.cc
vulkan/vk_batch.cc
vulkan/vk_buffer.cc
vulkan/vk_buffer_pool.cc
vulkan/vk_common.cc
vulkan/vk_context.cc
vulkan/vk_data_conversion.cc
vulkan/vk_debug.cc
vulkan/vk_descriptor_pools.cc
vulkan/vk_descriptor_set.cc
vulkan/vk_descriptor_set_layouts.cc
vulkan/vk_device.cc
vulkan/vk_device_submission.cc
vulkan/vk_fence.cc
vulkan/vk_framebuffer.cc
vulkan/vk_image_view.cc
vulkan/vk_immediate.cc
vulkan/vk_index_buffer.cc
vulkan/vk_memory_layout.cc
vulkan/vk_memory_pool.cc
vulkan/vk_pipeline_pool.cc
vulkan/vk_pixel_buffer.cc
vulkan/vk_push_constants.cc
vulkan/vk_query.cc
vulkan/render_graph/nodes/vk_pipeline_data.cc
vulkan/render_graph/vk_command_buffer_wrapper.cc
vulkan/render_graph/vk_command_builder.cc
vulkan/render_graph/vk_render_graph.cc
vulkan/render_graph/vk_render_graph_links.cc
vulkan/render_graph/vk_resource_access_info.cc
vulkan/render_graph/vk_resource_state_tracker.cc
vulkan/render_graph/vk_scheduler.cc
vulkan/vk_resource_pool.cc
vulkan/vk_sampler.cc
vulkan/vk_samplers.cc
vulkan/vk_shader.cc
vulkan/vk_shader_compiler.cc
vulkan/vk_shader_interface.cc
vulkan/vk_shader_log.cc
vulkan/vk_shader_module.cc
vulkan/vk_staging_buffer.cc
vulkan/vk_state_manager.cc
vulkan/vk_storage_buffer.cc
vulkan/vk_streaming_buffer.cc
vulkan/vk_texture.cc
vulkan/vk_texture_pool.cc
vulkan/vk_to_string.cc
vulkan/vk_uniform_buffer.cc
vulkan/vk_vertex_attribute_object.cc
vulkan/vk_vertex_buffer.cc
vulkan/vk_backend.hh
vulkan/vk_batch.hh
vulkan/vk_buffer.hh
vulkan/vk_buffer_pool.hh
vulkan/vk_common.hh
vulkan/vk_context.hh
vulkan/vk_data_conversion.hh
vulkan/vk_debug.hh
vulkan/vk_descriptor_pools.hh
vulkan/vk_descriptor_set.hh
vulkan/vk_descriptor_set_layouts.hh
vulkan/vk_device.hh
vulkan/vk_fence.hh
vulkan/vk_framebuffer.hh
vulkan/vk_ghost_api.hh
vulkan/vk_graphics_pipeline.hh
vulkan/vk_image_view.hh
vulkan/vk_immediate.hh
vulkan/vk_index_buffer.hh
vulkan/vk_memory.hh
vulkan/vk_memory_layout.hh
vulkan/vk_memory_pool.hh
vulkan/vk_pipeline_pool.hh
vulkan/vk_pixel_buffer.hh
vulkan/vk_push_constants.hh
vulkan/vk_query.hh
vulkan/render_graph/nodes/vk_begin_query_node.hh
vulkan/render_graph/nodes/vk_begin_rendering_node.hh
vulkan/render_graph/nodes/vk_blit_image_node.hh
vulkan/render_graph/nodes/vk_clear_attachments_node.hh
vulkan/render_graph/nodes/vk_clear_color_image_node.hh
vulkan/render_graph/nodes/vk_clear_depth_stencil_image_node.hh
vulkan/render_graph/nodes/vk_copy_buffer_node.hh
vulkan/render_graph/nodes/vk_copy_buffer_to_image_node.hh
vulkan/render_graph/nodes/vk_copy_image_node.hh
vulkan/render_graph/nodes/vk_copy_image_to_buffer_node.hh
vulkan/render_graph/nodes/vk_dispatch_indirect_node.hh
vulkan/render_graph/nodes/vk_dispatch_node.hh
vulkan/render_graph/nodes/vk_draw_indexed_indirect_node.hh
vulkan/render_graph/nodes/vk_draw_indexed_node.hh
vulkan/render_graph/nodes/vk_draw_indirect_node.hh
vulkan/render_graph/nodes/vk_draw_node.hh
vulkan/render_graph/nodes/vk_end_query_node.hh
vulkan/render_graph/nodes/vk_end_rendering_node.hh
vulkan/render_graph/nodes/vk_fill_buffer_node.hh
vulkan/render_graph/nodes/vk_node_info.hh
vulkan/render_graph/nodes/vk_pipeline_data.hh
vulkan/render_graph/nodes/vk_reset_query_pool_node.hh
vulkan/render_graph/nodes/vk_synchronization_node.hh
vulkan/render_graph/nodes/vk_update_buffer_node.hh
vulkan/render_graph/nodes/vk_update_mipmaps_node.hh
vulkan/render_graph/vk_command_buffer_wrapper.hh
vulkan/render_graph/vk_command_builder.hh
vulkan/render_graph/vk_render_graph.hh
vulkan/render_graph/vk_render_graph_links.hh
vulkan/render_graph/vk_render_graph_node.hh
vulkan/render_graph/vk_resource_access_info.hh
vulkan/render_graph/vk_resource_state_tracker.hh
vulkan/render_graph/vk_scheduler.hh
vulkan/vk_resource_pool.hh
vulkan/vk_sampler.hh
vulkan/vk_samplers.hh
vulkan/vk_shader.hh
vulkan/vk_shader_compiler.hh
vulkan/vk_shader_interface.hh
vulkan/vk_shader_log.hh
vulkan/vk_shader_module.hh
vulkan/vk_staging_buffer.hh
vulkan/vk_state_manager.hh
vulkan/vk_storage_buffer.hh
vulkan/vk_streaming_buffer.hh
vulkan/vk_texture.hh
vulkan/vk_texture_pool.hh
vulkan/vk_to_string.hh
vulkan/vk_uniform_buffer.hh
vulkan/vk_vertex_attribute_object.hh
vulkan/vk_vertex_buffer.hh
vulkan/vk_vertex_input_description.hh
)
set(METAL_SRC
metal/mtl_backend.mm
metal/mtl_batch.mm
metal/mtl_command_buffer.mm
metal/mtl_context.mm
metal/mtl_debug.mm
metal/mtl_framebuffer.mm
metal/mtl_immediate.mm
metal/mtl_index_buffer.mm
metal/mtl_memory.mm
metal/mtl_push_constant.cc
metal/mtl_query.mm
metal/mtl_shader.mm
metal/mtl_shader_generate.cc
metal/mtl_shader_interface.mm
metal/mtl_shader_log.mm
metal/mtl_state.mm
metal/mtl_storage_buffer.mm
metal/mtl_texture.mm
metal/mtl_texture_pool.mm
metal/mtl_texture_util.mm
metal/mtl_uniform_buffer.mm
metal/mtl_vertex_buffer.mm
metal/mtl_backend.hh
metal/mtl_batch.hh
metal/mtl_capabilities.hh
metal/mtl_command_buffer.hh
metal/mtl_common.hh
metal/mtl_context.hh
metal/mtl_debug.hh
metal/mtl_framebuffer.hh
metal/mtl_immediate.hh
metal/mtl_index_buffer.hh
metal/mtl_memory.hh
metal/mtl_primitive.hh
metal/mtl_pso_descriptor_state.hh
metal/mtl_push_constant.hh
metal/mtl_query.hh
metal/mtl_shader.hh
metal/mtl_shader_generate.hh
metal/mtl_shader_interface.hh
metal/mtl_shader_interface_type.hh
metal/mtl_shader_log.hh
metal/mtl_shader_shared.hh
metal/mtl_state.hh
metal/mtl_storage_buffer.hh
metal/mtl_texture.hh
metal/mtl_texture_pool.hh
metal/mtl_uniform_buffer.hh
metal/mtl_vertex_buffer.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::bmesh
PRIVATE bf::dna
PRIVATE bf::draw
PRIVATE bf::imbuf
PRIVATE bf::intern::atomic
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::nodes
PRIVATE bf::dependencies::opencolorio
)
# Select Backend source based on availability
if(WITH_OPENGL_BACKEND)
list(APPEND SRC
${OPENGL_SRC}
)
list(APPEND LIB
PRIVATE bf::dependencies::epoxy
)
add_definitions(-DWITH_OPENGL_BACKEND)
endif()
if(WITH_METAL_BACKEND)
list(APPEND SRC ${METAL_SRC})
endif()
if(WITH_VULKAN_BACKEND)
list(APPEND SRC
${VULKAN_SRC}
)
list(APPEND LIB
PRIVATE bf::extern::vulkan_memory_allocator
PRIVATE bf::extern::xxhash
PRIVATE bf::dependencies::optional::vulkan
PRIVATE bf::dependencies::optional::shaderc
)
add_definitions(-DWITH_VULKAN_BACKEND)
endif()
set(GLSL_SRC
GPU_shader_shared.hh
shaders/infos/gpu_clip_planes_infos.hh
shaders/infos/gpu_index_load_infos.hh
shaders/infos/gpu_interface_infos.hh
shaders/infos/gpu_shader_2D_area_borders_infos.hh
shaders/infos/gpu_shader_2D_checker_infos.hh
shaders/infos/gpu_shader_2D_diag_stripes_infos.hh
shaders/infos/gpu_shader_2D_image_desaturate_color_infos.hh
shaders/infos/gpu_shader_2D_image_infos.hh
shaders/infos/gpu_shader_2D_image_overlays_merge_infos.hh
shaders/infos/gpu_shader_2D_image_overlays_stereo_merge_infos.hh
shaders/infos/gpu_shader_2D_image_rect_color_infos.hh
shaders/infos/gpu_shader_2D_image_shuffle_color_infos.hh
shaders/infos/gpu_shader_2D_node_socket_infos.hh
shaders/infos/gpu_shader_2D_point_uniform_size_uniform_color_aa_infos.hh
shaders/infos/gpu_shader_2D_point_uniform_size_uniform_color_outline_aa_infos.hh
shaders/infos/gpu_shader_2D_point_varying_size_varying_color_infos.hh
shaders/infos/gpu_shader_2D_widget_infos.hh
shaders/infos/gpu_shader_3D_depth_only_infos.hh
shaders/infos/gpu_shader_3D_flat_color_infos.hh
shaders/infos/gpu_shader_3D_image_infos.hh
shaders/infos/gpu_shader_3D_point_infos.hh
shaders/infos/gpu_shader_3D_polyline_infos.hh
shaders/infos/gpu_shader_3D_smooth_color_infos.hh
shaders/infos/gpu_shader_3D_uniform_color_infos.hh
shaders/infos/gpu_shader_fullscreen_infos.hh
shaders/infos/gpu_shader_gpencil_stroke_infos.hh
shaders/infos/gpu_shader_icon_infos.hh
shaders/infos/gpu_shader_index_infos.hh
shaders/infos/gpu_shader_keyframe_shape_infos.hh
shaders/infos/gpu_shader_line_dashed_uniform_color_infos.hh
shaders/infos/gpu_shader_print_infos.hh
shaders/infos/gpu_shader_sequencer_infos.hh
shaders/infos/gpu_shader_simple_lighting_infos.hh
shaders/infos/gpu_shader_text_infos.hh
shaders/infos/gpu_srgb_to_framebuffer_space_infos.hh
shaders/gpu_shader_depth_only_frag.glsl
shaders/gpu_shader_uniform_color_frag.glsl
shaders/gpu_shader_checker_frag.glsl
shaders/gpu_shader_diag_stripes_frag.glsl
shaders/gpu_shader_simple_lighting_frag.glsl
shaders/gpu_shader_flat_color_frag.glsl
shaders/gpu_shader_2D_vert.glsl
shaders/gpu_shader_2D_area_borders_vert.glsl
shaders/gpu_shader_2D_area_borders_frag.glsl
shaders/gpu_shader_2D_widget_base.bsl.hh
shaders/gpu_shader_2D_widget_shadow_vert.glsl
shaders/gpu_shader_2D_widget_shadow_frag.glsl
shaders/gpu_shader_2D_node_socket_frag.glsl
shaders/gpu_shader_2D_node_socket_vert.glsl
shaders/gpu_shader_2D_nodelink.bsl.hh
shaders/gpu_shader_2D_line_dashed_frag.glsl
shaders/gpu_shader_2D_image_vert.glsl
shaders/gpu_shader_2D_image_rect_vert.glsl
shaders/gpu_shader_icon_multi_vert.glsl
shaders/gpu_shader_icon_frag.glsl
shaders/gpu_shader_icon_vert.glsl
shaders/gpu_shader_image_frag.glsl
shaders/gpu_shader_image_desaturate_frag.glsl
shaders/gpu_shader_image_overlays_merge_frag.glsl
shaders/gpu_shader_image_overlays_stereo_merge_frag.glsl
shaders/gpu_shader_image_shuffle_color_frag.glsl
shaders/gpu_shader_image_color_frag.glsl
shaders/gpu_shader_3D_image_vert.glsl
shaders/gpu_shader_3D_vert.glsl
shaders/gpu_shader_3D_normal_vert.glsl
shaders/gpu_shader_3D_flat_color_vert.glsl
shaders/gpu_shader_3D_line_dashed_uniform_color_vert.glsl
shaders/gpu_shader_3D_polyline_frag.glsl
shaders/gpu_shader_3D_polyline_vert.glsl
shaders/gpu_shader_3D_smooth_color_vert.glsl
shaders/gpu_shader_3D_smooth_color_frag.glsl
shaders/gpu_shader_3D_clipped_uniform_color_vert.glsl
shaders/gpu_shader_2D_update_mipmaps.bsl.hh
shaders/gpu_shader_point_uniform_color_aa_frag.glsl
shaders/gpu_shader_point_uniform_color_outline_aa_frag.glsl
shaders/gpu_shader_point_varying_color_frag.glsl
shaders/gpu_shader_3D_point_varying_size_varying_color_vert.glsl
shaders/gpu_shader_3D_point_uniform_size_aa_vert.glsl
shaders/gpu_shader_3D_point_flat_color_vert.glsl
shaders/gpu_shader_2D_point_varying_size_varying_color_vert.glsl
shaders/gpu_shader_2D_point_uniform_size_aa_vert.glsl
shaders/gpu_shader_2D_point_uniform_size_outline_aa_vert.glsl
shaders/gpu_shader_text_vert.glsl
shaders/gpu_shader_text_frag.glsl
shaders/gpu_shader_keyframe_shape_vert.glsl
shaders/gpu_shader_keyframe_shape_frag.glsl
shaders/gpu_shader_sequencer_scope_comp.glsl
shaders/gpu_shader_sequencer_scope_frag.glsl
shaders/gpu_shader_sequencer_strips_vert.glsl
shaders/gpu_shader_sequencer_strips_frag.glsl
shaders/gpu_shader_sequencer_thumbs_vert.glsl
shaders/gpu_shader_sequencer_thumbs_frag.glsl
shaders/gpu_shader_sequencer_zebra_frag.glsl
shaders/gpu_shader_codegen_lib.glsl
shaders/gpu_shader_python_comp.glsl
shaders/gpu_shader_python_frag.glsl
shaders/gpu_shader_python_typedef_lib.glsl
shaders/gpu_shader_python_vert.glsl
shaders/common/gpu_shader_attribute_load_lib.glsl
shaders/common/gpu_shader_bicubic_sampler_lib.glsl
shaders/common/gpu_shader_common_color_ramp.glsl
shaders/common/gpu_shader_common_color_utils.glsl
shaders/common/gpu_shader_common_curves.glsl
shaders/common/gpu_shader_common_hash.glsl
shaders/common/gpu_shader_common_math_utils.glsl
shaders/common/gpu_shader_common_math.glsl
shaders/common/gpu_shader_common_mix_rgb.glsl
shaders/common/gpu_shader_debug_gradients_lib.glsl
shaders/common/gpu_shader_fullscreen_vert.glsl
shaders/common/gpu_shader_fullscreen_lib.glsl
shaders/common/gpu_shader_index_load_lib.glsl
shaders/common/gpu_shader_index_range_lib.glsl
shaders/common/gpu_shader_math_angle_lib.glsl
shaders/common/gpu_shader_math_axis_angle_lib.glsl
shaders/common/gpu_shader_math_base_lib.glsl
shaders/common/gpu_shader_math_constants_lib.glsl
shaders/common/gpu_shader_math_euler_lib.glsl
shaders/common/gpu_shader_math_fast_lib.glsl
shaders/common/gpu_shader_math_matrix_adjoint_lib.glsl
shaders/common/gpu_shader_math_matrix_compare_lib.glsl
shaders/common/gpu_shader_math_matrix_construct_lib.glsl
shaders/common/gpu_shader_math_matrix_conversion_lib.glsl
shaders/common/gpu_shader_math_matrix_interpolate_lib.glsl
shaders/common/gpu_shader_math_matrix_lib.glsl
shaders/common/gpu_shader_math_matrix_normalize_lib.glsl
shaders/common/gpu_shader_math_matrix_projection_lib.glsl
shaders/common/gpu_shader_math_matrix_transform_lib.glsl
shaders/common/gpu_shader_math_quaternion_lib.glsl
shaders/common/gpu_shader_math_rotation_conversion_lib.glsl
shaders/common/gpu_shader_math_rotation_lib.glsl
shaders/common/gpu_shader_math_safe_lib.glsl
shaders/common/gpu_shader_math_vector_compare_lib.glsl
shaders/common/gpu_shader_math_vector_lib.glsl
shaders/common/gpu_shader_math_vector_reduce_lib.glsl
shaders/common/gpu_shader_math_vector_safe_lib.glsl
shaders/common/gpu_shader_offset_indices_lib.glsl
shaders/common/gpu_shader_print_lib.glsl
shaders/common/gpu_shader_ray_utils_lib.glsl
shaders/common/gpu_shader_ray_lib.glsl
shaders/common/gpu_shader_sequencer_lib.glsl
shaders/common/gpu_shader_shared_exponent_lib.glsl
shaders/common/gpu_shader_smaa_lib.glsl
shaders/common/gpu_shader_test_lib.bsl.hh
shaders/common/gpu_shader_tiled_image_lookup_lib.glsl
shaders/common/gpu_shader_utildefines_lib.glsl
shaders/material/gpu_shader_material_add_shader.glsl
shaders/material/gpu_shader_material_align_rotation_to_vector.glsl
shaders/material/gpu_shader_material_axes_to_rotation.glsl
shaders/material/gpu_shader_material_ambient_occlusion.glsl
shaders/material/gpu_shader_material_attribute.glsl
shaders/material/gpu_shader_material_axis_angle_to_rotation.glsl
shaders/material/gpu_shader_material_background.glsl
shaders/material/gpu_shader_material_bevel.glsl
shaders/material/gpu_shader_material_wavelength.glsl
shaders/material/gpu_shader_material_blackbody.glsl
shaders/material/gpu_shader_material_bright_contrast.glsl
shaders/material/gpu_shader_material_bump.glsl
shaders/material/gpu_shader_material_camera.glsl
shaders/material/gpu_shader_material_clamp.glsl
shaders/material/gpu_shader_material_combine_color.glsl
shaders/material/gpu_shader_material_combine_xyz.glsl
shaders/material/gpu_shader_material_diffuse.glsl
shaders/material/gpu_shader_material_displacement.glsl
shaders/material/gpu_shader_material_eevee_specular.glsl
shaders/material/gpu_shader_material_emission.glsl
shaders/material/gpu_shader_material_euler_to_rotation.glsl
shaders/material/gpu_shader_material_fractal_noise.glsl
shaders/material/gpu_shader_material_fractal_voronoi.glsl
shaders/material/gpu_shader_material_fresnel.glsl
shaders/material/gpu_shader_material_gamma.glsl
shaders/material/gpu_shader_material_geometry.glsl
shaders/material/gpu_shader_material_glass.glsl
shaders/material/gpu_shader_material_glossy.glsl
shaders/material/gpu_shader_material_hair_info.glsl
shaders/material/gpu_shader_material_hair.glsl
shaders/material/gpu_shader_material_holdout.glsl
shaders/material/gpu_shader_material_hue_sat_val.glsl
shaders/material/gpu_shader_material_implicit_defaults.glsl
shaders/material/gpu_shader_material_invert.glsl
shaders/material/gpu_shader_material_invert_rotation.glsl
shaders/material/gpu_shader_material_layer_weight.glsl
shaders/material/gpu_shader_material_light_falloff.glsl
shaders/material/gpu_shader_material_light_path.glsl
shaders/material/gpu_shader_material_mapping.glsl
shaders/material/gpu_shader_material_map_range.glsl
shaders/material/gpu_shader_material_metallic.glsl
shaders/material/gpu_shader_material_mix_color.glsl
shaders/material/gpu_shader_material_mix_shader.glsl
shaders/material/gpu_shader_material_noise.glsl
shaders/material/gpu_shader_material_normal.glsl
shaders/material/gpu_shader_material_normal_map.glsl
shaders/material/gpu_shader_material_object_info.glsl
shaders/material/gpu_shader_material_output_aov.glsl
shaders/material/gpu_shader_material_output_material.glsl
shaders/material/gpu_shader_material_output_world.glsl
shaders/material/gpu_shader_material_particle_info.glsl
shaders/material/gpu_shader_material_point_info.glsl
shaders/material/gpu_shader_material_principled.glsl
shaders/material/gpu_shader_material_quaternion_to_rotation.glsl
shaders/material/gpu_shader_material_raycast.glsl
shaders/material/gpu_shader_material_ray_portal.glsl
shaders/material/gpu_shader_material_refraction.glsl
shaders/material/gpu_shader_material_repeat_zone.glsl
shaders/material/gpu_shader_material_rgb_to_bw.glsl
shaders/material/gpu_shader_material_radial_tiling.glsl
shaders/material/gpu_shader_material_radial_tiling_shared.glsl
shaders/material/gpu_shader_material_rotate_rotation.glsl
shaders/material/gpu_shader_material_rotate_vector.glsl
shaders/material/gpu_shader_material_rotation_to_axis_angle.glsl
shaders/material/gpu_shader_material_rotation_to_euler.glsl
shaders/material/gpu_shader_material_rotation_to_quaternion.glsl
shaders/material/gpu_shader_material_scene_time.glsl
shaders/material/gpu_shader_material_separate_color.glsl
shaders/material/gpu_shader_material_separate_xyz.glsl
shaders/material/gpu_shader_material_set.glsl
shaders/material/gpu_shader_material_shader_to_rgba.glsl
shaders/material/gpu_shader_material_sheen.glsl
shaders/material/gpu_shader_material_squeeze.glsl
shaders/material/gpu_shader_material_subsurface_scattering.glsl
shaders/material/gpu_shader_material_tangent.glsl
shaders/material/gpu_shader_material_tex_brick.glsl
shaders/material/gpu_shader_material_tex_checker.glsl
shaders/material/gpu_shader_material_tex_environment.glsl
shaders/material/gpu_shader_material_tex_gabor.glsl
shaders/material/gpu_shader_material_tex_gradient.glsl
shaders/material/gpu_shader_material_tex_image.glsl
shaders/material/gpu_shader_material_tex_magic.glsl
shaders/material/gpu_shader_material_tex_noise.glsl
shaders/material/gpu_shader_material_tex_sky.glsl
shaders/material/gpu_shader_material_texture_coordinates.glsl
shaders/material/gpu_shader_material_tex_voronoi.glsl
shaders/material/gpu_shader_material_tex_wave.glsl
shaders/material/gpu_shader_material_tex_white_noise.glsl
shaders/material/gpu_shader_material_toon.glsl
shaders/material/gpu_shader_material_transform_utils.glsl
shaders/material/gpu_shader_material_translucent.glsl
shaders/material/gpu_shader_material_transparent.glsl
shaders/material/gpu_shader_material_uv_map.glsl
shaders/material/gpu_shader_material_vector_displacement.glsl
shaders/material/gpu_shader_material_vector_math.glsl
shaders/material/gpu_shader_material_vector_rotate.glsl
shaders/material/gpu_shader_material_vertex_color.glsl
shaders/material/gpu_shader_material_volume_absorption.glsl
shaders/material/gpu_shader_material_volume_principled.glsl
shaders/material/gpu_shader_material_volume_scatter.glsl
shaders/material/gpu_shader_material_volume_coefficients.glsl
shaders/material/gpu_shader_material_voronoi.glsl
shaders/material/gpu_shader_material_wireframe.glsl
shaders/gpu_shader_gpencil_stroke_vert.glsl
shaders/gpu_shader_gpencil_stroke_frag.glsl
shaders/gpu_shader_display_fallback_vert.glsl
shaders/gpu_shader_display_fallback_frag.glsl
shaders/gpu_shader_cfg_world_clip_lib.glsl
shaders/gpu_shader_colorspace_lib.glsl
shaders/gpu_shader_index_2d_array_points.glsl
shaders/gpu_shader_index_2d_array_lines.glsl
shaders/gpu_shader_index_2d_array_tris.glsl
shaders/gpu_shader_compat_glsl.glsl
shaders/gpu_shader_glsl_extension.glsl
GPU_shader_shared_utils.hh
)
set(GLSL_TEST_SRC
shaders/infos/gpu_shader_test_infos.hh
tests/shaders/gpu_math_test.bsl.hh
tests/shaders/gpu_buffer_texture_test.glsl
tests/shaders/gpu_compute_1d_test.glsl
tests/shaders/gpu_compute_2d_test.glsl
tests/shaders/gpu_compute_ibo_test.glsl
tests/shaders/gpu_compute_ssbo_test.glsl
tests/shaders/gpu_compute_vbo_test.glsl
tests/shaders/gpu_compute_dummy_test.glsl
tests/shaders/gpu_specialization_test.glsl
tests/shaders/gpu_framebuffer_layer_viewport_test.glsl
tests/shaders/gpu_framebuffer_subpass_input_test.glsl
tests/shaders/gpu_push_constants_test.glsl
tests/shaders/gpu_texture_test.glsl
tests/shaders/gpu_texture_atomic_test.glsl
)
set(MTL_BACKEND_GLSL_SRC
metal/kernels/depth_2d_update_infos.hh
metal/kernels/gpu_shader_fullscreen_blit_infos.hh
metal/kernels/depth_2d_update_float_frag.glsl
metal/kernels/depth_2d_update_int24_frag.glsl
metal/kernels/depth_2d_update_int32_frag.glsl
metal/kernels/depth_2d_update_vert.glsl
metal/kernels/gpu_shader_fullscreen_blit_vert.glsl
metal/kernels/gpu_shader_fullscreen_blit_frag.glsl
shaders/gpu_shader_msl_atomic.msl
shaders/gpu_shader_msl_attribute.msl
shaders/gpu_shader_msl_builtin.msl
shaders/gpu_shader_msl_image.msl
shaders/gpu_shader_msl_matrix_legacy.msl
shaders/gpu_shader_msl_matrix.msl
shaders/gpu_shader_msl_sampler.msl
shaders/gpu_shader_msl_string.msl
shaders/gpu_shader_msl_types.msl
shaders/gpu_shader_msl_types_legacy.msl
shaders/gpu_shader_msl_defines.msl
shaders/gpu_shader_msl_wrapper.msl
shaders/gpu_shader_compat_msl.msl
)
set(MSL_SRC
metal/mtl_shader_shared.hh
metal/kernels/compute_texture_update.msl
metal/kernels/compute_texture_read.msl
)
set(VULKAN_BACKEND_GLSL_SRC
vulkan/shaders/vk_backbuffer_blit_infos.hh
vulkan/shaders/vk_backbuffer_blit_comp.glsl
)
if(WITH_GTESTS)
if(WITH_GPU_BACKEND_TESTS)
list(APPEND GLSL_SRC ${GLSL_TEST_SRC})
endif()
endif()
if(WITH_METAL_BACKEND)
list(APPEND GLSL_SRC ${MTL_BACKEND_GLSL_SRC})
set(MSL_C "")
foreach(MSL_FILE ${MSL_SRC})
data_to_c_simple(${MSL_FILE} MSL_C)
endforeach()
endif()
if(WITH_VULKAN_BACKEND)
list(APPEND GLSL_SRC ${VULKAN_BACKEND_GLSL_SRC})
endif()
set(GLSL_INC
.
intern
shaders
shaders/common
shaders/infos
shaders/material
vulkan/shaders
)
set(GLSL_C "")
foreach(GLSL_FILE ${GLSL_SRC})
glsl_to_c(${GLSL_FILE} GLSL_C GLSL_INC)
endforeach()
set(SHADER_C "")
list(APPEND SHADER_C ${GLSL_C})
if(WITH_METAL_BACKEND)
list(APPEND SHADER_C ${MSL_C})
endif()
blender_add_lib(bf_gpu_shaders "${SHADER_C}" "" "" "")
blender_set_target_unity_build(bf_gpu_shaders 10)
list(APPEND LIB
bf_gpu_shaders
)
set(GLSL_SOURCE_CONTENT "")
set(GLSL_METADATA_CONTENT "")
set(GLSL_INFOS_CONTENT "")
foreach(GLSL_FILE ${GLSL_SRC})
get_filename_component(GLSL_FILE_NAME ${GLSL_FILE} NAME)
string(REPLACE "." "_" GLSL_FILE_NAME_UNDERSCORES ${GLSL_FILE_NAME})
string(APPEND GLSL_FILE_NAME_UNDERSCORES "_tmp")
string(APPEND GLSL_SOURCE_CONTENT "SHADER_SOURCE\(${GLSL_FILE_NAME_UNDERSCORES}, \"${GLSL_FILE_NAME}\", \"${GLSL_FILE}\"\)\n")
string(APPEND GLSL_METADATA_CONTENT "#include \"${GLSL_FILE}.hh\"\n")
string(APPEND GLSL_INFOS_CONTENT "#include \"${GLSL_FILE}.info\"\n")
endforeach()
set(glsl_source_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_gpu_source_list.h")
file(GENERATE OUTPUT ${glsl_source_list_file} CONTENT "${GLSL_SOURCE_CONTENT}")
list(APPEND SRC ${glsl_source_list_file})
set(glsl_metadata_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_gpu_metadata_list.hh")
file(GENERATE OUTPUT ${glsl_metadata_list_file} CONTENT "${GLSL_METADATA_CONTENT}")
list(APPEND SRC ${glsl_metadata_list_file})
set(glsl_infos_list_file "${CMAKE_CURRENT_BINARY_DIR}/glsl_gpu_infos_list.hh")
file(GENERATE OUTPUT ${glsl_infos_list_file} CONTENT "${GLSL_INFOS_CONTENT}")
list(APPEND SRC ${glsl_infos_list_file})
list(APPEND INC ${CMAKE_CURRENT_BINARY_DIR})
if(WITH_MOD_FLUID)
add_definitions(-DWITH_FLUID)
endif()
if(WITH_OPENSUBDIV)
add_definitions(-DWITH_OPENSUBDIV)
endif()
if(WITH_GPU_BACKEND_TESTS)
add_definitions(-DWITH_GPU_BACKEND_TESTS)
endif()
if(WITH_GTESTS)
add_definitions(-DWITH_GTESTS)
endif()
blender_add_lib(bf_gpu "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_library(bf::gpu ALIAS bf_gpu)
target_link_libraries(bf_gpu PUBLIC
bf_compositor_shaders
bf_draw_shaders
bf_gpu_shaders
bf_imbuf_opencolorio_shaders
)
# If `execinfo.h` exists on a *BSD system then also link in `libexecinfo`.
# Needed for `backtrace` / `backtrace_symbols` (GNU extensions)
# brought in by `blenlib/intern/system.cc`.
if(HAVE_EXECINFO_H AND CMAKE_SYSTEM_NAME MATCHES "FreeBSD|NetBSD|OpenBSD|DragonFly")
target_link_libraries(bf_gpu PUBLIC execinfo)
endif()
if(WITH_OPENSUBDIV)
target_link_libraries(bf_gpu PUBLIC bf_osd_shaders)
endif()
if(WITH_RENDERDOC)
target_link_libraries(bf_gpu PUBLIC bf_intern_renderdoc_dynload)
endif()
if(CXX_WARN_NO_SUGGEST_OVERRIDE)
target_compile_options(bf_gpu PRIVATE $<$<COMPILE_LANGUAGE:CXX>:-Wsuggest-override>)
endif()
if(WITH_GTESTS)
set(TEST_SRC "")
set(TEST_INC "")
set(TEST_LIB
bf_intern_ghost
bf_imbuf
bf_windowmanager
)
if(WITH_GPU_BACKEND_TESTS)
list(APPEND TEST_SRC
tests/buffer_texture_test.cc
tests/compute_test.cc
tests/framebuffer_test.cc
tests/immediate_test.cc
tests/index_buffer_test.cc
tests/push_constants_test.cc
tests/shader_create_info_test.cc
tests/shader_preprocess_test.cc
tests/shader_test.cc
tests/specialization_constants_test.cc
tests/state_blend_test.cc
tests/storage_buffer_test.cc
tests/texture_test.cc
tests/texture_view_test.cc
tests/vertex_buffer_test.cc
)
endif()
if(WITH_VULKAN_BACKEND)
list(APPEND TEST_SRC
vulkan/tests/vk_data_conversion_test.cc
vulkan/tests/vk_memory_layout_test.cc
vulkan/render_graph/tests/vk_render_graph_test_compute.cc
vulkan/render_graph/tests/vk_render_graph_test_present.cc
vulkan/render_graph/tests/vk_render_graph_test_render.cc
vulkan/render_graph/tests/vk_render_graph_test_scheduler.cc
vulkan/render_graph/tests/vk_render_graph_test_transfer.cc
vulkan/render_graph/tests/vk_render_graph_test_types.hh
)
endif()
set(TEST_COMMON_SRC
tests/gpu_testing.cc
tests/gpu_testing.hh
)
blender_add_test_suite_lib(gpu
"${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}" "${TEST_COMMON_SRC}"
)
endif()

View File

@@ -0,0 +1,61 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <algorithm>
#include <limits>
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
namespace blender::gpu {
struct PackedNormal {
int x : 10;
int y : 10;
int z : 10;
int w : 2; /* 0 by default, can manually set to { -2, -1, 0, 1 } */
PackedNormal() = default;
PackedNormal(int _x, int _y, int _z, int _w = 0) : x(_x), y(_y), z(_z), w(_w) {}
/* Cast from int to float. */
operator float4()
{
return float4(x, y, z, w);
}
};
inline int convert_normalized_f32_to_i10(float x)
{
/* OpenGL ES packs in a different order as desktop GL but component conversion is the same.
* Of the code here, only PackedNormal needs to change. */
constexpr int signed_int_10_max = 511;
constexpr int signed_int_10_min = -512;
int qx = x * signed_int_10_max;
return std::clamp(qx, signed_int_10_min, signed_int_10_max);
}
template<typename GPUType> inline GPUType convert_normal(const float3 &src);
template<> inline PackedNormal convert_normal(const float3 &src)
{
return {
convert_normalized_f32_to_i10(src[0]),
convert_normalized_f32_to_i10(src[1]),
convert_normalized_f32_to_i10(src[2]),
0,
};
}
template<> inline short4 convert_normal(const float3 &src)
{
return short4(src * float(std::numeric_limits<short>::max()), 0);
}
template<typename GPUType> void convert_normals(Span<float3> src, MutableSpan<GPUType> dst);
} // namespace blender::gpu

View File

@@ -0,0 +1,439 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU geometry batch.
*
* Contains Vertex Buffers, Index Buffers, and Shader reference, altogether representing a drawable
* entity. It is meant to be used for drawing large (> 1000 vertices) reusable (drawn multiple
* times) model with complex data layout. In other words, it is meant for all cases where the
* immediate drawing module (imm) is inadequate.
*
* Vertex & Index Buffers can be owned by a batch. In such case they will be freed when the batch
* gets cleared or discarded.
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_index_range.hh"
#include "GPU_index_buffer.hh"
#include "GPU_shader.hh"
#include "GPU_storage_buffer.hh"
#include "GPU_vertex_buffer.hh"
namespace blender {
namespace gpu {
class Shader;
} // namespace gpu
constexpr static int GPU_BATCH_VBO_MAX_LEN = 16;
constexpr static int GPU_BATCH_VAO_STATIC_LEN = 3;
constexpr static int GPU_BATCH_VAO_DYN_ALLOC_COUNT = 16;
enum GPUBatchFlag {
/** Invalid default state. */
GPU_BATCH_INVALID = 0,
/** gpu::VertBuf ownership. (One bit per vbo) */
GPU_BATCH_OWNS_VBO = (1 << 0),
GPU_BATCH_OWNS_VBO_MAX = (GPU_BATCH_OWNS_VBO << (GPU_BATCH_VBO_MAX_LEN - 1)),
GPU_BATCH_OWNS_VBO_ANY = ((GPU_BATCH_OWNS_VBO << GPU_BATCH_VBO_MAX_LEN) - 1),
/** gpu::IndexBuf ownership. */
GPU_BATCH_OWNS_INDEX = (GPU_BATCH_OWNS_VBO_MAX << 1),
/** Has been initialized. At least one VBO is set. */
GPU_BATCH_INIT = (1 << 26),
/** Batch is initialized but its VBOs are still being populated. (optional) */
GPU_BATCH_BUILDING = (1 << 26),
/** Cached data need to be rebuild. (VAO, PSO, ...) */
GPU_BATCH_DIRTY = (1 << 27),
};
#define GPU_BATCH_OWNS_NONE GPU_BATCH_INVALID
BLI_STATIC_ASSERT(GPU_BATCH_OWNS_INDEX < GPU_BATCH_INIT,
"GPUBatchFlag: Error: status flags are shadowed by the ownership bits!")
ENUM_OPERATORS(GPUBatchFlag)
namespace gpu {
/**
* Base class which is then specialized for each implementation (GL, VK, ...).
* TODO(fclem): Make the content of this struct hidden and expose getters/setters.
*
* Do not allocate manually as the real struct is bigger (i.e: GLBatch). This is only
* the common and "public" part of the struct. Use `GPU_batch_calloc()` and `GPU_batch_create()`
* instead.
*/
class Batch {
public:
/** verts[0] is required, others can be nullptr */
gpu::VertBuf *verts[GPU_BATCH_VBO_MAX_LEN];
/** nullptr if element list not needed */
gpu::IndexBuf *elem;
/** Number of vertices to draw for procedural drawcalls. -1 otherwise. */
int32_t procedural_vertices;
/** Bookkeeping. */
GPUBatchFlag flag;
/** Type of geometry to draw. */
GPUPrimType prim_type;
/** Current assigned shader. DEPRECATED. Here only for uniform binding. */
gpu::Shader *shader;
virtual ~Batch() = default;
virtual void draw(int v_first, int v_count, int i_first, int i_count) = 0;
virtual void draw_indirect(gpu::StorageBuf *indirect_buf, intptr_t offset) = 0;
virtual void multi_draw_indirect(gpu::StorageBuf *indirect_buf,
int count,
intptr_t offset,
intptr_t stride) = 0;
uint32_t vertex_count_get() const
{
if (elem) {
return elem_()->index_len_get();
}
return verts_(0)->vertex_len;
}
/* Convenience casts. */
IndexBuf *elem_() const
{
return elem;
}
VertBuf *verts_(const int index) const
{
return verts[index];
}
};
} // namespace gpu
/* -------------------------------------------------------------------- */
/** \name Creation
* \{ */
/**
* Allocate a #gpu::Batch with a cleared state.
* The returned #gpu::Batch needs to be passed to `GPU_batch_init` before being usable.
*/
gpu::Batch *GPU_batch_calloc();
/**
* Creates a #gpu::Batch with explicit buffer ownership.
*/
gpu::Batch *GPU_batch_create_ex(GPUPrimType primitive_type,
gpu::VertBuf *vertex_buf,
gpu::IndexBuf *index_buf,
GPUBatchFlag owns_flag);
gpu::Batch *GPU_batch_create_procedural(GPUPrimType primitive_type, int32_t vertex_count);
/**
* Creates a #gpu::Batch without buffer ownership.
*/
#define GPU_batch_create(primitive_type, vertex_buf, index_buf) \
GPU_batch_create_ex(primitive_type, vertex_buf, index_buf, (GPUBatchFlag)0)
/**
* Initialize a cleared #gpu::Batch with explicit buffer ownership.
* A #gpu::Batch is in cleared state if it was just allocated using `GPU_batch_calloc()`
* or cleared using `GPU_batch_clear()`.
*/
void GPU_batch_init_ex(gpu::Batch *batch,
GPUPrimType primitive_type,
gpu::VertBuf *vertex_buf,
gpu::IndexBuf *index_buf,
GPUBatchFlag owns_flag);
/**
* Initialize a cleared #gpu::Batch without buffer ownership.
* A #gpu::Batch is in cleared state if it was just allocated using `GPU_batch_calloc()`
* or cleared using `GPU_batch_clear()`.
*/
#define GPU_batch_init(batch, primitive_type, vertex_buf, index_buf) \
GPU_batch_init_ex(batch, primitive_type, vertex_buf, index_buf, (GPUBatchFlag)0)
/**
* DEPRECATED: It is easy to loose ownership with this. To be removed.
* This will share the VBOs with the new batch.
*/
void GPU_batch_copy(gpu::Batch *batch_dst, gpu::Batch *batch_src);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deletion
* \{ */
/**
* Clear a #gpu::Batch without freeing its own memory.
* The #gpu::Batch can then be reused using `GPU_batch_init()`.
* Discards all owned vertex and index buffers.
*/
void GPU_batch_clear(gpu::Batch *batch);
void GPU_batch_zero(gpu::Batch *batch);
#define GPU_BATCH_CLEAR_SAFE(batch) \
do { \
if (batch != nullptr) { \
GPU_batch_clear(batch); \
GPU_batch_zero(batch); \
} \
} while (0)
/**
* Free a #gpu::Batch object.
* Discards all owned vertex and index buffers.
*/
void GPU_batch_discard(gpu::Batch *batch);
#define GPU_BATCH_DISCARD_SAFE(batch) \
do { \
if (batch != nullptr) { \
GPU_batch_discard(batch); \
batch = nullptr; \
} \
} while (0)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Buffers Management
* \{ */
/**
* Add the given \a vertex_buf as vertex buffer to a #gpu::Batch.
* \return the index of verts in the batch.
*/
int GPU_batch_vertbuf_add(gpu::Batch *batch, gpu::VertBuf *vertex_buf, bool own_vbo);
/**
* Set the index buffer of a #gpu::Batch.
* \note Override any previously assigned index buffer (and free it if owned).
*/
void GPU_batch_elembuf_set(gpu::Batch *batch, gpu::IndexBuf *index_buf, bool own_ibo);
/**
* Returns true if the #GPUbatch has \a vertex_buf in its vertex buffer list.
* \note The search is only conducted on the non-instance rate vertex buffer list.
*/
bool GPU_batch_vertbuf_has(const gpu::Batch *batch, const gpu::VertBuf *vertex_buf);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shader Binding & Uniforms
*
* TODO(fclem): This whole section should be removed. See the other `TODO`s in this section.
* This is because we want to remove #gpu::Batch.shader to avoid usage mistakes.
* Interacting directly with the #gpu::Shader provide a clearer interface and less error-prone.
* \{ */
/**
* Sets the shader to be drawn with this #gpu::Batch.
* \note This need to be called first for the `GPU_batch_uniform_*` functions to work.
*/
/* TODO(fclem): These should be removed and replaced by `GPU_shader_bind()`. */
void GPU_batch_set_shader(gpu::Batch *batch,
gpu::Shader *shader,
const gpu::shader::SpecializationConstants *constants_state = nullptr);
void GPU_batch_program_set_builtin(gpu::Batch *batch, GPUBuiltinShader shader_id);
void GPU_batch_program_set_builtin_with_config(gpu::Batch *batch,
GPUBuiltinShader shader_id,
GPUShaderConfig sh_cfg);
/**
* Bind program bound to IMM (immediate mode) to the #gpu::Batch.
*
* XXX: Use this with much care. Drawing with the #gpu::Batch API is not compatible with
* IMM. DO NOT DRAW WITH THE BATCH BEFORE CALLING #immUnbindProgram.
*/
void GPU_batch_program_set_imm_shader(gpu::Batch *batch);
/**
* Set uniform variables for the shader currently bound to the #gpu::Batch.
*/
/* TODO(fclem): These need to be replaced by GPU_shader_uniform_* with explicit shader. */
#define GPU_batch_uniform_1i(batch, name, x) GPU_shader_uniform_1i((batch)->shader, name, x);
#define GPU_batch_uniform_1b(batch, name, x) GPU_shader_uniform_1b((batch)->shader, name, x);
#define GPU_batch_uniform_1f(batch, name, x) GPU_shader_uniform_1f((batch)->shader, name, x);
#define GPU_batch_uniform_2f(batch, name, x, y) GPU_shader_uniform_2f((batch)->shader, name, x, y);
#define GPU_batch_uniform_3f(batch, name, x, y, z) \
GPU_shader_uniform_3f((batch)->shader, name, x, y, z);
#define GPU_batch_uniform_4f(batch, name, x, y, z, w) \
GPU_shader_uniform_4f((batch)->shader, name, x, y, z, w);
#define GPU_batch_uniform_2fv(batch, name, val) GPU_shader_uniform_2fv((batch)->shader, name, val);
#define GPU_batch_uniform_3fv(batch, name, val) GPU_shader_uniform_3fv((batch)->shader, name, val);
#define GPU_batch_uniform_4fv(batch, name, val) GPU_shader_uniform_4fv((batch)->shader, name, val);
#define GPU_batch_uniform_2fv_array(batch, name, len, val) \
GPU_shader_uniform_2fv_array((batch)->shader, name, len, val);
#define GPU_batch_uniform_4fv_array(batch, name, len, val) \
GPU_shader_uniform_4fv_array((batch)->shader, name, len, val);
#define GPU_batch_uniform_mat4(batch, name, val) \
GPU_shader_uniform_mat4((batch)->shader, name, val);
#define GPU_batch_uniformbuf_bind(batch, name, ubo) \
GPU_uniformbuf_bind(ubo, GPU_shader_get_ubo_binding((batch)->shader, name));
#define GPU_batch_texture_bind(batch, name, tex) \
GPU_texture_bind(tex, GPU_shader_get_sampler_binding((batch)->shader, name));
/**
* Bind vertex and index buffers to SSBOs using `Frequency::GEOMETRY`.
*/
void GPU_batch_bind_as_resources(gpu::Batch *batch,
gpu::Shader *shader,
const gpu::shader::SpecializationConstants *constants = nullptr);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shader Binding & Uniforms
* \{ */
/**
* Draw the #gpu::Batch with vertex count and instance count from its vertex buffers
* lengths. Ensures the associated shader is bound. TODO(fclem) remove this behavior.
*/
void GPU_batch_draw(gpu::Batch *batch);
/**
* Draw the #gpu::Batch with vertex count and instance count from its vertex buffers
* lengths. Ensures the associated shader is bound. TODO(fclem) remove this behavior.
*
* A \a vertex_count of 0 will use the default number of vertex.
* The \a vertex_first sets the start of the instance-rate attributes.
*
* \note No out-of-bound access check is made on the vertex buffers since they are tricky to
* detect. Double check that the range of vertex has data or that the data isn't read by the
* shader.
*/
void GPU_batch_draw_range(gpu::Batch *batch, int vertex_first, int vertex_count);
/**
* Draw multiple instances of the #gpu::Batch with custom instance range.
* Ensures the associated shader is bound. TODO(fclem) remove this behavior.
*
* An \a instance_count of 0 will use the default number of instances.
* The \a instance_first sets the start of the instance-rate attributes.
*
* \note this can be used even if the #gpu::Batch contains no instance-rate attributes.
* \note No out-of-bound access check is made on the vertex buffers since they are tricky to
* detect. Double check that the range of vertex has data or that the data isn't read by the
* shader.
*/
void GPU_batch_draw_instance_range(gpu::Batch *batch, int instance_first, int instance_count);
/**
* Draw the #gpu::Batch custom parameters.
* IMPORTANT: This does not bind/unbind shader and does not call GPU_matrix_bind().
*
* A \a vertex_count of 0 will use the default number of vertex.
* An \a instance_count of 0 will use the default number of instances.
*
* \note No out-of-bound access check is made on the vertex buffers since they are tricky to
* detect. Double check that the range of vertex has data or that the data isn't read by the
* shader.
*/
void GPU_batch_draw_advanced(
gpu::Batch *batch, int vertex_first, int vertex_count, int instance_first, int instance_count);
/**
* Issue a single draw call using arguments sourced from a #gpu::StorageBuf.
* The argument are expected to be valid for the type of geometry contained by this
* #gpu::Batch (index or non-indexed).
*
* The indirect buffer needs to be synced after filling its contents and before calling this
* function using `GPU_storagebuf_sync_as_indirect_buffer`.
*
* For more info see the GL documentation:
* https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDrawArraysIndirect.xhtml
*/
void GPU_batch_draw_indirect(gpu::Batch *batch, gpu::StorageBuf *indirect_buf, intptr_t offset);
/**
* Issue \a count draw calls using arguments sourced from a #gpu::StorageBuf.
* The \a stride (in bytes) control the spacing between each command description.
* The argument are expected to be valid for the type of geometry contained by this
* #gpu::Batch (index or non-indexed).
*
* The indirect buffer needs to be synced after filling its contents and before calling this
* function using `GPU_storagebuf_sync_as_indirect_buffer`.
*
* For more info see the GL documentation:
* https://registry.khronos.org/OpenGL-Refpages/gl4/html/glMultiDrawArraysIndirect.xhtml
*/
void GPU_batch_multi_draw_indirect(
gpu::Batch *batch, gpu::StorageBuf *indirect_buf, int count, intptr_t offset, intptr_t stride);
/**
* Return indirect draw call parameters for this #gpu::Batch.
* NOTE: \a r_base_index is set to -1 if not using an index buffer.
*/
void GPU_batch_draw_parameter_get(gpu::Batch *batch,
int *r_vertex_count,
int *r_vertex_first,
int *r_base_index,
int *r_instance_count);
/**
* Return vertex range for this #gpu::Batch when using primitive expansions.
*/
IndexRange GPU_batch_draw_expanded_parameter_get(GPUPrimType input_prim_type,
GPUPrimType output_prim_type,
int vertex_count,
int vertex_first,
int output_primitive_cout);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Procedural drawing
*
* A draw-call always need a batch to be issued.
* These are dummy batches that contains no vertex data and can be used to render geometry
* without per vertex inputs.
* \{ */
/**
* Batch with no attributes, suited for rendering procedural geometry.
* IMPORTANT: The returned batch is only valid for the current context.
*/
gpu::Batch *GPU_batch_procedural_points_get();
/**
* Batch with no attributes, suited for rendering procedural geometry.
* IMPORTANT: The returned batch is only valid for the current context.
*/
gpu::Batch *GPU_batch_procedural_lines_get();
/**
* Batch with no attributes, suited for rendering procedural geometry.
* IMPORTANT: The returned batch is only valid for the current context.
*/
gpu::Batch *GPU_batch_procedural_triangles_get();
/**
* Batch with no attributes, suited for rendering procedural geometry.
* IMPORTANT: The returned batch is only valid for the current context.
*/
gpu::Batch *GPU_batch_procedural_triangle_strips_get();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Module init/exit
* \{ */
void gpu_batch_init();
void gpu_batch_exit();
/** \} */
} // namespace blender

View File

@@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Batched geometry rendering is powered by the GPU library.
* This file contains any additions or modifications specific to Blender.
*/
#pragma once
#include "BLI_compiler_attrs.h"
namespace blender {
namespace gpu {
class Batch;
class StorageBuf;
} // namespace gpu
/* `gpu_batch_presets.cc` */
/* Replacement for #gluSphere */
gpu::Batch *GPU_batch_preset_sphere(int lod) ATTR_WARN_UNUSED_RESULT;
gpu::Batch *GPU_batch_preset_sphere_wire(int lod) ATTR_WARN_UNUSED_RESULT;
/**
* To be used with procedural placement inside shader.
*/
gpu::Batch *GPU_batch_preset_quad();
void gpu_batch_presets_init();
/* Registers batch to be destroyed at exit time. */
void gpu_batch_presets_register(gpu::Batch *preset_batch);
/* Registers buffer to be destroyed at exit time. */
void gpu_batch_storage_buffer_register(gpu::StorageBuf *preset_buffer);
void gpu_batch_presets_exit();
} // namespace blender

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_sys_types.h"
namespace blender {
struct rctf;
namespace gpu {
class Batch;
}
/* `gpu_batch_utils.cc` */
/**
* Creates triangles from a byte-array of polygons.
*
* See 'make_shape_2d_from_blend.py' utility to create data to pass to this function.
*
* \param polys_flat: Pairs of X, Y coordinates (repeating to signify closing the polygon).
* \param polys_flat_len: Length of the array (must be an even number).
* \param rect: Optional region to map the byte 0..255 coords to. When not set use -1..1.
*/
gpu::Batch *GPU_batch_tris_from_poly_2d_encoded(const uchar *polys_flat,
uint polys_flat_len,
const rctf *rect) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1);
gpu::Batch *GPU_batch_wire_from_poly_2d_encoded(const uchar *polys_flat,
uint polys_flat_len,
const rctf *rect) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1);
/**
* Create a batch of a unit cube (-1..+1).
* Only has position attribute.
*/
gpu::Batch *GPU_batch_unit_cube() ATTR_WARN_UNUSED_RESULT;
/**
* Replacement for #gluSphere.
*
* \note Only use by draw manager. Use the presets function instead for interface.
*/
gpu::Batch *gpu_batch_sphere(int lat_res, int lon_res) ATTR_WARN_UNUSED_RESULT;
} // namespace blender

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU Capabilities & workarounds
* This module expose the reported implementation limits & enabled
* workaround for drivers that needs specific code-paths.
*/
#pragma once
#include "BLI_sys_types.h"
namespace blender {
int GPU_max_texture_size();
int GPU_max_texture_3d_size();
uint32_t GPU_max_buffer_texture_size();
int GPU_max_texture_layers();
int GPU_max_textures();
int GPU_max_images();
int GPU_max_work_group_count(int index);
int GPU_max_work_group_size(int index);
int GPU_max_uniforms_vert();
int GPU_max_uniforms_frag();
int GPU_max_batch_indices();
int GPU_max_batch_vertices();
int GPU_max_vertex_attribs();
int GPU_max_varying_floats();
int GPU_max_shader_storage_buffer_bindings();
int GPU_max_compute_shader_storage_blocks();
size_t GPU_max_uniform_buffer_size();
size_t GPU_max_storage_buffer_size();
/* Used when binding subrange of SSBOs. In bytes.
* The start of the range must be aligned with this value. */
size_t GPU_storage_buffer_alignment();
int GPU_extensions_len();
const char *GPU_extension_get(int i);
int GPU_texture_size_with_limit(int res);
/**
* Returns whether it should be "safe" to use texture of a given size.
*
* The heuristic is that maybe allocating texture that is 25% of
* #GPU_max_texture_size squared is fine. Note that the actual texture creation
* can still fail even if deemed "safe" by this function, depending on current memory
* usage, texture format, etc.
*/
bool GPU_is_safe_texture_size(int width, int height);
bool GPU_use_subprocess_compilation();
int GPU_max_parallel_compilations();
bool GPU_stencil_clasify_buffer_workaround();
bool GPU_depth_blitting_workaround();
bool GPU_use_main_context_workaround();
bool GPU_use_hq_normals_workaround();
bool GPU_geometry_shader_support();
bool GPU_hdr_support();
bool GPU_stencil_export_support();
bool GPU_mem_stats_supported();
void GPU_mem_stats_get(int *r_totalmem, int *r_freemem);
/**
* Return support for the active context + window.
*/
bool GPU_stereo_quadbuffer_support();
int GPU_minimum_per_vertex_stride();
/** WARNING: Should only be called at startup from creator_args. Never call it at runtime. */
void GPU_compilation_subprocess_override_set(int count);
} // namespace blender

View File

@@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#define PROGRAM_NO_OPTI 0
// #define GPU_NO_USE_PY_REFERENCES
/* GPU_INLINE */
#if defined(_MSC_VER)
# define GPU_INLINE static __forceinline
#else
# define GPU_INLINE static inline __attribute__((always_inline)) __attribute__((__unused__))
#endif

View File

@@ -0,0 +1,292 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
namespace blender {
/**
* Describes the load operation of a frame-buffer attachment at the start of a render pass.
*/
enum GPULoadOp {
/**
* Clear the frame-buffer attachment using the clear value.
*/
GPU_LOADACTION_CLEAR = 0,
/**
* Load the value from the attached texture.
* Cannot be used with memoryless attachments.
* Slower than `GPU_LOADACTION_CLEAR` or `GPU_LOADACTION_DONT_CARE`.
*/
GPU_LOADACTION_LOAD,
/**
* Do not care about the content of the attachment when the render pass starts.
* Useful if only the values being written are important.
* Faster than `GPU_LOADACTION_CLEAR`.
*/
GPU_LOADACTION_DONT_CARE,
};
/**
* Describes the store operation of a frame-buffer attachment at the end of a render pass.
*/
enum GPUStoreOp {
/**
* Do not care about the content of the attachment when the render pass ends.
* Useful if only the values being written are important.
* Cannot be used with memoryless attachments.
*/
GPU_STOREACTION_STORE = 0,
/**
* The result of the rendering for this attachment will be discarded.
* No writes to the texture memory will be done which makes it faster than
* `GPU_STOREACTION_STORE`.
* IMPORTANT: The actual values of the attachment is to be considered undefined.
* Only to be used on transient attachment that are only used within the boundaries of
* a render pass (ex.: Unneeded depth buffer result).
*/
GPU_STOREACTION_DONT_CARE,
};
/**
* Describes the state of a frame-buffer attachment during a sub-pass.
*
* NOTE: Until this is correctly implemented in all backend, reading and writing from the
* same attachment will not work. Although there is no case where it would currently be useful.
*/
enum GPUAttachmentState {
/** Attachment will not be written during rendering. */
GPU_ATTACHMENT_IGNORE = 0,
/** Attachment will be written during render sub-pass. This also works with blending. */
GPU_ATTACHMENT_WRITE,
/** Attachment is used as input in the fragment shader. Incompatible with depth on Metal. */
GPU_ATTACHMENT_READ,
};
enum GPUFrontFace {
GPU_CLOCKWISE,
GPU_COUNTERCLOCKWISE,
};
namespace gpu {
enum class ShaderStage : uint8_t {
VERTEX = 1 << 0,
FRAGMENT = 1 << 1,
COMPUTE = 2 << 1,
ANY = (ShaderStage::VERTEX | ShaderStage::FRAGMENT | ShaderStage::COMPUTE),
};
ENUM_OPERATORS(ShaderStage);
} // namespace gpu
namespace gpu::shader {
enum class Type : int8_t {
/* Types supported natively across all GPU back-ends. */
float_t = 0,
float2_t,
float3_t,
float4_t,
float3x3_t,
float4x4_t,
uint_t,
uint2_t,
uint3_t,
uint4_t,
int_t,
int2_t,
int3_t,
int4_t,
bool_t,
/* Additionally supported types to enable data optimization and native
* support in some GPU back-ends.
* NOTE: These types must be representable in all APIs. E.g. `VEC3_101010I2` is aliased as vec3
* in the GL back-end, as implicit type conversions from packed normal attribute data to vec3 is
* supported. UCHAR/CHAR types are natively supported in Metal and can be used to avoid
* additional data conversions for `GPU_COMP_U8` vertex attributes. */
float3_10_10_10_2_t,
uchar_t,
uchar2_t,
uchar3_t,
uchar4_t,
char_t,
char2_t,
char3_t,
char4_t,
ushort_t,
ushort2_t,
ushort3_t,
ushort4_t,
short_t,
short2_t,
short3_t,
short4_t
};
BLI_INLINE int to_component_count(const Type &type)
{
switch (type) {
case Type::float_t:
case Type::uint_t:
case Type::int_t:
case Type::bool_t:
return 1;
case Type::float2_t:
case Type::uint2_t:
case Type::int2_t:
return 2;
case Type::float3_t:
case Type::uint3_t:
case Type::int3_t:
return 3;
case Type::float4_t:
case Type::uint4_t:
case Type::int4_t:
return 4;
case Type::float3x3_t:
return 9;
case Type::float4x4_t:
return 16;
/* Alias special types. */
case Type::uchar_t:
case Type::ushort_t:
case Type::char_t:
case Type::short_t:
return 1;
case Type::uchar2_t:
case Type::ushort2_t:
case Type::char2_t:
case Type::short2_t:
return 2;
case Type::uchar3_t:
case Type::ushort3_t:
case Type::char3_t:
case Type::short3_t:
return 3;
case Type::uchar4_t:
case Type::ushort4_t:
case Type::char4_t:
case Type::short4_t:
return 4;
case Type::float3_10_10_10_2_t:
return 3;
}
BLI_assert_unreachable();
return -1;
}
struct SpecializationConstant {
struct Value {
union {
uint32_t u;
int32_t i;
float f;
};
bool operator==(const Value &other) const
{
return u == other.u;
}
uint64_t hash() const
{
return uint64_t(u);
}
};
Type type;
StringRefNull name;
Value value;
SpecializationConstant() = default;
SpecializationConstant(const char *name, uint32_t value) : type(Type::uint_t), name(name)
{
this->value.u = value;
}
SpecializationConstant(const char *name, int value) : type(Type::int_t), name(name)
{
this->value.i = value;
}
SpecializationConstant(const char *name, float value) : type(Type::float_t), name(name)
{
this->value.f = value;
}
SpecializationConstant(const char *name, bool value) : type(Type::bool_t), name(name)
{
this->value.u = value ? 1 : 0;
}
bool operator==(const SpecializationConstant &b) const
{
return this->type == b.type && this->name == b.name && this->value == b.value;
}
};
/**
* Specialization constants as a Struct-of-Arrays. Allow simpler comparison and reset.
* The backend is free to implement their support as they see fit.
*/
struct SpecializationConstants {
Vector<gpu::shader::Type, 8> types;
/* Current values set by `GPU_shader_constant_*()` call. The backend can choose to interpret
* that however it wants (i.e: bind another shader instead). */
Vector<SpecializationConstant::Value, 8> values;
void set_value(int index, uint32_t value)
{
BLI_assert_msg(types[index] == Type::uint_t, "Mismatch between interface and constant type");
values[index].u = value;
}
void set_value(int index, int value)
{
BLI_assert_msg(types[index] == Type::int_t, "Mismatch between interface and constant type");
values[index].i = value;
}
void set_value(int index, float value)
{
BLI_assert_msg(types[index] == Type::float_t, "Mismatch between interface and constant type");
values[index].f = value;
}
void set_value(int index, bool value)
{
BLI_assert_msg(types[index] == Type::bool_t, "Mismatch between interface and constant type");
values[index].u = value ? 1 : 0;
}
bool is_empty() const
{
return types.is_empty();
}
};
struct CompilationConstant {
Type type;
StringRefNull name;
/* Reusing value type. */
SpecializationConstant::Value value;
bool operator==(const CompilationConstant &b) const
{
return this->type == b.type && this->name == b.name && this->value == b.value;
}
};
} // namespace gpu::shader
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_subprocess.hh"
namespace blender {
#if defined(WITH_OPENGL_BACKEND) && BLI_SUBPROCESS_SUPPORT
void GPU_compilation_subprocess_run(const char *subprocess_name);
#endif
} // namespace blender

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU Compute Pipeline
*
* Allows to dispatch compute shader tasks on the GPU.
* Every dispatch is sent to the active `GPUContext`.
*/
#pragma once
#include "BLI_sys_types.h"
#include "GPU_shader.hh"
#include "GPU_storage_buffer.hh"
namespace blender {
/**
* Dispatch a compute shader task.
* The number of work groups (aka thread groups) is bounded by `GPU_max_work_group_count()` which
* might be different in each of the 3 dimensions.
*/
void GPU_compute_dispatch(gpu::Shader *shader,
uint groups_x_len,
uint groups_y_len,
uint groups_z_len,
const gpu::shader::SpecializationConstants *constants_state = nullptr);
/**
* Dispatch a compute shader task. The size of the dispatch is sourced from a \a indirect_buf
* which must follow this layout:
* \code{.c}
* struct DispatchIndirectCommand {
* uint groups_x_len;
* uint groups_y_len;
* uint groups_z_len;
* };
* \encode
*
* \note The writes to the \a indirect_buf do not need to be synchronized as a memory barrier is
* emitted internally.
*
* The number of work groups (aka thread groups) is bounded by `GPU_max_work_group_count()` which
* might be different in each of the 3 dimensions.
*/
void GPU_compute_dispatch_indirect(
gpu::Shader *shader,
gpu::StorageBuf *indirect_buf,
const gpu::shader::SpecializationConstants *constants_state = nullptr);
} // namespace blender

View File

@@ -0,0 +1,214 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* This interface allow GPU to manage VAOs for multiple context and threads.
*/
#pragma once
#include <cstdint>
#include <cstdio>
#include "GPU_platform.hh"
struct GHOST_GPUDevice;
class GHOST_IContext;
class GHOST_ISystem;
class GHOST_IWindow;
namespace blender {
struct GPUContext;
/* GPU back-ends abstract the differences between different APIs. #GPU_context_create
* automatically initializes the back-end, and #GPU_context_discard frees it when there
* are no more contexts. */
bool GPU_backend_supported();
void GPU_backend_type_selection_set(const GPUBackendType backend);
GPUBackendType GPU_backend_type_selection_get();
GPUBackendType GPU_backend_get_type();
const char *GPU_backend_get_name();
/**
* Detect the most suited GPUBackendType.
*
* - The detected backend will be set in `GPU_backend_type_selection_set`.
* - When GPU_backend_type_selection_is_overridden it checks the overridden backend.
* When not overridden it checks a default list.
* - OpenGL backend will be checked as a fallback for Metal.
*
* Returns true when detection found a supported backend, otherwise returns false.
* When no supported backend is found GPU_backend_type_selection_set is called with
* GPU_BACKEND_NONE.
*/
bool GPU_backend_type_selection_detect();
/**
* Alter the GPU_backend_type_selection_detect to only test a specific backend
*/
void GPU_backend_type_selection_set_override(GPUBackendType backend_type);
/**
* Check if the GPU_backend_type_selection_detect is overridden to only test a specific backend.
*/
bool GPU_backend_type_selection_is_overridden();
/**
* Override the user-preference GPU device to use the specified GPU.
*
* Device selection first tries the device with this (`vendor_id`, `device_id`, `index`),
* then falls back to the preferred device unless hard fail debugging is enabled. The
* values `vendor_id == uint32_t(-1)` and `device_id == uint32_t(-1)` are sentinels meaning
* "wildcard" (don't constrain that field), so passing both as the sentinel selects purely by
* enumeration `index`.
*/
void GPU_backend_preferred_device_set_override(int index, uint32_t vendor_id, uint32_t device_id);
/**
* Return the preferred GPU device for new contexts.
*/
GHOST_GPUDevice GPU_backend_preferred_device_get();
#ifdef WITH_VULKAN_BACKEND
/**
* Print one line per Vulkan device that meets minimum requirements, matching the
* `<vendor-hex>/<device-hex>/<index>` identifier used in user preferences. Used by
* `--gpu-device help`.
*/
void GPU_vulkan_supported_devices_print(FILE *fp);
#endif
/**
* Get the VSync value (when set).
*/
int GPU_backend_vsync_get();
/**
* Override the default VSync.
*
* \param vsync: See #GHOST_TVSyncModes for details.
*/
void GPU_backend_vsync_set_override(int vsync);
bool GPU_backend_vsync_is_overridden();
/** Opaque type hiding gpu::Context. */
GPUContext *GPU_context_create(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context);
/**
* To be called after #GPU_context_active_set(ctx_to_destroy).
*/
void GPU_context_discard(GPUContext *);
/**
* #GPUContext can be null.
*/
void GPU_context_active_set(GPUContext *);
GPUContext *GPU_context_active_get();
/**
* Begin and end frame are used to mark the singular boundary representing the lifetime of a whole
* frame. This also acts as a divisor for ensuring workload submission and flushing, especially for
* background rendering when there is no call to present.
* This is required by explicit-API's where there is no implicit workload flushing.
*/
void GPU_context_begin_frame(GPUContext *ctx);
void GPU_context_end_frame(GPUContext *ctx);
/**
* Legacy GPU (Intel HD4000 series) do not support sharing GPU objects between GPU
* contexts. EEVEE/Workbench can create different contexts for image/preview rendering, baking or
* compiling. When a legacy GPU is detected (`GPU_use_main_context_workaround()`) any worker
* threads should use the draw manager opengl context and make sure that they are the only one
* using it by locking the main context using these two functions.
*/
void GPU_context_main_lock();
void GPU_context_main_unlock();
/**
* \brief Enable shader create info pipeline state assert.
*
* Activates an assert when a shader create info contains pipeline states but using the shader
* still require a new pipeline. This helps to identify mismatches between the shader create info
* and actual usage.
*
* The assert can not be enabled by default as there are cases where new pipelines are expected.
* This function is used inside unit tests to check if pipeline creation is done when not expected.
* \param context: Context where to activate the pipeline creation debug.
* \param enable: #true enables the feature, #false disables the feature.
*
* \note Currently only supported by Vulkan.
*/
void GPU_context_debug_pipeline_creation(GPUContext *context, bool enable);
/** GPU Begin/end work blocks */
void GPU_render_begin();
void GPU_render_end();
/**
* For operations which need to run exactly once per frame -- even if there are no render updates.
*/
void GPU_render_step(bool force_resource_release = false);
/** For when we need access to a system context in order to create a GPU context. */
void GPU_backend_ghost_system_set(GHOST_ISystem *ghost_system_handle);
GHOST_ISystem *GPU_backend_ghost_system_get();
namespace gpu {
struct GPUSecondaryContextData {
GHOST_IContext *ghost_context = nullptr;
GPUContext *gpu_context = nullptr;
};
/** Creates a secondary off-screen GHOST and GPU contexts. Must be called on the main thread. */
GPUSecondaryContextData GPU_create_secondary_context();
/** Activates the given secondary GPU context. */
void GPU_activate_secondary_context(const GPUSecondaryContextData &data);
/** Deactivates the given secondary GPU context. */
void GPU_deactivate_secondary_context(const GPUSecondaryContextData &data);
/** Destroys the given secondary GPU context. */
void GPU_destroy_secondary_context(GPUSecondaryContextData &data);
/**
* Abstracts secondary GHOST and GPU context creation, activation and deletion.
* Must be created from the main thread and destructed from the thread they where activated in.
* (See GPUWorker for an usage example)
*/
class GPUSecondaryContext {
private:
GPUSecondaryContextData data_;
public:
GPUSecondaryContext();
~GPUSecondaryContext();
/** Must be called from a secondary thread. */
void activate();
};
/**
* \brief Activate pipeline creation debugging for a certain scope.
*/
struct DebugScopePipelineCreation {
private:
GPUContext *context_ = nullptr;
public:
DebugScopePipelineCreation(GPUContext *context) : context_(context)
{
GPU_context_debug_pipeline_creation(context, true);
}
~DebugScopePipelineCreation()
{
GPU_context_debug_pipeline_creation(context_, false);
}
};
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,163 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Helpers for GPU / drawing debugging.
*
*
* GPU debug capture usage example:
*
* ### Instant frame capture. ###
*
* Will trigger a capture and load it inside RenderDoc or Xcode.
*
* \code
* #include "GPU_debug.hh"
*
* void render_function()
* {
* GPU_debug_capture_begin(__func__);
* // Draw-call submission goes here.
* GPU_debug_capture_end();
* }
* \endcode
*
* ### Capture scopes. ###
*
* Capture scope can be sprinkled around the codebase for easier selective capture.
*
* They are listed from inside Xcode (on Mac) when doing a Metal capture.
*
* OpenGL and Vulkan backend need to use the `--debug-gpu-scope-capture` launch argument to specify
* which scope to capture. Building with RenderDoc API support is required for this launch option
* to be available.
*
* They can be nested but only one can be captured at a time.
*
* \code
* #include "GPU_debug.hh"
*
* void render_function()
* {
* static gpu::DebugScope capture_scope = {"UniqueName"};
*
* // Manually triggered version, better for conditional capture.
* capture_scope.begin();
* // Draw-call submission goes here.
* capture_scope.end();
*
* {
* // Scoped version, better for complex control flow.
* static gpu::DebugScope capture_scope = {"AnotherUniqueName"};
* capture_scope.scoped_capture();
* // Draw-call submission goes here.
* }
* }
* \endcode
*/
#pragma once
#include "BLI_index_range.hh"
#include <string>
namespace blender {
#define GPU_DEBUG_SHADER_COMPILATION_GROUP "Shader Compilation"
#define GPU_DEBUG_SHADER_SPECIALIZATION_GROUP "Shader Specialization"
void GPU_debug_group_begin(const char *name);
void GPU_debug_group_end();
/**
* Return a formatted string showing the current group hierarchy in this format:
* "Group1 > Group 2 > Group3 > ... > GroupN : "
*/
void GPU_debug_get_groups_names(int name_buf_len, char *r_name_buf);
std::string GPU_debug_get_groups_names(IndexRange levels = IndexRange(0, 9999));
/**
* Return true if inside a debug group with the same name.
*/
bool GPU_debug_group_match(const char *ref);
/**
* GPU Frame capture support.
*
* Allows instantaneous frame capture of GPU calls between begin/end.
*
* \param title: Optional title to set for the frame capture.
*/
void GPU_debug_capture_begin(const char *title);
void GPU_debug_capture_end();
/**
* GPU debug frame capture scopes.
*
* Allows creation of a GPU frame capture scope that define a region within which an external GPU
* Frame capture tool can perform a deferred capture of GPU API calls within the boundary upon user
* request.
*
* \param name: Unique name of capture scope displayed within capture tool.
* \return pointer wrapping an API-specific capture scope object.
* \note a capture scope should be created a single time and only used within one begin/end pair.
*/
void *GPU_debug_capture_scope_create(const char *name);
/**
* Used to declare the region within which GPU calls are captured when the scope is triggered.
*
* \param scope: Pointer to capture scope object created with GPU_debug_capture_scope_create.
* \return True if the capture tool is actively capturing this scope when function is executed.
* Otherwise, False.
*/
bool GPU_debug_capture_scope_begin(void *scope);
void GPU_debug_capture_scope_end(void *scope);
namespace gpu {
/**
* Need to be declared as static with a unique identifier string.
*/
struct DebugScope {
void *scope;
DebugScope(const char *identifier)
{
scope = GPU_debug_capture_scope_create(identifier);
}
void begin_capture()
{
GPU_debug_capture_scope_begin(scope);
}
void end_capture()
{
GPU_debug_capture_scope_end(scope);
}
struct ScopedCapture {
void *scope;
ScopedCapture(void *scope) : scope(scope)
{
GPU_debug_capture_scope_begin(scope);
}
~ScopedCapture()
{
GPU_debug_capture_scope_end(scope);
}
};
/* Capture everything until the end of the scope. */
ScopedCapture scoped_capture()
{
return ScopedCapture(scope);
}
};
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,265 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_assert.h"
#include "BLI_sys_types.h"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Standard Formats
* \{ */
/* NOTE: Metal does not support pixel formats with 3 channel. These are aliased to 4 channel types
* and converted on data upload. */
/* clang-format off */
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SNORM_8_(impl) impl(/*TODO*/, 1 * 1, 1, SNORM_8, R8_SNORM, R8Snorm, CharNormalized, R8_SNORM, r8_snorm)
#define SNORM_8_8_(impl) impl(/*TODO*/, 1 * 2, 2, SNORM_8_8, R8G8_SNORM, RG8Snorm, Char2Normalized, RG8_SNORM, rg8_snorm)
#define SNORM_8_8_8_(impl) impl(/*TODO*/, 1 * 3, 3, SNORM_8_8_8, R8G8B8_SNORM, RGBA8Snorm, Char3Normalized, RGB8_SNORM, rgb8_snorm)
#define SNORM_8_8_8_8_(impl) impl(/*TODO*/, 1 * 4, 4, SNORM_8_8_8_8, R8G8B8A8_SNORM, RGBA8Snorm, Char4Normalized, RGBA8_SNORM, rgba8_snorm)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SNORM_16_(impl) impl(/*TODO*/, 2 * 1, 1, SNORM_16, R16_SNORM, R16Snorm, ShortNormalized, R16_SNORM, r16_snorm)
#define SNORM_16_16_(impl) impl(/*TODO*/, 2 * 2, 2, SNORM_16_16, R16G16_SNORM, RG16Snorm, Short2Normalized, RG16_SNORM, rg16_snorm)
#define SNORM_16_16_16_(impl) impl(/*TODO*/, 2 * 3, 3, SNORM_16_16_16, R16G16B16_SNORM, RGBA16Snorm, Short3Normalized, RGB16_SNORM, rgb16_snorm)
#define SNORM_16_16_16_16_(impl) impl(/*TODO*/, 2 * 4, 4, SNORM_16_16_16_16, R16G16B16A16_SNORM, RGBA16Snorm, Short4Normalized, RGBA16_SNORM, rgba16_snorm)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define UNORM_8_(impl) impl(/*TODO*/, 1 * 1, 1, UNORM_8, R8_UNORM, R8Unorm, UCharNormalized, R8, r8_unorm)
#define UNORM_8_8_(impl) impl(/*TODO*/, 1 * 2, 2, UNORM_8_8, R8G8_UNORM, RG8Unorm, UChar2Normalized, RG8, rg8_unorm)
#define UNORM_8_8_8_(impl) impl(/*TODO*/, 1 * 3, 3, UNORM_8_8_8, R8G8B8_UNORM, RGBA8Unorm, UChar3Normalized, RGB8, rgb8_unorm)
#define UNORM_8_8_8_8_(impl) impl(/*TODO*/, 1 * 4, 4, UNORM_8_8_8_8, R8G8B8A8_UNORM, RGBA8Unorm, UChar4Normalized, RGBA8, rgba8_unorm)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define UNORM_16_(impl) impl(/*TODO*/, 2 * 1, 1, UNORM_16, R16_UNORM, R16Unorm, UShortNormalized, R16, r16_unorm)
#define UNORM_16_16_(impl) impl(/*TODO*/, 2 * 2, 2, UNORM_16_16, R16G16_UNORM, RG16Unorm, UShort2Normalized, RG16, rg16_unorm)
#define UNORM_16_16_16_(impl) impl(/*TODO*/, 2 * 3, 3, UNORM_16_16_16, R16G16B16_UNORM, RGBA16Unorm, UShort3Normalized, RGB16, rgb16_unorm)
#define UNORM_16_16_16_16_(impl) impl(/*TODO*/, 2 * 4, 4, UNORM_16_16_16_16, R16G16B16A16_UNORM, RGBA16Unorm, UShort4Normalized, RGBA16, rgba16_unorm)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SINT_8_(impl) impl(int8_t, 1 * 1, 1, SINT_8, R8_SINT, R8Sint, Char, R8I, r8_sint)
#define SINT_8_8_(impl) impl(char2, 1 * 2, 2, SINT_8_8, R8G8_SINT, RG8Sint, Char2, RG8I, rg8_sint)
#define SINT_8_8_8_(impl) impl(char3, 1 * 3, 3, SINT_8_8_8, R8G8B8_SINT, RGBA8Sint, Char3, RGB8I, rgb8_sint)
#define SINT_8_8_8_8_(impl) impl(char4, 1 * 4, 4, SINT_8_8_8_8, R8G8B8A8_SINT, RGBA8Sint, Char4, RGBA8I, rgba8_sint)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SINT_16_(impl) impl(int16_t, 2 * 1, 1, SINT_16, R16_SINT, R16Sint, Short, R16I, r16_sint)
#define SINT_16_16_(impl) impl(short2, 2 * 2, 2, SINT_16_16, R16G16_SINT, RG16Sint, Short2, RG16I, rg16_sint)
#define SINT_16_16_16_(impl) impl(short3, 2 * 3, 3, SINT_16_16_16, R16G16B16_SINT, RGBA16Sint, Short3, RGB16I, rgb16_sint)
#define SINT_16_16_16_16_(impl) impl(short4, 2 * 4, 4, SINT_16_16_16_16, R16G16B16A16_SINT, RGBA16Sint, Short4, RGBA16I, rgba16_sint)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SINT_32_(impl) impl(int32_t, 4 * 1, 1, SINT_32, R32_SINT, R32Sint, Int, R32I, r32_sint)
#define SINT_32_32_(impl) impl(int2, 4 * 2, 2, SINT_32_32, R32G32_SINT, RG32Sint, Int2, RG32I, rg32_sint)
#define SINT_32_32_32_(impl) impl(int3, 4 * 3, 3, SINT_32_32_32, R32G32B32_SINT, RGBA32Sint, Int3, RGB32I, rgb32_sint)
#define SINT_32_32_32_32_(impl) impl(int4, 4 * 4, 4, SINT_32_32_32_32, R32G32B32A32_SINT, RGBA32Sint, Int4, RGBA32I, rgba32_sint)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define UINT_8_(impl) impl(uint8_t, 1 * 1, 1, UINT_8, R8_UINT, R8Uint, UChar, R8UI, r8_uint)
#define UINT_8_8_(impl) impl(uchar2, 1 * 2, 2, UINT_8_8, R8G8_UINT, RG8Uint, UChar2, RG8UI, rg8_uint)
#define UINT_8_8_8_(impl) impl(uchar3, 1 * 3, 3, UINT_8_8_8, R8G8B8_UINT, RGBA8Uint, UChar3, RGB8UI, rgb8_uint)
#define UINT_8_8_8_8_(impl) impl(uchar4, 1 * 4, 4, UINT_8_8_8_8, R8G8B8A8_UINT, RGBA8Uint, UChar4, RGBA8UI, rgba8_uint)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define UINT_16_(impl) impl(uint16_t, 2 * 1, 1, UINT_16, R16_UINT, R16Uint, UShort, R16UI, r16_uint)
#define UINT_16_16_(impl) impl(ushort2, 2 * 2, 2, UINT_16_16, R16G16_UINT, RG16Uint, UShort2, RG16UI, rg16_uint)
#define UINT_16_16_16_(impl) impl(ushort3, 2 * 3, 3, UINT_16_16_16, R16G16B16_UINT, RGBA16Uint, UShort3, RGB16UI, rgb16_uint)
#define UINT_16_16_16_16_(impl) impl(ushort4, 2 * 4, 4, UINT_16_16_16_16, R16G16B16A16_UINT, RGBA16Uint, UShort4, RGBA16UI, rgba16_uint)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define UINT_32_(impl) impl(uint32_t, 4 * 1, 1, UINT_32, R32_UINT, R32Uint, UInt, R32UI, r32_uint)
#define UINT_32_32_(impl) impl(uint2, 4 * 2, 2, UINT_32_32, R32G32_UINT, RG32Uint, UInt2, RG32UI, rg32_uint)
#define UINT_32_32_32_(impl) impl(uint3, 4 * 3, 3, UINT_32_32_32, R32G32B32_UINT, RGBA32Uint, UInt3, RGB32UI, rgb32_uint)
#define UINT_32_32_32_32_(impl) impl(uint4, 4 * 4, 4, UINT_32_32_32_32, R32G32B32A32_UINT, RGBA32Uint, UInt4, RGBA32UI, rgba32_uint)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SFLOAT_16_(impl) impl(/*TODO*/, 2 * 1, 1, SFLOAT_16, R16_SFLOAT, R16Float, Half, R16F, r16_sfloat)
#define SFLOAT_16_16_(impl) impl(/*TODO*/, 2 * 2, 2, SFLOAT_16_16, R16G16_SFLOAT, RG16Float, Half2, RG16F, rg16_sfloat)
#define SFLOAT_16_16_16_(impl) impl(/*TODO*/, 2 * 3, 3, SFLOAT_16_16_16, R16G16B16_SFLOAT, RGBA16Float, Half3, RGB16F, rgb16_sfloat)
#define SFLOAT_16_16_16_16_(impl) impl(/*TODO*/, 2 * 4, 4, SFLOAT_16_16_16_16, R16G16B16A16_SFLOAT, RGBA16Float, Half4, RGBA16F, rgba16_sfloat)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SFLOAT_32_(impl) impl(float, 4 * 1, 1, SFLOAT_32, R32_SFLOAT, R32Float, Float, R32F, r32_sfloat)
#define SFLOAT_32_32_(impl) impl(float2, 4 * 2, 2, SFLOAT_32_32, R32G32_SFLOAT, RG32Float, Float2, RG32F, rg32_sfloat)
#define SFLOAT_32_32_32_(impl) impl(float3, 4 * 3, 3, SFLOAT_32_32_32, R32G32B32_SFLOAT, RGBA32Float, Float3, RGB32F, rgb32_sfloat)
#define SFLOAT_32_32_32_32_(impl) impl(float4, 4 * 4, 4, SFLOAT_32_32_32_32, R32G32B32A32_SFLOAT, RGBA32Float, Float4, RGBA32F, rgba32_sfloat)
/* clang-format on */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Special Formats
* \{ */
/* clang-format off */
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SNORM_10_10_10_2_(impl) impl(/*TODO*/, 4, 4, SNORM_10_10_10_2, A2B10G10R10_SNORM_PACK32, /* n/a */, Int1010102Normalized, /* n/a */, /* n/a */ )
#define UNORM_10_10_10_2_(impl) impl(/*TODO*/, 4, 4, UNORM_10_10_10_2, A2B10G10R10_UNORM_PACK32, RGB10A2Unorm, UInt1010102Normalized, RGB10_A2, rgb10_a2_unorm )
#define UINT_10_10_10_2_(impl) impl(/*TODO*/, 4, 4, UINT_10_10_10_2, A2B10G10R10_UINT_PACK32, RGB10A2Uint, /* n/a */, RGB10_A2UI, rgb10_a2_uint )
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define UFLOAT_11_11_10_(impl) impl(/*TODO*/, 4, 3, UFLOAT_11_11_10, B10G11R11_UFLOAT_PACK32, RG11B10Float, FloatRG11B10, R11F_G11F_B10F, r11_g11_b10_ufloat)
#define UFLOAT_9_9_9_EXP_5_(impl) impl(/*TODO*/, 4, 3, UFLOAT_9_9_9_EXP_5, E5B9G9R9_UFLOAT_PACK32, RGB9E5Float, FloatRGB9E5, RGB9_E5, /* n/a */)
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SRGBA_8_8_8_8_(impl) impl(/*TODO*/, 4, 4, SRGBA_8_8_8_8, R8G8B8A8_SRGB, RGBA8Unorm_sRGB, /* n/a */, SRGB8_ALPHA8, /* n/a */ )
#define SRGBA_8_8_8_(impl) impl(/*TODO*/, 3, 3, SRGBA_8_8_8, R8G8B8_SRGB, RGBA8Unorm_sRGB, /* n/a */, SRGB8, /* n/a */ )
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define UNORM_16_DEPTH_(impl) impl(/*TODO*/, 4, 1, UNORM_16_DEPTH, D16_UNORM, Depth16Unorm, /* n/a */, DEPTH_COMPONENT16, /* n/a */ )
#define UNORM_24_DEPTH_(impl) impl(/*TODO*/, 4, 1, UNORM_24_DEPTH, X8_D24_UNORM_PACK32, Depth24Unorm_Stencil8, /* n/a */, DEPTH_COMPONENT24, /* n/a */ )
#define UNORM_24_DEPTH_UINT_8_(impl) impl(/*TODO*/, 8, 1, UNORM_24_DEPTH_UINT_8, D24_UNORM_S8_UINT, Depth24Unorm_Stencil8, /* n/a */, DEPTH24_STENCIL8, /* n/a */ )
#define SFLOAT_32_DEPTH_(impl) impl(/*TODO*/, 4, 1, SFLOAT_32_DEPTH, D32_SFLOAT, Depth32Float, /* n/a */, DEPTH_COMPONENT32F, /* n/a */ )
#define SFLOAT_32_DEPTH_UINT_8_(impl) impl(/*TODO*/, 8, 1, SFLOAT_32_DEPTH_UINT_8, D32_SFLOAT_S8_UINT, Depth32Float_Stencil8, /* n/a */, DEPTH32F_STENCIL8, /* n/a */ )
/* type size comps blender_enum vk_enum mtl_pixel_enum mtl_vertex_enum gl_pixel_enum shader_enum */
#define SNORM_DXT1_(impl) impl(/* n/a */, 1, 1, SNORM_DXT1, BC1_RGBA_UNORM_BLOCK, BC1_RGBA, /* n/a */, COMPRESSED_RGBA_S3TC_DXT1_EXT, /* n/a */ )
#define SNORM_DXT3_(impl) impl(/* n/a */, 1, 1, SNORM_DXT3, BC2_UNORM_BLOCK, BC2_RGBA, /* n/a */, COMPRESSED_RGBA_S3TC_DXT3_EXT, /* n/a */ )
#define SNORM_DXT5_(impl) impl(/* n/a */, 1, 1, SNORM_DXT5, BC3_UNORM_BLOCK, BC3_RGBA, /* n/a */, COMPRESSED_RGBA_S3TC_DXT5_EXT, /* n/a */ )
#define SRGB_DXT1_(impl) impl(/* n/a */, 1, 1, SRGB_DXT1, BC1_RGBA_SRGB_BLOCK, BC1_RGBA_sRGB, /* n/a */, COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, /* n/a */ )
#define SRGB_DXT3_(impl) impl(/* n/a */, 1, 1, SRGB_DXT3, BC2_SRGB_BLOCK, BC2_RGBA_sRGB, /* n/a */, COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, /* n/a */ )
#define SRGB_DXT5_(impl) impl(/* n/a */, 1, 1, SRGB_DXT5, BC3_SRGB_BLOCK, BC3_RGBA_sRGB, /* n/a */, COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, /* n/a */ )
/* clang-format on */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Data Formats
* \{ */
/**
* Collection of all data formats.
* Vertex formats and Texture formats are both subset of this.
*/
enum class DataFormat : uint8_t {
Invalid = 0,
#define DECLARE(a, b, c, blender_enum, d, e, f, g, h) blender_enum,
#define GPU_DATA_FORMAT_EXPAND(impl) \
SNORM_8_(impl) \
SNORM_8_8_(impl) \
SNORM_8_8_8_(impl) \
SNORM_8_8_8_8_(impl) \
\
SNORM_16_(impl) \
SNORM_16_16_(impl) \
SNORM_16_16_16_(impl) \
SNORM_16_16_16_16_(impl) \
\
UNORM_8_(impl) \
UNORM_8_8_(impl) \
UNORM_8_8_8_(impl) \
UNORM_8_8_8_8_(impl) \
\
UNORM_16_(impl) \
UNORM_16_16_(impl) \
UNORM_16_16_16_(impl) \
UNORM_16_16_16_16_(impl) \
\
SINT_8_(impl) \
SINT_8_8_(impl) \
SINT_8_8_8_(impl) \
SINT_8_8_8_8_(impl) \
\
SINT_16_(impl) \
SINT_16_16_(impl) \
SINT_16_16_16_(impl) \
SINT_16_16_16_16_(impl) \
\
SINT_32_(impl) \
SINT_32_32_(impl) \
SINT_32_32_32_(impl) \
SINT_32_32_32_32_(impl) \
\
UINT_8_(impl) \
UINT_8_8_(impl) \
UINT_8_8_8_(impl) \
UINT_8_8_8_8_(impl) \
\
UINT_16_(impl) \
UINT_16_16_(impl) \
UINT_16_16_16_(impl) \
UINT_16_16_16_16_(impl) \
\
UINT_32_(impl) \
UINT_32_32_(impl) \
UINT_32_32_32_(impl) \
UINT_32_32_32_32_(impl) \
\
SFLOAT_16_(impl) \
SFLOAT_16_16_(impl) \
SFLOAT_16_16_16_(impl) \
SFLOAT_16_16_16_16_(impl) \
\
SFLOAT_32_(impl) \
SFLOAT_32_32_(impl) \
SFLOAT_32_32_32_(impl) \
SFLOAT_32_32_32_32_(impl) \
\
SNORM_10_10_10_2_(impl) \
UNORM_10_10_10_2_(impl) \
UINT_10_10_10_2_(impl) \
\
UFLOAT_11_11_10_(impl) \
UFLOAT_9_9_9_EXP_5_(impl) \
\
UNORM_16_DEPTH_(impl) \
UNORM_24_DEPTH_(impl) /* TODO(fclem): Incompatible with metal, is emulated. To remove. */ \
UNORM_24_DEPTH_UINT_8_(impl) \
SFLOAT_32_DEPTH_(impl) \
SFLOAT_32_DEPTH_UINT_8_(impl) \
\
SRGBA_8_8_8_(impl) \
SRGBA_8_8_8_8_(impl) \
\
SNORM_DXT1_(impl) \
SNORM_DXT3_(impl) \
SNORM_DXT5_(impl) \
SRGB_DXT1_(impl) \
SRGB_DXT3_(impl) \
SRGB_DXT5_(impl)
GPU_DATA_FORMAT_EXPAND(DECLARE)
#undef DECLARE
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utilities
*
* Allow querying information about the format enum values.
* \{ */
/* NOTE: Compressed format bytesize are rounded up as their actual value is fractional. */
inline int to_bytesize(const DataFormat format)
{
#define CASE(a, size, c, blender_enum, d, e, f, g, h) \
case DataFormat::blender_enum: \
return size;
switch (format) {
GPU_DATA_FORMAT_EXPAND(CASE)
case DataFormat::Invalid:
break;
}
#undef CASE
BLI_assert_unreachable();
return -1;
}
inline int format_component_len(const DataFormat format)
{
#define CASE(a, b, comp, blender_enum, d, e, f, g, h) \
case DataFormat::blender_enum: \
return comp;
switch (format) {
GPU_DATA_FORMAT_EXPAND(CASE)
case DataFormat::Invalid:
break;
}
#undef CASE
BLI_assert_unreachable();
return -1;
}
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,730 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* A `GPUFramebuffer` is a wrapper for a frame-buffer object (FBO) from the underlying graphic API.
*
* A `GPUFramebuffer` is limited to one context and thus cannot be shared across different
* contexts. In the case this is needed, one must recreate the same `GPUFramebuffer` in each
* context.
*
* Note that actual FBO creation & config is deferred until `GPU_framebuffer_bind` or
* `GPU_framebuffer_check_valid` is called. This means the context the `GPUFramebuffer` is bound
* with is the one active when `GPU_framebuffer_bind` is called.
*
* When a `gpu::Texture` is attached to a `GPUFramebuffer` a reference is created.
* Deleting either does not require any unbinding.
*
* A `GPUOffScreen` is a convenience type that holds a `GPUFramebuffer` and its associated
* `gpu::Texture`s. It is useful for quick drawing surface configuration.
*/
#pragma once
#include "GPU_common_types.hh"
#include "GPU_texture.hh"
#include "BLI_enum_flags.hh"
#include "BLI_math_vector_types.hh"
namespace blender {
namespace gpu {
class Texture;
class FrameBuffer;
} // namespace gpu
enum GPUFrameBufferBits {
GPU_COLOR_BIT = (1 << 0),
GPU_DEPTH_BIT = (1 << 1),
GPU_STENCIL_BIT = (1 << 2),
};
ENUM_OPERATORS(GPUFrameBufferBits)
/* Guaranteed by the spec and is never greater than 16 on any hardware or implementation. */
constexpr static int GPU_MAX_VIEWPORTS = 16;
struct GPUAttachment {
gpu::Texture *tex;
int layer, mip;
};
/* -------------------------------------------------------------------- */
/** \name Creation
* \{ */
/**
* Create a #FrameBuffer object. It is not configured and not bound to a specific context until
* `GPU_framebuffer_bind()` is called.
*/
gpu::FrameBuffer *GPU_framebuffer_create(const char *name);
/**
* Returns the current context active framebuffer.
* Return nullptr if no context is active.
*/
gpu::FrameBuffer *GPU_framebuffer_active_get();
/**
* Returns the default (back-left) frame-buffer. It will always exists even if it's just a dummy.
* Return nullptr if no context is active.
*/
gpu::FrameBuffer *GPU_framebuffer_back_get();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Free
* \{ */
/**
* Create a #FrameBuffer object. It is not configured and not bound to a specific context until
* `GPU_framebuffer_bind()` is called.
*/
void GPU_framebuffer_free(gpu::FrameBuffer *fb);
#define GPU_FRAMEBUFFER_FREE_SAFE(fb) \
do { \
if (fb != nullptr) { \
GPU_framebuffer_free(fb); \
fb = nullptr; \
} \
} while (0)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Binding
* \{ */
enum GPUBackBuffer {
/** Default framebuffer of a window. Always available. */
GPU_BACKBUFFER_LEFT = 0,
/** Right buffer of a window. Only available if window was created using stereo-view. */
GPU_BACKBUFFER_RIGHT,
};
/**
* Binds the active context's window frame-buffer.
* Note that `GPU_BACKBUFFER_RIGHT` is only available if the window was created using stereo-view.
*/
void GPU_backbuffer_bind(GPUBackBuffer back_buffer_type);
/**
* Binds a #FrameBuffer making it the active framebuffer for all geometry rendering.
*/
void GPU_framebuffer_bind(gpu::FrameBuffer *fb);
/**
* Same as `GPU_framebuffer_bind` but do not enable the SRGB transform.
*/
void GPU_framebuffer_bind_no_srgb(gpu::FrameBuffer *fb);
/**
* Binds back the active context's default frame-buffer.
* Equivalent to `GPU_backbuffer_bind(GPU_BACKBUFFER_LEFT)`.
*/
void GPU_framebuffer_restore();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Advanced binding control
* \{ */
struct GPULoadStore {
GPULoadOp load_action;
GPUStoreOp store_action;
double clear_value[4];
};
/* Empty bind point. */
#define NULL_ATTACHMENT_COLOR {0.0, 0.0, 0.0, 0.0}
#define NULL_LOAD_STORE \
{GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_DONT_CARE, NULL_ATTACHMENT_COLOR}
/**
* Load store config array (load_store_actions) matches attachment structure of
* GPU_framebuffer_config_array. This allows us to explicitly specify whether attachment data needs
* to be loaded and stored on a per-attachment basis. This enables a number of bandwidth
* optimizations:
* - No need to load contents if subsequent work is over-writing every pixel.
* - No need to store attachments whose contents are not used beyond this pass e.g. depth buffer.
* - State can be customized at bind-time rather than applying to the frame-buffer object as a
* whole.
*
* NOTE: Using GPU_framebuffer_clear_* functions in conjunction with a custom load-store
* configuration is invalid. Instead, utilize GPU_LOADACTION_CLEAR and provide a clear color as
* the third parameter in `GPULoadStore action`.
*
* For Color attachments: `{GPU_LOADACTION_CLEAR, GPU_STOREACTION_STORE, {Rf, Gf, Bf, Af}}`
* For Depth attachments: `{GPU_LOADACTION_CLEAR, GPU_STOREACTION_STORE, {Df}}`
*
* Example:
* \code{.c}
* GPU_framebuffer_bind_loadstore(&fb, {
* {GPU_LOADACTION_LOAD, GPU_STOREACTION_DONT_CARE} // must be depth buffer
* {GPU_LOADACTION_LOAD, GPU_STOREACTION_STORE}, // Color attachment 0
* {GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE}, // Color attachment 1
* {GPU_LOADACTION_DONT_CARE, GPU_STOREACTION_STORE} // Color attachment 2
* }, 4)
* \endcode
*/
void GPU_framebuffer_bind_loadstore(gpu::FrameBuffer *fb,
const GPULoadStore *load_store_actions,
uint load_store_actions_len);
#define GPU_framebuffer_bind_ex(_fb, ...) \
{ \
GPULoadStore actions[] = __VA_ARGS__; \
GPU_framebuffer_bind_loadstore(_fb, actions, (sizeof(actions) / sizeof(GPULoadStore))); \
}
/**
* Sub-pass config array matches attachment structure of `GPU_framebuffer_config_array`.
* This allows to explicitly specify attachment state within the next sub-pass.
* This enables a number of bandwidth optimizations specially on Tile Based Deferred Renderers
* where the attachments can be kept into tile memory and used in place for later sub-passes.
*
* IMPORTANT: When using this, the framebuffer initial state is undefined. A sub-pass transition
* need to be issued before any draw-call.
*
* Example:
* \code{.c}
* GPU_framebuffer_bind_loadstore(&fb, {
* GPU_ATTACHMENT_WRITE, // must be depth buffer
* GPU_ATTACHMENT_READ, // Color attachment 0
* GPU_ATTACHMENT_IGNORE, // Color attachment 1
* GPU_ATTACHMENT_WRITE} // Color attachment 2
* })
* \endcode
*
* \note Excess attachments will have no effect as long as they are GPU_ATTACHMENT_IGNORE.
*/
void GPU_framebuffer_subpass_transition_array(gpu::FrameBuffer *fb,
const GPUAttachmentState *attachment_states,
uint attachment_len);
#define GPU_framebuffer_subpass_transition(_fb, ...) \
{ \
GPUAttachmentState actions[] = __VA_ARGS__; \
GPU_framebuffer_subpass_transition_array( \
_fb, actions, (sizeof(actions) / sizeof(GPUAttachmentState))); \
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Attachments
* \{ */
/**
* How to use #GPU_framebuffer_ensure_config().
*
* Example:
* \code{.c}
* GPU_framebuffer_ensure_config(&fb, {
* GPU_ATTACHMENT_TEXTURE(depth), // must be depth buffer
* GPU_ATTACHMENT_TEXTURE(tex1),
* GPU_ATTACHMENT_TEXTURE_CUBEFACE(tex2, 0),
* GPU_ATTACHMENT_TEXTURE_LAYER_MIP(tex2, 0, 0)
* })
* \endcode
*
* \note Unspecified attachments (i.e: those beyond the last
* GPU_ATTACHMENT_* in GPU_framebuffer_ensure_config list) are left unchanged.
*
* \note Make sure that the dimensions of your textures matches
* otherwise you will have an invalid framebuffer error.
*/
#define GPU_framebuffer_ensure_config(_fb, ...) \
do { \
if (*(_fb) == nullptr) { \
*(_fb) = GPU_framebuffer_create(#_fb); \
} \
GPUAttachment config[] = __VA_ARGS__; \
GPU_framebuffer_config_array(*(_fb), config, (sizeof(config) / sizeof(GPUAttachment))); \
} while (0)
/**
* First #GPUAttachment in *config is always the depth/depth_stencil buffer.
* Following #GPUAttachments are color buffers.
* Setting #GPUAttachment.mip to -1 will leave the texture in this slot.
* Setting #GPUAttachment.tex to nullptr will detach the texture in this slot.
*/
void GPU_framebuffer_config_array(gpu::FrameBuffer *fb,
const GPUAttachment *config,
int config_len);
/** Empty bind point. */
#define GPU_ATTACHMENT_NONE \
GPUAttachment \
{ \
nullptr, -1, 0, \
}
/** Leave currently bound texture in this slot. DEPRECATED: Specify all textures for clarity. */
#define GPU_ATTACHMENT_LEAVE \
GPUAttachment \
{ \
nullptr, -1, -1, \
}
/** Bind the first mip level of a texture (all layers). */
#define GPU_ATTACHMENT_TEXTURE(_texture) \
GPUAttachment \
{ \
_texture, -1, 0, \
}
/** Bind the \a _mip level of a texture (all layers). */
#define GPU_ATTACHMENT_TEXTURE_MIP(_texture, _mip) \
GPUAttachment \
{ \
_texture, -1, _mip, \
}
/** Bind the \a _layer layer of the first mip level of a texture. */
#define GPU_ATTACHMENT_TEXTURE_LAYER(_texture, _layer) \
GPUAttachment \
{ \
_texture, _layer, 0, \
}
/** Bind the \a _layer layer of the \a _mip level of a texture. */
#define GPU_ATTACHMENT_TEXTURE_LAYER_MIP(_texture, _layer, _mip) \
GPUAttachment \
{ \
_texture, _layer, _mip, \
}
/** NOTE: The cube-face variants are equivalent to the layer ones but give better semantic. */
/** Bind the first mip level of a cube-map \a _face texture. */
#define GPU_ATTACHMENT_TEXTURE_CUBEFACE(_texture, _face) \
GPUAttachment \
{ \
_texture, _face, 0, \
}
/** Bind the \a _mip level of a cube-map \a _face texture. */
#define GPU_ATTACHMENT_TEXTURE_CUBEFACE_MIP(_texture, _face, _mip) \
GPUAttachment \
{ \
_texture, _face, _mip, \
}
/**
* Attach an entire texture mip level to a #FrameBuffer.
* Changes will only take effect next time `GPU_framebuffer_bind()` is called.
* \a slot is the color texture slot to bind this texture to. Must be 0 if it is the depth texture.
* \a mip is the mip level of this texture to attach to the framebuffer.
* DEPRECATED: Prefer using multiple #FrameBuffer with different configurations with
* `GPU_framebuffer_config_array()`.
*/
void GPU_framebuffer_texture_attach(gpu::FrameBuffer *fb,
gpu::Texture *texture,
int slot,
int mip);
/**
* Attach a single layer of an array texture mip level to a #FrameBuffer.
* Changes will only take effect next time `GPU_framebuffer_bind()` is called.
* \a slot is the color texture slot to bind this texture to. Must be 0 if it is the depth texture.
* \a layer is the layer of this array texture to attach to the framebuffer.
* \a mip is the mip level of this texture to attach to the framebuffer.
* DEPRECATED: Prefer using multiple #FrameBuffer with different configurations with
* `GPU_framebuffer_config_array()`.
*/
void GPU_framebuffer_texture_layer_attach(
gpu::FrameBuffer *fb, gpu::Texture *texture, int slot, int layer, int mip);
/**
* Attach a single cube-face of an cube-map texture mip level to a #FrameBuffer.
* Changes will only take effect next time `GPU_framebuffer_bind()` is called.
* \a slot is the color texture slot to bind this texture to. Must be 0 if it is the depth texture.
* \a face is the cube-face of this cube-map texture to attach to the framebuffer.
* \a mip is the mip level of this texture to attach to the framebuffer.
* DEPRECATED: Prefer using multiple #FrameBuffer with different configurations with
* `GPU_framebuffer_config_array()`.
*/
void GPU_framebuffer_texture_cubeface_attach(
gpu::FrameBuffer *fb, gpu::Texture *texture, int slot, int face, int mip);
/**
* Detach a texture from a #FrameBuffer. The texture must be attached.
* Changes will only take effect next time `GPU_framebuffer_bind()` is called.
* DEPRECATED: Prefer using multiple #FrameBuffer with different configurations with
* `GPU_framebuffer_config_array()`.
*/
void GPU_framebuffer_texture_detach(gpu::FrameBuffer *fb, gpu::Texture *texture);
/**
* Checks a framebuffer current configuration for errors.
* Checks for texture size mismatch, incompatible attachment, incomplete textures etc...
* \note This binds the framebuffer to the active context.
* \a err_out is an error output buffer.
* \return false if the framebuffer is invalid.
*/
bool GPU_framebuffer_check_valid(gpu::FrameBuffer *fb, char err_out[256]);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Empty frame-buffer
*
* An empty frame-buffer is a frame-buffer with no attachments. This allow to rasterize geometry
* without creating any dummy attachments and write some computation results using other means
* (SSBOs, Images).
* \{ */
/**
* Default size is used if the frame-buffer contains no attachments.
* It needs to be re-specified each time an attachment is added.
*/
void GPU_framebuffer_default_size(gpu::FrameBuffer *fb, int width, int height);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal state
* \{ */
/**
* \brief Get the extent of the framebuffer
*/
int2 GPU_framebuffer_extent_get(gpu::FrameBuffer *fb);
/**
* Set the viewport offset and size.
* These are reset to the original dimensions explicitly (using `GPU_framebuffer_viewport_reset()`)
* or when binding the frame-buffer after modifying its attachments.
*
* \note Viewport and scissor size is stored per frame-buffer.
* \note Setting a singular viewport will only change the state of the first viewport.
* \note Must be called after first bind.
*/
void GPU_framebuffer_viewport_set(gpu::FrameBuffer *fb, int x, int y, int width, int height);
/**
* Similar to `GPU_framebuffer_viewport_set()` but specify the bounds of all 16 viewports.
* By default geometry renders only to the first viewport. That can be changed by setting
* `gpu_ViewportIndex` in the vertex.
*
* \note Viewport and scissor size is stored per frame-buffer.
* \note Must be called after first bind.
*/
void GPU_framebuffer_multi_viewports_set(gpu::FrameBuffer *gpu_fb,
const int viewport_rects[GPU_MAX_VIEWPORTS][4]);
/**
* Return the viewport offset and size in a int quadruple: (x, y, width, height).
* \note Viewport and scissor size is stored per frame-buffer.
*/
void GPU_framebuffer_viewport_get(gpu::FrameBuffer *fb, int r_viewport[4]);
/**
* Reset a frame-buffer viewport bounds to its attachment(s) size.
* \note Viewport and scissor size is stored per frame-buffer.
*/
void GPU_framebuffer_viewport_reset(gpu::FrameBuffer *fb);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Clearing
* \{ */
/**
* Clear the frame-buffer attachments.
* \a buffers controls the types of attachments to clear. Setting GPU_COLOR_BIT will clear *all*
* the color attachment.
* Each attachment gets cleared to the value of its type:
* - color attachments gets cleared to \a clear_col .
* - depth attachment gets cleared to \a clear_depth .
* - stencil attachment gets cleared to \a clear_stencil .
*
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
*/
void GPU_framebuffer_clear(gpu::FrameBuffer *fb,
GPUFrameBufferBits buffers,
const double4 clear_col,
float clear_depth,
unsigned int clear_stencil);
/**
* Clear all color attachment textures with the value \a clear_col .
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
*/
void GPU_framebuffer_clear_color(gpu::FrameBuffer *fb, const double4 clear_col);
/**
* Clear the depth attachment texture with the value \a clear_depth .
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
*/
void GPU_framebuffer_clear_depth(gpu::FrameBuffer *fb, float clear_depth);
/**
* Clear the stencil attachment with the value \a clear_stencil .
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
*/
void GPU_framebuffer_clear_stencil(gpu::FrameBuffer *fb, uint clear_stencil);
/**
* Clear all color attachment textures with the value \a clear_col and the depth attachment texture
* with the value \a clear_depth .
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
*/
void GPU_framebuffer_clear_color_depth(gpu::FrameBuffer *fb,
const double4 clear_col,
float clear_depth);
/**
* Clear the depth attachment texture with the value \a clear_depth and the stencil attachment with
* the value \a clear_stencil .
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
*/
void GPU_framebuffer_clear_depth_stencil(gpu::FrameBuffer *fb,
float clear_depth,
uint clear_stencil);
/**
* Clear the depth attachment texture with the value \a clear_depth , the stencil attachment with
* the value \a clear_stencil and all the color attachments with the value \a clear_col .
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
*/
void GPU_framebuffer_clear_color_depth_stencil(gpu::FrameBuffer *fb,
const double4 clear_col,
float clear_depth,
uint clear_stencil);
/**
* Clear each color attachment texture attached to this frame-buffer with a different color.
* IMPORTANT: The size of `clear_colors` must contain at least the number of color attachments.
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
*/
void GPU_framebuffer_multi_clear(gpu::FrameBuffer *fb, Span<double4> clear_colors);
/**
* Clear all color attachment textures of the active frame-buffer with the given red, green, blue,
* alpha values.
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
* DEPRECATED: Use `GPU_framebuffer_clear_color` with explicit frame-buffer.
*/
void GPU_clear_color(float red, float green, float blue, float alpha);
/**
* Clear the depth attachment texture of the active frame-buffer with the given depth value.
* \note `GPU_write_mask`, and stencil test do not affect this command.
* \note Viewport and scissor regions affect this command but are not efficient nor recommended.
* DEPRECATED: Use `GPU_framebuffer_clear_color` with explicit frame-buffer.
*/
void GPU_clear_depth(float depth);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging introspection API.
* \{ */
const char *GPU_framebuffer_get_name(gpu::FrameBuffer *fb);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Python API & meta-data
*
* These are not intrinsic properties of a frame-buffer but they are stored inside the
* gpu::FrameBuffer structure for tracking purpose.
* \{ */
/**
* Reference of a pointer that needs to be cleaned when deallocating the frame-buffer.
* Points to #BPyGPUFrameBuffer.fb
*/
#ifndef GPU_NO_USE_PY_REFERENCES
void **GPU_framebuffer_py_reference_get(gpu::FrameBuffer *fb);
void GPU_framebuffer_py_reference_set(gpu::FrameBuffer *fb, void **py_ref);
#endif
/**
* Keep a stack of bound frame-buffer to allow scoped binding of frame-buffer in python.
* This is also used by #GPUOffScreen to save/restore the current frame-buffers.
* \note This isn't thread safe.
*/
/* TODO(fclem): This has nothing to do with the GPU module and should be move to the pyGPU module.
*/
void GPU_framebuffer_push(gpu::FrameBuffer *fb);
gpu::FrameBuffer *GPU_framebuffer_pop();
uint GPU_framebuffer_stack_level_get();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Deprecated
* \{ */
/**
* Return true if \a framebuffer is the active framebuffer of the active context.
* \note return false if no context is active.
* \note this is undefined behavior if \a framebuffer is `nullptr`.
* DEPRECATED: Kept only because of Python GPU API. */
bool GPU_framebuffer_bound(gpu::FrameBuffer *fb);
/**
* Read a region of the framebuffer depth attachment and copy it to \a r_data .
* The pixel data will be converted to \a data_format but it needs to be compatible with the
* attachment type. DEPRECATED: Prefer using `GPU_texture_read()`.
*/
void GPU_framebuffer_read_depth(gpu::FrameBuffer *fb,
int x,
int y,
int width,
int height,
eGPUDataFormat data_format,
void *r_data);
/**
* Read a region of a framebuffer color attachment and copy it to \a r_data .
* The pixel data will be converted to \a data_format but it needs to be compatible with the
* attachment type. DEPRECATED: Prefer using `GPU_texture_read()`.
*/
void GPU_framebuffer_read_color(gpu::FrameBuffer *fb,
int x,
int y,
int width,
int height,
int channels,
int slot,
eGPUDataFormat data_format,
void *r_data);
/**
* Read the color of the window screen as it is currently displayed (so the previously rendered
* back-buffer).
* DEPRECATED: This isn't even working correctly on some implementation.
* TODO: Emulate this by doing some slow texture copy on the backend side or try to read the areas
* offscreen textures directly.
*/
void GPU_frontbuffer_read_color(
int x, int y, int width, int height, int channels, eGPUDataFormat data_format, void *r_data);
/**
* Copy the content of \a fb_read attachments to the \a fb_read attachments.
* The attachments types are chosen by \a blit_buffers .
* Only one color buffer can by copied at a time and its index is chosen by \a read_slot and \a
* write_slot.
* The source and destination frame-buffers dimensions have to match.
* DEPRECATED: Prefer using `GPU_texture_copy()`.
*/
void GPU_framebuffer_blit(gpu::FrameBuffer *fb_read,
int read_slot,
gpu::FrameBuffer *fb_write,
int write_slot,
GPUFrameBufferBits blit_buffers);
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPU OffScreen
*
* A `GPUOffScreen` is a convenience type that holds a `GPUFramebuffer` and its associated
* `gpu::Texture`s. It is useful for quick drawing surface configuration.
* NOTE: They are still limited by the same single context limitation as #FrameBuffer.
* \{ */
struct GPUOffScreen;
/**
* Create a #GPUOffScreen with attachment size of \a width by \a height pixels.
* If \a with_depth_buffer is true, a depth buffer attachment will also be created.
* \a format is the format of the color buffer.
* If \a clear is true, the color and depth buffer attachments will be cleared.
* If \a err_out is not `nullptr` it will be use to write any configuration error message..
* \note This function binds the framebuffer to the active context.
* \note `GPU_TEXTURE_USAGE_ATTACHMENT` is added to the usage parameter by default.
*/
GPUOffScreen *GPU_offscreen_create(int width,
int height,
bool with_depth_buffer,
gpu::TextureFormat format,
eGPUTextureUsage usage,
bool clear,
char err_out[256]);
/**
* Free a #GPUOffScreen.
*/
void GPU_offscreen_free(GPUOffScreen *offscreen);
/**
* Unbind a #GPUOffScreen from a #GPUContext.
* If \a save is true, it will save the currently bound framebuffer into a stack.
*/
/* TODO(fclem): Remove the `save` parameter and always save/restore. */
void GPU_offscreen_bind(GPUOffScreen *offscreen, bool save);
/**
* Unbind a #GPUOffScreen from a #GPUContext.
* If \a restore is true, it will restore the previously bound framebuffer. If false, it will bind
* the window back-buffer.
*/
/* TODO(fclem): Remove the `save` parameter and always save/restore. */
void GPU_offscreen_unbind(GPUOffScreen *offscreen, bool restore);
/**
* Read the whole color texture of the #GPUOffScreen.
* The pixel data will be converted to \a data_format but it needs to be compatible with the
* attachment type.
* IMPORTANT: \a r_data must be big enough for all pixels in \a data_format.
*/
void GPU_offscreen_read_color(GPUOffScreen *offscreen, eGPUDataFormat data_format, void *r_data);
/**
* A version of #GPU_offscreen_read_color that reads into a region.
*/
void GPU_offscreen_read_color_region(
GPUOffScreen *offscreen, eGPUDataFormat data_format, int x, int y, int w, int h, void *r_data);
/**
* Blit the offscreen color texture to the active framebuffer at the `(x, y)` location.
*/
void GPU_offscreen_draw_to_screen(GPUOffScreen *offscreen, int x, int y);
/**
* Return the width of a #GPUOffScreen.
*/
int GPU_offscreen_width(const GPUOffScreen *offscreen);
/**
* Return the height of a #GPUOffScreen.
*/
int GPU_offscreen_height(const GPUOffScreen *offscreen);
/**
* Return the color texture of a #GPUOffScreen. Does not give ownership.
* \note only to be used by viewport code!
*/
gpu::Texture *GPU_offscreen_color_texture(const GPUOffScreen *offscreen);
/**
* Return the texture format of a #GPUOffScreen.
*/
gpu::TextureFormat GPU_offscreen_format(const GPUOffScreen *offscreen);
/**
* Return the internals of a #GPUOffScreen. Does not give ownership.
* \note only to be used by viewport code!
*/
void GPU_offscreen_viewport_data_get(GPUOffScreen *offscreen,
gpu::FrameBuffer **r_fb,
gpu::Texture **r_color,
gpu::Texture **r_depth);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU immediate mode work-alike
*/
#pragma once
#include "GPU_batch.hh"
#include "GPU_immediate_util.hh" /* IWYU pragma: export */
#include "GPU_primitive.hh"
#include "GPU_shader.hh"
#include "GPU_texture.hh"
#include "GPU_vertex_format.hh"
namespace blender {
namespace gpu {
class UniformBuf;
} // namespace gpu
/** Returns a cleared vertex format, ready for #add_attr. */
GPUVertFormat *immVertexFormat();
/** Every #immBegin must have a program bound first. */
void immBindShader(gpu::Shader *shader);
/** Call after your last immEnd, or before binding another program. */
void immUnbindProgram();
/**
* Check if there is a shader bound.
*
* Useful to trigger asserts when immediate mode drawing and
* batch based drawing are mixed. It isn't allowed to have an immediate mode shader bound when a
* batch is drawn.
*
* TODO: We should move these asserts to batch drawing, but didn't do that as it was never forced.
*/
bool immIsShaderBound();
/** Must supply exactly vertex_len vertices. */
void immBegin(GPUPrimType, uint vertex_len);
/** Can supply fewer vertices. */
void immBeginAtMost(GPUPrimType, uint max_vertex_len);
void immEnd(); /* finishes and draws. */
/* - #immBegin a batch, then use standard `imm*` functions as usual.
* - #immEnd will finalize the batch instead of drawing.
*
* Then you can draw it as many times as you like!
* Partially replaces the need for display lists. */
gpu::Batch *immBeginBatch(GPUPrimType, uint vertex_len);
gpu::Batch *immBeginBatchAtMost(GPUPrimType, uint vertex_len);
/* Provide attribute values that can change per vertex. */
/* First vertex after immBegin must have all its attributes specified. */
/* Skipped attributes will continue using the previous value for that attr_id. */
void immAttr1f(uint attr_id, float x);
void immAttr2f(uint attr_id, float x, float y);
void immAttr3f(uint attr_id, float x, float y, float z);
void immAttr4f(uint attr_id, float x, float y, float z, float w);
void immAttr2i(uint attr_id, int x, int y);
void immAttr1u(uint attr_id, uint x);
void immAttr2fv(uint attr_id, const float data[2]);
void immAttr3fv(uint attr_id, const float data[3]);
void immAttr4fv(uint attr_id, const float data[4]);
void immAttr4ub(uint attr_id, unsigned char r, unsigned char g, unsigned char b, unsigned char a);
void immAttr4ubv(uint attr_id, const unsigned char data[4]);
/* Explicitly skip an attribute.
* This advanced option kills automatic value copying for this attr_id. */
void immAttrSkip(uint attr_id);
/* Provide one last attribute value & end the current vertex.
* This is most often used for 2D or 3D position (similar to #glVertex). */
void immVertex2f(uint attr_id, float x, float y);
void immVertex3f(uint attr_id, float x, float y, float z);
void immVertex4f(uint attr_id, float x, float y, float z, float w);
void immVertex2i(uint attr_id, int x, int y);
void immVertex2fv(uint attr_id, const float data[2]);
void immVertex3fv(uint attr_id, const float data[3]);
void immVertex2iv(uint attr_id, const int data[2]);
/* Provide uniform values that don't change for the entire draw call. */
void immUniform1i(const char *name, int x);
void immUniform1f(const char *name, float x);
void immUniform2f(const char *name, float x, float y);
void immUniform2fv(const char *name, const float data[2]);
void immUniform3f(const char *name, float x, float y, float z);
void immUniform3fv(const char *name, const float data[3]);
void immUniform4f(const char *name, float x, float y, float z, float w);
void immUniform4fv(const char *name, const float data[4]);
/**
* Note array index is not supported for name (i.e: "array[0]").
*/
void immUniformArray4fv(const char *bare_name, const float *data, int count);
void immUniformMatrix4fv(const char *name, const float data[4][4]);
void immBindTexture(const char *name, gpu::Texture *tex);
void immBindTextureSampler(const char *name, gpu::Texture *tex, GPUSamplerState state);
void immBindUniformBuf(const char *name, gpu::UniformBuf *ubo);
/* Convenience functions for setting "uniform vec4 color". */
/* The RGB functions have implicit alpha = 1.0. */
void immUniformColor4f(float r, float g, float b, float a);
void immUniformColor4fv(const float rgba[4]);
void immUniformColor3f(float r, float g, float b);
void immUniformColor3fv(const float rgb[3]);
void immUniformColor3fvAlpha(const float rgb[3], float a);
void immUniformColor3ub(unsigned char r, unsigned char g, unsigned char b);
void immUniformColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
void immUniformColor3ubv(const unsigned char rgb[3]);
void immUniformColor3ubvAlpha(const unsigned char rgb[3], unsigned char a);
void immUniformColor4ubv(const unsigned char rgba[4]);
/**
* Extend #immBindShader to use Blender's library of built-in shader programs.
* Use #immUnbindProgram() when done.
*/
void immBindBuiltinProgram(GPUBuiltinShader shader_id);
/** Extend #immUniformColor to take Blender's themes. */
void immUniformThemeColor(int color_id);
void immUniformThemeColorAlpha(int color_id, float a);
void immUniformThemeColor3(int color_id);
void immUniformThemeColorShade(int color_id, int offset);
void immUniformThemeColorShadeAlpha(int color_id, int color_offset, int alpha_offset);
void immUniformThemeColorBlendShade(int color_id1, int color_id2, float fac, int offset);
void immUniformThemeColorBlend(int color_id1, int color_id2, float fac);
void immThemeColorShadeAlpha(int colorid, int coloffset, int alphaoffset);
} // namespace blender

View File

@@ -0,0 +1,180 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Utility drawing functions (rough equivalent to OpenGL's GLU)
*/
#pragma once
#include "BLI_sys_types.h"
namespace blender {
struct rctf;
/* Draw 2D rectangles (replaces glRect functions) */
/* caller is responsible for vertex format & shader */
void immRectf(uint pos, float x1, float y1, float x2, float y2);
void immRecti(uint pos, int x1, int y1, int x2, int y2);
void immRectf_with_texco(uint pos, uint tex_coord, const rctf &p, const rctf &uv);
/**
* Same as #immRectf / #immRecti but does not call #immBegin / #immEnd.
* To use with #GPU_PRIM_TRIS.
*/
void immRectf_fast(uint pos, float x1, float y1, float x2, float y2);
void immRectf_fast_with_color(
uint pos, uint col, float x1, float y1, float x2, float y2, const float color[4]);
void immRecti_fast_with_color(
uint pos, uint col, int x1, int y1, int x2, int y2, const float color[4]);
/**
* Pack color into 3 bytes
*
* This define converts a numerical value to the equivalent 24-bit
* color, while not being endian-sensitive. On little-endian, this
* is the same as doing a 'naive' indexing, on big-endian, it is not!
*
* \note BGR format (i.e. 0xBBGGRR)...
*
* \param x: color.
*/
void imm_cpack(uint x);
/**
* Draw a circle outline with the given \a radius.
* The circle is centered at \a x, \a y and drawn in the XY plane.
*
* \param shdr_pos: The vertex attribute number for position.
* \param x: Horizontal center.
* \param y: Vertical center.
* \param radius: The circle's radius.
* \param nsegments: The number of segments to use in drawing (more = smoother).
*/
void imm_draw_circle_wire_2d(uint shdr_pos, float x, float y, float radius, int nsegments);
/**
* Draw a filled circle with the given \a radius.
* The circle is centered at \a x, \a y and drawn in the XY plane.
*
* \param shdr_pos: The vertex attribute number for position.
* \param x: Horizontal center.
* \param y: Vertical center.
* \param radius: The circle's radius.
* \param nsegments: The number of segments to use in drawing (more = smoother).
*/
void imm_draw_circle_fill_2d(uint shdr_pos, float x, float y, float radius, int nsegments);
void imm_draw_circle_wire_aspect_2d(
uint shdr_pos, float x, float y, float radius_x, float radius_y, int nsegments);
void imm_draw_circle_fill_aspect_2d(
uint shdr_pos, float x, float y, float radius_x, float radius_y, int nsegments);
/**
* Use this version when #GPUVertFormat has a vec3 position.
*/
void imm_draw_circle_wire_3d(uint pos, float x, float y, float radius, int nsegments);
void imm_draw_circle_wire_aspect_3d(
uint pos, float x, float y, float radius_x, float radius_y, int nsegments);
void imm_draw_circle_dashed_3d(uint pos, float x, float y, float radius, int nsegments);
void imm_draw_circle_fill_3d(uint pos, float x, float y, float radius, int nsegments);
void imm_draw_circle_fill_aspect_3d(
uint pos, float x, float y, float radius_x, float radius_y, int nsegments);
/**
* Same as 'imm_draw_disk_partial_fill_2d', except it draws a wire arc.
*/
void imm_draw_circle_partial_wire_2d(
uint pos, float x, float y, float radius, int nsegments, float start, float sweep);
void imm_draw_circle_partial_wire_3d(
uint pos, float x, float y, float z, float radius, int nsegments, float start, float sweep);
/**
* Draw a filled arc with the given inner and outer radius.
* The circle is centered at \a x, \a y and drawn in the XY plane.
*
* \note Arguments are `gluPartialDisk` compatible.
*
* \param pos: The vertex attribute number for position.
* \param x: Horizontal center.
* \param y: Vertical center.
* \param rad_inner: The inner circle's radius.
* \param rad_outer: The outer circle's radius (can be zero).
* \param nsegments: The number of segments to use in drawing (more = smoother).
* \param start: Specifies the starting angle, in degrees, of the disk portion.
* \param sweep: Specifies the sweep angle, in degrees, of the disk portion.
*/
void imm_draw_disk_partial_fill_2d(uint pos,
float x,
float y,
float rad_inner,
float rad_outer,
int nsegments,
float start,
float sweep);
void imm_draw_disk_partial_fill_3d(uint pos,
float x,
float y,
float z,
float rad_inner,
float rad_outer,
int nsegments,
float start,
float sweep);
/**
* Draw a lined box.
*
* \param pos: The vertex attribute number for position.
* \param x1: left.
* \param y1: bottom.
* \param x2: right.
* \param y2: top.
*/
void imm_draw_box_wire_2d(uint pos, float x1, float y1, float x2, float y2);
void imm_draw_box_wire_3d(uint pos, float x1, float y1, float x2, float y2);
/**
* Draw a standard checkerboard to indicate transparent backgrounds.
*/
void imm_draw_box_checker_2d_ex(float x1,
float y1,
float x2,
float y2,
const float color_primary[4],
const float color_secondary[4],
int checker_size);
void imm_draw_box_checker_2d(float x1, float y1, float x2, float y2, bool clear_alpha = false);
void imm_draw_cube_fill_3d(uint pos, const float center[3], const float aspect[3]);
void imm_draw_cube_wire_3d(uint pos, const float center[3], const float aspect[3]);
void imm_draw_cube_corners_3d(uint pos,
const float center[3],
const float aspect[3],
float factor);
/**
* Draw a cylinder. Replacement for #gluCylinder.
* \warning Slow, better use it only if you no other choices.
*
* \param pos: The vertex attribute number for position.
* \param nor: The vertex attribute number for normal.
* \param base: Specifies the radius of the cylinder at z = 0.
* \param top: Specifies the radius of the cylinder at z = height.
* \param height: Specifies the height of the cylinder.
* \param slices: Specifies the number of subdivisions around the z axis.
* \param stacks: Specifies the number of subdivisions along the z axis.
*/
void imm_draw_cylinder_fill_normal_3d(
uint pos, uint nor, float base, float top, float height, int slices, int stacks);
void imm_draw_cylinder_wire_3d(
uint pos, float base, float top, float height, int slices, int stacks);
void imm_draw_cylinder_fill_3d(
uint pos, float base, float top, float height, int slices, int stacks);
void imm_drawcircball(const float cent[3], float radius, const float tmat[4][4], uint pos);
} // namespace blender

View File

@@ -0,0 +1,291 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU index buffer
*/
#pragma once
#include "BLI_span.hh"
#include "GPU_primitive.hh"
namespace blender {
#define GPU_TRACK_INDEX_RANGE 1
namespace gpu {
/** Value for invisible elements in a #GPU_PRIM_POINTS index buffer. */
constexpr uint32_t RESTART_INDEX = 0xFFFFFFFF;
enum GPUIndexBufType {
GPU_INDEX_U16,
GPU_INDEX_U32,
};
inline size_t to_bytesize(GPUIndexBufType type)
{
return (type == GPU_INDEX_U32) ? sizeof(uint32_t) : sizeof(uint16_t);
}
/**
* Base class which is then specialized for each implementation (GL, VK, ...).
*
* \note #IndexBuf does not hold any #GPUPrimType.
* This is because it can be interpreted differently by multiple batches.
*/
class IndexBuf {
protected:
/** Type of indices used inside this buffer. */
GPUIndexBufType index_type_ = GPU_INDEX_U32;
/** Offset in this buffer to the first index to render. Is 0 if not a subrange. */
uint32_t index_start_ = 0;
/** Number of indices to render. */
uint32_t index_len_ = 0;
/** Base index: Added to all indices after fetching. Allows index compression. */
uint32_t index_base_ = 0;
/** Bookkeeping. */
bool is_init_ = false;
/** Is this object only a reference to a subrange of another IndexBuf. */
bool is_subrange_ = false;
/** True if buffer only contains restart indices. */
bool is_empty_ = false;
union {
/** Mapped buffer data. non-NULL indicates not yet sent to VRAM. */
void *data_ = nullptr;
/** If is_subrange is true, this is the source index buffer. */
IndexBuf *src_;
};
public:
IndexBuf() {};
virtual ~IndexBuf();
void init(uint indices_len,
uint32_t *indices,
uint min_index,
uint max_index,
GPUPrimType prim_type,
bool uses_restart_indices);
void init_subrange(IndexBuf *elem_src, uint start, uint length);
void init_build_on_device(uint index_len);
/* Returns render index count (not precise). */
uint32_t index_len_get() const
{
/* Return 0 to bypass drawing for index buffers full of restart indices.
* They can lead to graphical glitches on some systems. (See #96892) */
return is_empty_ ? 0 : index_len_;
}
uint32_t index_start_get() const
{
return index_start_;
}
uint32_t index_base_get() const
{
return index_base_;
}
bool is_32bit() const
{
return index_type_ == GPU_INDEX_U32;
}
/* Return size in byte of the drawable data buffer range. Actual buffer size might be bigger. */
size_t size_get() const
{
return index_len_ * to_bytesize(index_type_);
};
bool is_init() const
{
return is_init_;
};
virtual void upload_data() = 0;
virtual void bind_as_ssbo(uint binding) = 0;
virtual void read(uint32_t *data) const = 0;
virtual void update_sub(uint start, uint len, const void *data) = 0;
private:
inline void squeeze_indices_short(uint min_idx,
uint max_idx,
GPUPrimType prim_type,
bool clamp_indices_in_range);
virtual void strip_restart_indices() = 0;
};
inline int indices_per_primitive(GPUPrimType prim_type)
{
switch (prim_type) {
case GPU_PRIM_POINTS:
return 1;
case GPU_PRIM_LINES:
return 2;
case GPU_PRIM_TRIS:
return 3;
case GPU_PRIM_LINES_ADJ:
return 4;
case GPU_PRIM_TRIS_ADJ:
return 6;
/** IMPORTANT: These last two expects no restart primitive.
* Asserting for this would be too slow. Just don't be stupid.
* This is needed for polylines but should be deprecated.
* See GPU_batch_draw_expanded_parameter_get */
case GPU_PRIM_LINE_STRIP:
return 1; /* Minus one for the whole length. */
case GPU_PRIM_LINE_LOOP:
return 1;
default:
return -1;
}
}
} // namespace gpu
gpu::IndexBuf *GPU_indexbuf_calloc();
struct GPUIndexBufBuilder {
uint max_allowed_index;
uint max_index_len;
uint index_len;
uint index_min;
uint index_max;
uint restart_index_value;
bool uses_restart_indices;
GPUPrimType prim_type;
uint32_t *data;
};
/** Supports all primitive types. */
void GPU_indexbuf_init_ex(GPUIndexBufBuilder *, GPUPrimType, uint index_len, uint vertex_len);
/** Supports only #GPU_PRIM_POINTS, #GPU_PRIM_LINES and #GPU_PRIM_TRIS. */
void GPU_indexbuf_init(GPUIndexBufBuilder *, GPUPrimType, uint prim_len, uint vertex_len);
gpu::IndexBuf *GPU_indexbuf_build_on_device(uint index_len);
void GPU_indexbuf_init_build_on_device(gpu::IndexBuf *elem, uint index_len);
MutableSpan<uint32_t> GPU_indexbuf_get_data(GPUIndexBufBuilder *);
/*
* Thread safe.
*
* Function inspired by the reduction directives of multi-thread work API's.
*/
void GPU_indexbuf_join(GPUIndexBufBuilder *builder, const GPUIndexBufBuilder *builder_from);
void GPU_indexbuf_add_generic_vert(GPUIndexBufBuilder *, uint v);
void GPU_indexbuf_add_primitive_restart(GPUIndexBufBuilder *);
void GPU_indexbuf_add_point_vert(GPUIndexBufBuilder *, uint v);
void GPU_indexbuf_add_line_verts(GPUIndexBufBuilder *, uint v1, uint v2);
void GPU_indexbuf_add_tri_verts(GPUIndexBufBuilder *, uint v1, uint v2, uint v3);
void GPU_indexbuf_add_line_adj_verts(GPUIndexBufBuilder *, uint v1, uint v2, uint v3, uint v4);
void GPU_indexbuf_set_point_vert(GPUIndexBufBuilder *builder, uint elem, uint v1);
void GPU_indexbuf_set_line_verts(GPUIndexBufBuilder *builder, uint elem, uint v1, uint v2);
void GPU_indexbuf_set_tri_verts(GPUIndexBufBuilder *builder, uint elem, uint v1, uint v2, uint v3);
/* Skip primitive rendering at the given index. */
void GPU_indexbuf_set_point_restart(GPUIndexBufBuilder *builder, uint elem);
void GPU_indexbuf_set_line_restart(GPUIndexBufBuilder *builder, uint elem);
void GPU_indexbuf_set_tri_restart(GPUIndexBufBuilder *builder, uint elem);
gpu::IndexBuf *GPU_indexbuf_build(GPUIndexBufBuilder *);
gpu::IndexBuf *GPU_indexbuf_build_ex(GPUIndexBufBuilder *builder,
uint index_min,
uint index_max,
bool uses_restart_indices);
void GPU_indexbuf_build_in_place(GPUIndexBufBuilder *, gpu::IndexBuf *);
void GPU_indexbuf_build_in_place_ex(GPUIndexBufBuilder *builder,
uint index_min,
uint index_max,
bool uses_restart_indices,
gpu::IndexBuf *elem);
/**
* Fill an IBO by uploading the referenced data directly to the GPU, bypassing the separate storage
* in the IBO. This should be used whenever the equivalent indices already exist in a contiguous
* array on the CPU.
*
* \todo The optimization to avoid the local copy currently isn't implemented.
*/
gpu::IndexBuf *GPU_indexbuf_build_from_memory(GPUPrimType prim_type,
const uint32_t *data,
int32_t data_len,
int32_t index_min,
int32_t index_max,
bool uses_restart_indices);
/**
* \note Sub-ranges are not taken into account, the whole buffer will be bound without any offset.
*/
void GPU_indexbuf_bind_as_ssbo(gpu::IndexBuf *elem, int binding);
gpu::IndexBuf *GPU_indexbuf_build_curves_on_device(GPUPrimType prim_type,
uint curves_num,
uint verts_per_curve);
/* Upload data to the GPU (if not built on the device) and bind the buffer to its default target.
*/
void GPU_indexbuf_use(gpu::IndexBuf *elem);
/* Partially update the gpu::IndexBuf which was already sent to the device, or built
* directly on the device. The data needs to be compatible with potential compression applied to
* the original indices when the index buffer was built, i.e., if the data was compressed to use
* shorts instead of ints, shorts should passed here. */
void GPU_indexbuf_update_sub(gpu::IndexBuf *elem, uint start, uint len, const void *data);
/* Create a sub-range of an existing index-buffer. */
gpu::IndexBuf *GPU_indexbuf_create_subrange(gpu::IndexBuf *elem_src, uint start, uint length);
void GPU_indexbuf_create_subrange_in_place(gpu::IndexBuf *elem,
gpu::IndexBuf *elem_src,
uint start,
uint length);
/**
* (Download and) fill data with the contents of the index buffer.
*
* NOTE: caller is responsible to reserve enough memory.
*/
void GPU_indexbuf_read(gpu::IndexBuf *elem, uint32_t *data);
void GPU_indexbuf_discard(gpu::IndexBuf *elem);
bool GPU_indexbuf_is_init(gpu::IndexBuf *elem);
int GPU_indexbuf_primitive_len(GPUPrimType prim_type);
/* Macros */
#define GPU_INDEXBUF_DISCARD_SAFE(elem) \
do { \
if (elem != nullptr) { \
GPU_indexbuf_discard(elem); \
elem = nullptr; \
} \
} while (0)
namespace gpu {
class IndexBufDeleter {
public:
void operator()(IndexBuf *ibo)
{
GPU_indexbuf_discard(ibo);
}
};
using IndexBufPtr = std::unique_ptr<IndexBuf, IndexBufDeleter>;
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
namespace blender {
void GPU_init();
void GPU_exit();
bool GPU_is_init();
} // namespace blender

View File

@@ -0,0 +1,490 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include <string>
#include "BLI_enum_flags.hh"
#include "BLI_math_base.h"
#include "BLI_set.hh"
#include "DNA_customdata_types.h" /* for eCustomDataType */
#include "DNA_image_types.h"
#include "DNA_listBase.h"
#include "GPU_shader.hh" /* for GPUShaderCreateInfo */
#include "GPU_texture.hh" /* for GPUSamplerState */
namespace blender {
struct GHash;
struct GPUMaterial;
struct GPUInput;
struct GPUNodeLink;
struct GPUNodeStack;
struct GPUPass;
namespace gpu {
class Texture;
class UniformBuf;
} // namespace gpu
struct Image;
struct ImageUser;
struct Main;
struct Material;
struct Scene;
struct bNode;
struct bNodeTree;
/**
* High level functions to create and use GPU materials.
*/
enum eGPUMaterialEngine {
GPU_MAT_EEVEE,
GPU_MAT_COMPOSITOR,
GPU_MAT_ENGINE_MAX,
};
enum GPUMaterialStatus {
GPU_MAT_FAILED = 0,
GPU_MAT_QUEUED,
GPU_MAT_SUCCESS,
};
/* GPU_MAT_OPTIMIZATION_SKIP for cases where we do not
* plan to perform optimization on a given material. */
enum eGPUMaterialOptimizationStatus {
GPU_MAT_OPTIMIZATION_SKIP = 0,
GPU_MAT_OPTIMIZATION_QUEUED,
GPU_MAT_OPTIMIZATION_SUCCESS,
};
enum eGPUMaterialFlag {
GPU_MATFLAG_DIFFUSE = (1 << 0),
GPU_MATFLAG_SUBSURFACE = (1 << 1),
GPU_MATFLAG_GLOSSY = (1 << 2),
GPU_MATFLAG_REFRACT = (1 << 3),
GPU_MATFLAG_EMISSION = (1 << 4),
GPU_MATFLAG_TRANSPARENT = (1 << 5),
GPU_MATFLAG_HOLDOUT = (1 << 6),
GPU_MATFLAG_SHADER_TO_RGBA = (1 << 7),
GPU_MATFLAG_AO = (1 << 8),
/* Signals the presence of multiple reflection closures. */
GPU_MATFLAG_COAT = (1 << 9),
GPU_MATFLAG_TRANSLUCENT = (1 << 10),
GPU_MATFLAG_RAYCAST = (1 << 11),
GPU_MATFLAG_VOLUME_SCATTER = (1 << 16),
GPU_MATFLAG_VOLUME_ABSORPTION = (1 << 17),
GPU_MATFLAG_OBJECT_INFO = (1 << 18),
GPU_MATFLAG_AOV = (1 << 19),
GPU_MATFLAG_BARYCENTRIC = (1 << 20),
/* Signals that these specific closures might *not* be colorless.
* If this flag is not set, all closures are ensured to not be tinted. */
GPU_MATFLAG_REFLECTION_MAYBE_COLORED = (1 << 21),
GPU_MATFLAG_REFRACTION_MAYBE_COLORED = (1 << 22),
GPU_MATFLAG_TRANSPARENT_MAYBE_COLORED = (1 << 23),
/* Set if the material uses the "Is Diffuse / Glossy Ray" output of the light path node. */
GPU_MATFLAG_IS_DIFFUSE_OR_GLOSSY_RAY_FLAG = (1 << 24),
/* Signals scene time use. */
GPU_MATFLAG_SCENE_TIME = (1 << 25),
/* Tells the render engine the material was just compiled or updated. */
GPU_MATFLAG_UPDATED = (1 << 29),
};
ENUM_OPERATORS(eGPUMaterialFlag);
using GPUCodegenCallbackFn = void (*)(void *thunk,
GPUMaterial *mat,
struct GPUCodegenOutput *codegen);
/**
* Should return an already compiled pass if it's functionally equivalent to the one being
* compiled.
*/
using GPUMaterialPassReplacementCallbackFn = GPUPass *(*)(void *thunk, GPUMaterial *mat);
struct GPUMaterialFromNodeTreeResult {
GPUMaterial *material = nullptr;
struct Error {
const bNode *node;
std::string message;
};
Vector<Error> errors;
};
/** WARNING: gpumaterials thread safety must be ensured by the caller. */
GPUMaterialFromNodeTreeResult GPU_material_from_nodetree(
Material *ma,
bNodeTree *ntree,
ListBaseT<LinkData> *gpumaterials,
const char *name,
eGPUMaterialEngine engine,
uint64_t shader_uuid,
bool deferred_compilation,
GPUCodegenCallbackFn callback,
void *thunk,
GPUMaterialPassReplacementCallbackFn pass_replacement_cb = nullptr);
/* A callback passed to GPU_material_from_callbacks to construct the material graph by adding and
* linking the necessary GPU material nodes. */
using ConstructGPUMaterialFn = void (*)(void *thunk, GPUMaterial *material);
/* Construct a GPU material from a set of callbacks. See the callback types for more information.
* The given thunk will be passed as the first parameter of each callback. */
GPUMaterial *GPU_material_from_callbacks(eGPUMaterialEngine engine,
ConstructGPUMaterialFn construct_function_cb,
GPUCodegenCallbackFn generate_code_function_cb,
void *thunk);
void GPU_material_free_single(GPUMaterial *material);
void GPU_material_free(ListBaseT<LinkData> *gpumaterial);
void GPU_materials_free(Main *bmain);
GPUPass *GPU_material_get_pass(GPUMaterial *material);
/** Return the most optimal shader configuration for the given material. */
gpu::Shader *GPU_material_get_shader(GPUMaterial *material);
const char *GPU_material_get_name(GPUMaterial *material);
/**
* Return can be null if it's a world material.
*/
Material *GPU_material_get_material(GPUMaterial *material);
/**
* Return true if the material compilation has not yet begin or begin.
*/
GPUMaterialStatus GPU_material_status(GPUMaterial *mat);
/**
* Return status for asynchronous optimization jobs.
*/
eGPUMaterialOptimizationStatus GPU_material_optimization_status(GPUMaterial *mat);
uint64_t GPU_material_compilation_timestamp(GPUMaterial *mat);
gpu::UniformBuf *GPU_material_uniform_buffer_get(GPUMaterial *material);
/**
* Create dynamic UBO from parameters
*
* \param inputs: Items are #LinkData, data is #GPUInput (`BLI_genericNodeN(GPUInput)`).
*/
void GPU_material_uniform_buffer_create(GPUMaterial *material, ListBaseT<LinkData> *inputs);
bool GPU_material_has_surface_output(GPUMaterial *mat);
bool GPU_material_has_volume_output(GPUMaterial *mat);
bool GPU_material_has_displacement_output(GPUMaterial *mat);
bool GPU_material_flag_get(const GPUMaterial *mat, eGPUMaterialFlag flag);
uint64_t GPU_material_uuid_get(GPUMaterial *mat);
struct GPULayerAttr {
GPULayerAttr *next, *prev;
/* Meaningful part of the attribute set key. */
char name[256]; /* Multiple MAX_CUSTOMDATA_LAYER_NAME */
/** Hash of `name[68]`. */
uint32_t hash_code;
/* Helper fields used by code generation. */
int users;
};
const ListBaseT<GPULayerAttr> *GPU_material_layer_attributes(const GPUMaterial *material);
/* Requested Material Attributes and Textures */
enum GPUType {
/* Keep in sync with GPU_DATATYPE_STR */
/* The value indicates the number of elements in each type */
GPU_NONE = 0,
GPU_FLOAT = 1,
GPU_VEC2 = 2,
GPU_VEC3 = 3,
GPU_VEC4 = 4,
GPU_MAT3 = 9,
GPU_MAT4 = 16,
GPU_MAX_CONSTANT_DATA = GPU_MAT4,
/* Values not in GPU_DATATYPE_STR */
GPU_TEX1D_ARRAY = 1001,
GPU_TEX2D = 1002,
GPU_TEX2D_ARRAY = 1003,
GPU_TEX3D = 1004,
/* GLSL Struct types */
GPU_CLOSURE = 1007,
/* Opengl Attributes */
GPU_ATTR = 3001,
};
enum GPUDefaultValue {
GPU_DEFAULT_0 = 0,
GPU_DEFAULT_1,
};
struct GPUMaterialAttribute {
GPUMaterialAttribute *next, *prev;
int type; /* eCustomDataType */
char name[/*MAX_CUSTOMDATA_LAYER_NAME*/ 68];
char input_name[/*GPU_MAX_SAFE_ATTR_NAME + 1*/ 12 + 1];
GPUType gputype;
GPUDefaultValue default_value; /* Only for volumes attributes. */
int id;
int users;
/**
* If true, the corresponding attribute is the specified default color attribute on the mesh,
* if it exists. In that case the type and name data can vary per geometry, so it will not be
* valid here.
*/
bool is_default_color;
/**
* If true, the attribute is the length of hair particles and curves.
*/
bool is_hair_length;
/**
* If true, the attribute is the intercept of hair particles and curves.
*/
bool is_hair_intercept;
};
struct GPUMaterialTexture {
GPUMaterialTexture *next, *prev;
Image *ima;
ImageUser iuser;
bool iuser_available;
gpu::Texture **colorband;
gpu::Texture **sky;
char sampler_name[32]; /* Name of sampler in GLSL. */
char tiled_mapping_name[32]; /* Name of tile mapping sampler in GLSL. */
int users;
GPUSamplerState sampler_state;
};
ListBaseT<GPUMaterialAttribute> GPU_material_attributes(const GPUMaterial *material);
ListBaseT<GPUMaterialTexture> GPU_material_textures(GPUMaterial *material);
struct GPUUniformAttr {
GPUUniformAttr *next, *prev;
/* Meaningful part of the attribute set key. */
char name[/*MAX_CUSTOMDATA_LAYER_NAME*/ 68];
/** Hash of `name[MAX_CUSTOMDATA_LAYER_NAME] + use_dupli`. */
uint32_t hash_code;
bool use_dupli;
/* Helper fields used by code generation. */
short id;
int users;
};
struct GPUUniformAttrList {
ListBaseT<GPUUniformAttr> list;
/* List length and hash code precomputed for fast lookup and comparison. */
unsigned int count, hash_code;
};
const GPUUniformAttrList *GPU_material_uniform_attributes(const GPUMaterial *material);
/* Functions to create GPU Materials nodes. */
/* TODO: Move to its own header. */
struct GPUNodeStack {
GPUType type;
float vec[4];
GPUNodeLink *link;
bool hasinput;
bool hasoutput;
short sockettype;
bool end;
/* Return true if the socket might contain a polychromatic value.
* This is a conservative heuristic that allows for optimization. */
bool might_be_tinted() const
{
return this->link || (this->vec[0] != this->vec[1]) || (this->vec[1] != this->vec[2]);
}
bool socket_not_zero() const
{
return this->link || (saturate_f(this->vec[0]) > near_zero);
}
bool socket_not_one() const
{
return this->link || (saturate_f(this->vec[0]) < near_one);
}
bool socket_not_black() const
{
return this->link || saturate_f(this->vec[0]) > near_zero ||
saturate_f(this->vec[1]) > near_zero || saturate_f(this->vec[2]) > near_zero;
}
bool socket_not_white() const
{
return this->link || saturate_f(this->vec[0]) < near_one ||
saturate_f(this->vec[1]) < near_one || saturate_f(this->vec[2]) < near_one;
}
private:
static constexpr float near_zero = 1e-5f;
static constexpr float near_one = 1.0f - 1e-5f;
float saturate_f(const float f) const
{
return clamp_f(f, 0.0f, 1.0f);
}
};
struct GPUGraphOutput {
std::string serialized;
Vector<StringRefNull> dependencies;
bool empty() const
{
return serialized.empty();
}
std::string serialized_or_default(std::string value) const
{
return serialized.empty() ? value : serialized;
}
};
struct GPUCodegenOutput {
std::string attr_load;
/* Node-tree functions calls. */
GPUGraphOutput displacement;
GPUGraphOutput surface;
GPUGraphOutput volume;
GPUGraphOutput thickness;
GPUGraphOutput composite;
Vector<GPUGraphOutput> material_functions;
GPUShaderCreateInfo *create_info;
};
GPUNodeLink *GPU_constant(const float *num);
GPUNodeLink *GPU_uniform(const float *num);
GPUNodeLink *GPU_attribute(GPUMaterial *mat, eCustomDataType type, const char *name);
/**
* Add a GPU attribute that refers to the default color attribute on a geometry.
* The name, type, and domain are unknown and do not depend on the material.
*/
GPUNodeLink *GPU_attribute_default_color(GPUMaterial *mat);
/**
* Add a GPU attribute that refers to the approximate length of curves/hairs.
*/
GPUNodeLink *GPU_attribute_hair_length(GPUMaterial *mat);
GPUNodeLink *GPU_attribute_hair_intercept(GPUMaterial *mat);
GPUNodeLink *GPU_attribute_with_default(GPUMaterial *mat,
eCustomDataType type,
const char *name,
GPUDefaultValue default_value);
GPUNodeLink *GPU_uniform_attribute(GPUMaterial *mat,
const char *name,
bool use_dupli,
uint32_t *r_hash);
GPUNodeLink *GPU_layer_attribute(GPUMaterial *mat, const char *name);
GPUNodeLink *GPU_image(GPUMaterial *mat,
Image *ima,
ImageUser *iuser,
GPUSamplerState sampler_state);
void GPU_image_tiled(GPUMaterial *mat,
Image *ima,
ImageUser *iuser,
GPUSamplerState sampler_state,
GPUNodeLink **r_image_tiled_link,
GPUNodeLink **r_image_tiled_mapping_link);
GPUNodeLink *GPU_image_sky(GPUMaterial *mat,
int width,
int height,
const float *pixels,
float *layer,
GPUSamplerState sampler_state);
GPUNodeLink *GPU_color_band(GPUMaterial *mat, int size, float *pixels, float *r_row);
/**
* Create an implementation defined differential calculation of a float function.
* The given function should return a float.
* The result will be a vec2 containing dFdx and dFdy result of that function.
*/
GPUNodeLink *GPU_differentiate_float_function(const char *function_name, const float filter_width);
bool GPU_link(GPUMaterial *mat, const char *name, ...);
bool GPU_stack_link(GPUMaterial *mat,
const bNode *node,
const char *name,
GPUNodeStack *in,
GPUNodeStack *out,
...);
bool GPU_stack_link_zone(GPUMaterial *material,
const bNode *bnode,
const char *name,
GPUNodeStack *in,
GPUNodeStack *out,
int zone_index,
bool is_zone_end,
int in_argument_count,
int out_argument_count);
void GPU_material_output_surface(GPUMaterial *material, GPUNodeLink *link);
void GPU_material_output_volume(GPUMaterial *material, GPUNodeLink *link);
void GPU_material_output_displacement(GPUMaterial *material, GPUNodeLink *link);
void GPU_material_output_thickness(GPUMaterial *material, GPUNodeLink *link);
void GPU_material_add_output_link_aov(GPUMaterial *material, GPUNodeLink *link, int hash);
void GPU_material_add_output_link_composite(GPUMaterial *material, GPUNodeLink *link);
/**
* Wrap a part of the material graph into a function. You need then need to call the function by
* using something like #GPU_differentiate_float_function.
* \note This replace the link by a constant to break the link with the main graph.
* \param return_type: sub function return type. Output is cast to this type.
* \param link: link to use as the sub function output.
* \return the name of the generated function.
*/
char *GPU_material_split_sub_function(GPUMaterial *material,
GPUType return_type,
GPUNodeLink **link);
void GPU_material_flag_set(GPUMaterial *mat, eGPUMaterialFlag flag);
eGPUMaterialFlag GPU_material_flag(const GPUMaterial *mat);
GHash *GPU_uniform_attr_list_hash_new(const char *info);
void GPU_uniform_attr_list_copy(GPUUniformAttrList *dest, const GPUUniformAttrList *src);
void GPU_uniform_attr_list_free(GPUUniformAttrList *set);
/* Returns the GPU node stack of the input with the given identifier in the given node within the
* given inputs stack array. */
GPUNodeStack &GPU_node_get_input(const bNode &node, GPUNodeStack inputs[], StringRef identifier);
/* Returns the GPU node stack of the output with the given identifier in the given node within the
* given output stack array. */
GPUNodeStack &GPU_node_get_output(const bNode &node, GPUNodeStack outputs[], StringRef identifier);
/* Returns the GPU node link of the input with the given identifier in the given node within the
* given inputs stack array, if the input is not linked, a uniform link carrying the value of the
* input will be created and returned. It is expected that the caller will use the returned link in
* a GPU material, otherwise, the link may not be properly freed. */
GPUNodeLink *GPU_node_get_input_link(const bNode &node,
GPUNodeStack inputs[],
StringRef identifier);
} // namespace blender

View File

@@ -0,0 +1,231 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
namespace blender {
namespace gpu {
class Shader;
} // namespace gpu
/**
* To Identity transform & empty stack.
*/
void GPU_matrix_reset();
/* ModelView Matrix (2D or 3D) */
/**
* Push a new matrix unto the stack (copying the previous matrix).
*
* \note A function `GPU_matrix_push_identity` could be added
* instead of calling #GPU_matrix_identity_set after pushing.
*/
void GPU_matrix_push();
void GPU_matrix_pop();
/**
* Set the model-view to an identity matrix.
*/
void GPU_matrix_identity_set();
/**
* Scale the model-view matrix.
*/
void GPU_matrix_scale_1f(float factor);
/* 3D ModelView Matrix */
void GPU_matrix_set(const float m[4][4]);
void GPU_matrix_mul(const float m[4][4]);
void GPU_matrix_translate_3f(float x, float y, float z);
void GPU_matrix_translate_3fv(const float vec[3]);
void GPU_matrix_scale_3f(float x, float y, float z);
void GPU_matrix_scale_3fv(const float vec[3]);
/**
* Axis of rotation should be a unit vector.
*/
void GPU_matrix_rotate_3f(float deg, float x, float y, float z);
/**
* Axis of rotation should be a unit vector.
*/
void GPU_matrix_rotate_3fv(float deg, const float axis[3]);
void GPU_matrix_rotate_axis(float deg, char axis); /* TODO: enum for axis? */
void GPU_matrix_look_at(float eyeX,
float eyeY,
float eyeZ,
float centerX,
float centerY,
float centerZ,
float upX,
float upY,
float upZ);
/* TODO: variant that takes eye[3], center[3], up[3] */
/* 2D ModelView Matrix */
void GPU_matrix_translate_2f(float x, float y);
void GPU_matrix_translate_2fv(const float vec[2]);
void GPU_matrix_scale_2f(float x, float y);
void GPU_matrix_scale_2fv(const float vec[2]);
void GPU_matrix_rotate_2d(float deg);
/* Projection Matrix (2D or 3D). */
void GPU_matrix_push_projection();
void GPU_matrix_pop_projection();
/* 3D Projection Matrix. */
void GPU_matrix_identity_projection_set();
void GPU_matrix_projection_set(const float m[4][4]);
void GPU_matrix_ortho_set(float left, float right, float bottom, float top, float near, float far);
void GPU_matrix_ortho_set_z(float near, float far);
void GPU_matrix_frustum_set(
float left, float right, float bottom, float top, float near, float far);
void GPU_matrix_perspective_set(float fovy, float aspect, float near, float far);
/* 3D Projection between Window and World Space */
struct GPUMatrixUnproject_Precalc {
float model_inverted[4][4];
float view[4];
bool is_persp;
/**
* Result of #projmat_dimensions_db.
* Using double precision here is important as far clipping ranges
* can cause divide-by-zero when using float, see: #66937.
*/
struct {
double xmin, xmax;
double ymin, ymax;
double zmin, zmax;
} dims;
};
bool GPU_matrix_unproject_precalc(GPUMatrixUnproject_Precalc *unproj_precalc,
const float model[4][4],
const float proj[4][4],
const int view[4]);
void GPU_matrix_project_3fv(const float world[3],
const float model[4][4],
const float proj[4][4],
const int view[4],
float r_win[3]);
void GPU_matrix_project_2fv(const float world[3],
const float model[4][4],
const float proj[4][4],
const int view[4],
float r_win[2]);
bool GPU_matrix_unproject_3fv(const float win[3],
const float model_inverted[4][4],
const float proj[4][4],
const int view[4],
float r_world[3]);
/* 2D Projection Matrix. */
void GPU_matrix_ortho_2d_set(float left, float right, float bottom, float top);
/* Functions to get matrix values. */
const float (*GPU_matrix_model_view_get(float m[4][4]))[4];
const float (*GPU_matrix_projection_get(float m[4][4]))[4];
const float (*GPU_matrix_model_view_projection_get(float m[4][4]))[4];
const float (*GPU_matrix_normal_get(float m[3][3]))[3];
const float (*GPU_matrix_normal_inverse_get(float m[3][3]))[3];
/**
* Set uniform values for currently bound shader.
*/
void GPU_matrix_bind(gpu::Shader *shader);
bool GPU_matrix_dirty_get(); /* since last bind */
/**
* Own working polygon offset.
*/
float GPU_polygon_offset_calc(const float (*winmat)[4], float viewdist, float dist);
/**
* \note \a viewdist is only for orthographic projections at the moment.
*/
void GPU_polygon_offset(float viewdist, float dist);
/* Python API needs to be able to inspect the stack so errors raise exceptions
* instead of crashing. */
#ifdef USE_GPU_PY_MATRIX_API
int GPU_matrix_stack_level_get_model_view();
int GPU_matrix_stack_level_get_projection();
/* static assert ensures this doesn't change! */
# define GPU_PY_MATRIX_STACK_LEN 31
#endif /* USE_GPU_PY_MATRIX_API */
#ifndef SUPPRESS_GENERIC_MATRIX_API
# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
# define _GPU_MAT3_CONST_CAST(x) \
(_Generic((x), \
void *: (const float (*)[3])(x), \
float *: (const float (*)[3])(x), \
float (*)[4]: (const float (*)[3])(x), \
const void *: (const float (*)[3])(x), \
const float *: (const float (*)[3])(x), \
const float (*)[3]: (const float (*)[3])(x)))
# define _GPU_MAT3_CAST(x) \
(_Generic((x), \
void *: (float (*)[3])(x), \
float *: (float (*)[3])(x), \
float (*)[3]: (float (*)[3])(x)))
# define _GPU_MAT4_CONST_CAST(x) \
(_Generic((x), \
void *: (const float (*)[4])(x), \
float *: (const float (*)[4])(x), \
float (*)[4]: (const float (*)[4])(x), \
const void *: (const float (*)[4])(x), \
const float *: (const float (*)[4])(x), \
const float (*)[4]: (const float (*)[4])(x)))
# define _GPU_MAT4_CAST(x) \
(_Generic((x), \
void *: (float (*)[4])(x), \
float *: (float (*)[4])(x), \
float (*)[4]: (float (*)[4])(x)))
# else
# define _GPU_MAT3_CONST_CAST(x) (const float (*)[3])(x)
# define _GPU_MAT3_CAST(x) (float (*)[3])(x)
# define _GPU_MAT4_CONST_CAST(x) (const float (*)[4])(x)
# define _GPU_MAT4_CAST(x) (float (*)[4])(x)
# endif /* C11 */
/* make matrix inputs generic, to avoid warnings */
# define GPU_matrix_mul(x) GPU_matrix_mul(_GPU_MAT4_CONST_CAST(x))
# define GPU_matrix_set(x) GPU_matrix_set(_GPU_MAT4_CONST_CAST(x))
# define GPU_matrix_projection_set(x) GPU_matrix_projection_set(_GPU_MAT4_CONST_CAST(x))
# define GPU_matrix_model_view_get(x) GPU_matrix_model_view_get(_GPU_MAT4_CAST(x))
# define GPU_matrix_projection_get(x) GPU_matrix_projection_get(_GPU_MAT4_CAST(x))
# define GPU_matrix_model_view_projection_get(x) \
GPU_matrix_model_view_projection_get(_GPU_MAT4_CAST(x))
# define GPU_matrix_normal_get(x) GPU_matrix_normal_get(_GPU_MAT3_CAST(x))
# define GPU_matrix_normal_inverse_get(x) GPU_matrix_normal_inverse_get(_GPU_MAT3_CAST(x))
#endif /* SUPPRESS_GENERIC_MATRIX_API */
/* Not part of the GPU_matrix API,
* however we need to check these limits in code that calls into these API's. */
constexpr static int GPU_MATRIX_ORTHO_CLIP_NEAR_DEFAULT = -100;
constexpr static int GPU_MATRIX_ORTHO_CLIP_FAR_DEFAULT = 100;
} // namespace blender

View File

@@ -0,0 +1,52 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Generate and cache shaders generated from the intermediate node graph.
*/
#pragma once
#include "GPU_material.hh"
#include "GPU_shader.hh"
namespace blender {
struct GPUNodeGraph;
struct GPUPass;
enum GPUPassStatus {
GPU_PASS_FAILED = 0,
GPU_PASS_QUEUED,
GPU_PASS_SUCCESS,
};
GPUPass *GPU_generate_pass(GPUMaterial *material,
GPUNodeGraph *graph,
const char *debug_name,
eGPUMaterialEngine engine,
bool deferred_compilation,
GPUCodegenCallbackFn finalize_source_cb,
void *thunk,
bool optimize_graph);
GPUPassStatus GPU_pass_status(GPUPass *pass);
bool GPU_pass_should_optimize(GPUPass *pass);
void GPU_pass_ensure_its_ready(GPUPass *pass);
gpu::Shader *GPU_pass_shader_get(GPUPass *pass);
void GPU_pass_acquire(GPUPass *pass);
void GPU_pass_release(GPUPass *pass);
uint64_t GPU_pass_global_compilation_count();
uint64_t GPU_pass_compilation_timestamp(GPUPass *pass);
void GPU_pass_cache_init();
void GPU_pass_cache_update();
void GPU_pass_cache_wait_for_all();
void GPU_pass_cache_free();
} // namespace blender

View File

@@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include <cstdint>
#include <optional>
#include <string>
#include "BLI_enum_flags.hh"
#include "BLI_span.hh"
#include "GPU_platform_backend_enum.h" // IWYU pragma: export
namespace blender {
/* GPU platform support */
/* GPU Types */
enum GPUDeviceType {
GPU_DEVICE_NVIDIA = (1 << 0),
GPU_DEVICE_ATI = (1 << 1),
GPU_DEVICE_INTEL = (1 << 2),
GPU_DEVICE_INTEL_UHD = (1 << 3),
GPU_DEVICE_APPLE = (1 << 4),
GPU_DEVICE_SOFTWARE = (1 << 5),
GPU_DEVICE_QUALCOMM = (1 << 6),
GPU_DEVICE_UNKNOWN = (1 << 7),
GPU_DEVICE_ANY = (0xff),
};
ENUM_OPERATORS(GPUDeviceType)
enum GPUOSType {
GPU_OS_WIN = (1 << 8),
GPU_OS_MAC = (1 << 9),
GPU_OS_UNIX = (1 << 10),
GPU_OS_ANY = (0xff00),
};
enum GPUDriverType {
GPU_DRIVER_OFFICIAL = (1 << 16),
GPU_DRIVER_OPENSOURCE = (1 << 17),
GPU_DRIVER_SOFTWARE = (1 << 18),
GPU_DRIVER_ANY = (0xff0000),
};
enum GPUSupportLevel {
GPU_SUPPORT_LEVEL_SUPPORTED,
GPU_SUPPORT_LEVEL_LIMITED,
GPU_SUPPORT_LEVEL_UNSUPPORTED,
};
enum GPUArchitectureType {
/* Immediate Mode Renderer (IMR).
* Typically, an IMR architecture will execute GPU work in sequence, rasterizing primitives in
* order. */
GPU_ARCHITECTURE_IMR = 0,
/* Tile-Based-Deferred-Renderer (TBDR).
* A TBDR architecture will typically execute the vertex stage up-front for all primitives,
* binning geometry into distinct tiled regions. Fragments will then be rasterized within
* the bounds of one tile at a time. */
GPU_ARCHITECTURE_TBDR = 1,
};
struct GPUDevice {
std::string identifier;
int index;
uint32_t vendor_id;
uint32_t device_id;
std::string name;
};
/* GPU Types */
/* TODO: Verify all use-cases of GPU_type_matches to determine which graphics API it should apply
* to, and replace with `GPU_type_matches_ex` where appropriate. */
bool GPU_type_matches(GPUDeviceType device, GPUOSType os, GPUDriverType driver);
bool GPU_type_matches_ex(GPUDeviceType device,
GPUOSType os,
GPUDriverType driver,
GPUBackendType backend);
GPUSupportLevel GPU_platform_support_level();
const char *GPU_platform_vendor();
const char *GPU_platform_renderer();
const char *GPU_platform_version();
const char *GPU_platform_support_level_key();
const char *GPU_platform_gpu_name();
GPUArchitectureType GPU_platform_architecture();
Span<GPUDevice> GPU_platform_devices_list();
/* The UUID of the device. Can be an empty array, since it is not supported on all platforms. */
Span<uint8_t> GPU_platform_uuid();
/* The LUID of the device. Can be an empty array, since it is not supported on all platforms. */
Span<uint8_t> GPU_platform_luid();
/* A bit field with the nth bit active identifying the nth device with the same LUID. Only matters
* if LUID is defined. */
uint32_t GPU_platform_luid_node_mask();
} // namespace blender

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
namespace blender {
/** NOTE: Keep in sync with eUserPref_GPUBackendType. */
enum GPUBackendType {
GPU_BACKEND_NONE = 0,
GPU_BACKEND_OPENGL = 1 << 0,
GPU_BACKEND_METAL = 1 << 1,
GPU_BACKEND_VULKAN = 1 << 3,
GPU_BACKEND_ANY = 0xFFFFFFFFu
};
} // namespace blender

View File

@@ -0,0 +1,124 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU geometric primitives
*/
#pragma once
#include "BLI_assert.h"
#include "BLI_sys_types.h"
namespace blender {
enum GPUPrimType : int8_t {
GPU_PRIM_POINTS,
GPU_PRIM_LINES,
GPU_PRIM_TRIS,
GPU_PRIM_LINE_STRIP,
GPU_PRIM_LINE_LOOP, /* GL has this, Vulkan and Metal do not */
GPU_PRIM_TRI_STRIP,
GPU_PRIM_TRI_FAN, /* Metal API does not support this. */
/* Metal API does not support ADJ primitive types but
* handled via the geometry-shader-alternative path. */
GPU_PRIM_LINES_ADJ,
GPU_PRIM_TRIS_ADJ,
GPU_PRIM_LINE_STRIP_ADJ,
GPU_PRIM_NONE,
};
/* what types of primitives does each shader expect? */
enum GPUPimClass {
GPU_PRIM_CLASS_NONE = 0,
GPU_PRIM_CLASS_POINT = (1 << 0),
GPU_PRIM_CLASS_LINE = (1 << 1),
GPU_PRIM_CLASS_SURFACE = (1 << 2),
GPU_PRIM_CLASS_ANY = GPU_PRIM_CLASS_POINT | GPU_PRIM_CLASS_LINE | GPU_PRIM_CLASS_SURFACE,
};
inline int gpu_get_prim_count_from_type(uint vertex_len, GPUPrimType prim_type)
{
/* does vertex_len make sense for this primitive type? */
if (vertex_len == 0) {
return 0;
}
switch (prim_type) {
case GPU_PRIM_POINTS:
return vertex_len;
case GPU_PRIM_LINES:
BLI_assert(vertex_len % 2 == 0);
return vertex_len / 2;
case GPU_PRIM_LINE_STRIP:
return vertex_len - 1;
case GPU_PRIM_LINE_LOOP:
return vertex_len;
case GPU_PRIM_LINES_ADJ:
BLI_assert(vertex_len % 4 == 0);
return vertex_len / 4;
case GPU_PRIM_LINE_STRIP_ADJ:
return vertex_len - 2;
case GPU_PRIM_TRIS:
BLI_assert(vertex_len % 3 == 0);
return vertex_len / 3;
case GPU_PRIM_TRI_STRIP:
BLI_assert(vertex_len >= 3);
return vertex_len - 2;
case GPU_PRIM_TRI_FAN:
BLI_assert(vertex_len >= 3);
return vertex_len - 2;
case GPU_PRIM_TRIS_ADJ:
BLI_assert(vertex_len % 6 == 0);
return vertex_len / 6;
default:
BLI_assert_unreachable();
return 0;
}
}
inline bool is_restart_compatible(GPUPrimType type)
{
switch (type) {
case GPU_PRIM_POINTS:
case GPU_PRIM_LINES:
case GPU_PRIM_TRIS:
case GPU_PRIM_LINES_ADJ:
case GPU_PRIM_TRIS_ADJ:
case GPU_PRIM_NONE:
default: {
return false;
}
case GPU_PRIM_LINE_STRIP:
case GPU_PRIM_LINE_LOOP:
case GPU_PRIM_TRI_STRIP:
case GPU_PRIM_TRI_FAN:
case GPU_PRIM_LINE_STRIP_ADJ: {
return true;
}
}
return false;
}
/**
* TODO: Improve error checking by validating that the shader is suited for this primitive type.
* GPUPrimClass GPU_primtype_class(GPUPrimType);
* bool GPU_primtype_belongs_to_class(GPUPrimType, GPUPrimClass);
*/
} // namespace blender

View File

@@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_span.hh"
#include "BLI_sys_types.h"
#include "BLI_vector.hh"
namespace blender {
struct rcti;
/** Flags for mode of operation. */
enum GPUSelectMode {
GPU_SELECT_INVALID = 0,
GPU_SELECT_ALL = 1,
/* gpu_select_query */
GPU_SELECT_NEAREST_FIRST_PASS = 2,
GPU_SELECT_NEAREST_SECOND_PASS = 3,
/* gpu_select_pick */
GPU_SELECT_PICK_ALL = 4,
GPU_SELECT_PICK_NEAREST = 5,
};
/**
* The result of calling #GPU_select_begin & #GPU_select_end.
*/
struct GPUSelectResult {
/** The selection identifier matching the value passed in by #GPU_select_load_id. */
unsigned int id;
/**
* The nearest depth.
* - Only supported by picking modes (#GPU_SELECT_PICK_ALL and #GPU_SELECT_PICK_NEAREST)
* since occlusion quires don't provide a convenient way of accessing the depth-buffer.
* - OpenGL's `GL_SELECT` supported both near and far depths,
* this has not been included as Blender doesn't need this however support could be added.
*/
unsigned int depth;
};
using GPUSelectStorage = Vector<GPUSelectResult, 2500>;
struct GPUSelectBuffer {
GPUSelectStorage storage;
};
/**
* Initialize and provide buffer for results.
*/
void GPU_select_begin(GPUSelectBuffer *buffer, const rcti *input, GPUSelectMode mode, int oldhits);
/**
* Initialize and provide buffer for results.
* Uses the new Select-Next engine if enabled.
*/
void GPU_select_begin_next(
GPUSelectBuffer *buffer, const rcti *input, int radius, GPUSelectMode mode, int oldhits);
/**
* Loads a new selection id and ends previous query, if any.
* In second pass of selection it also returns
* if id has been hit on the first pass already.
* Thus we can skip drawing un-hit objects.
*
* \warning We rely on the order of object rendering on passes to be the same for this to work.
*/
bool GPU_select_load_id(unsigned int id);
void GPU_select_finalize();
/**
* Cleanup and flush selection results to buffer.
* Return number of hits and hits in buffer.
* if \a dopass is true, we will do a second pass with occlusion queries to get the closest hit.
*/
unsigned int GPU_select_end();
/* Cache selection region. */
bool GPU_select_is_cached();
void GPU_select_cache_begin();
void GPU_select_cache_load_id();
void GPU_select_cache_end();
/* Utilities. */
/**
* Helper function, nothing special but avoids doing inline since hits aren't sorted by depth
* and purpose of 4x buffer indices isn't so clear.
*
* Note that comparing depth as uint is fine.
*/
const GPUSelectResult *GPU_select_buffer_near(const Span<GPUSelectResult> hit_results);
/**
* Compare result of `GPU_select`: #GPUSelectResult,
* Needed for stable sorting, so cycling through all items near the cursor behaves predictably.
*/
int gpu_select_buffer_depth_id_cmp(const void *sel_a_p, const void *sel_b_p);
uint GPU_select_buffer_remove_by_id(MutableSpan<GPUSelectResult> hit_results, uint select_id);
/**
* Part of the solution copied from `rect_subregion_stride_calc`.
*/
void GPU_select_buffer_stride_realign(const rcti *src, const rcti *dst, uint *r_buf);
} // namespace blender

View File

@@ -0,0 +1,585 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* A #gpu::Shader is a container for backend specific shader program.
*/
#pragma once
#include <atomic>
#include <mutex>
#include <optional>
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
#include "BLI_vector.hh"
#include "GPU_common_types.hh"
#include "GPU_shader_builtin.hh"
struct GPUShaderCreateInfo;
namespace blender {
namespace gpu {
class VertBuf;
class Shader;
namespace shader {
struct ShaderCreateInfo;
} // namespace shader
} // namespace gpu
/** Opaque type hiding #gpu::shader::ShaderCreateInfo */
/* Hardware limit is 16. Position attribute is always needed so we reduce to 15.
* This makes sure the GPUVertexFormat name buffer does not overflow. */
constexpr static int GPU_MAX_ATTR = 15;
/* -------------------------------------------------------------------- */
/** \name Creation
* \{ */
/**
* Preprocess a raw GLSL source to adhere to our backend compatible shader language.
* Needed if the string was not part of our build system and is used in a #GPUShaderCreateInfo.
* `info` is the create info to add builtin flags parsed from the original string.
*/
std::string GPU_shader_preprocess_source(StringRefNull original,
gpu::shader::ShaderCreateInfo &info);
/**
* Create a shader using the given #GPUShaderCreateInfo.
* Can return a null pointer if compilation fails.
*/
gpu::Shader *GPU_shader_create_from_info(const GPUShaderCreateInfo *_info);
/**
* Same as GPU_shader_create_from_info but will run preprocessor on source strings.
*/
gpu::Shader *GPU_shader_create_from_info_python(const GPUShaderCreateInfo *_info);
/**
* Create a shader using a named #GPUShaderCreateInfo registered at startup.
* These are declared inside `*_info.hh` files using the `GPU_SHADER_CREATE_INFO()` macro.
* They are also expected to have been flagged using `do_static_compilation`.
* Can return a null pointer if compilation fails.
*/
gpu::Shader *GPU_shader_create_from_info_name(const char *info_name);
/**
* Fetch a named #GPUShaderCreateInfo registered at startup.
* These are declared inside `*_info.hh` files using the `GPU_SHADER_CREATE_INFO()` macro.
* Can return a null pointer if no match is found.
*/
const GPUShaderCreateInfo *GPU_shader_create_info_get(const char *info_name);
/**
* Error checking for user created shaders.
* \return true is create info is valid.
*/
bool GPU_shader_create_info_check_error(const GPUShaderCreateInfo *_info, char r_error[128]);
enum class CompilationPriority { Low, Medium, High };
using AsyncCompilationHandle = int64_t;
/**
* Request the async compilation of a shader, allowing the backend to use multithreaded
* compilation. Returns a handle that can be used to poll if the shader has been compiled, and to
* retrieve the compiled shader.
* WARNING: The GPUShaderCreateInfo pointer should be valid until
* `GPU_shader_async_compilation_finalize` has returned.
*/
AsyncCompilationHandle GPU_shader_async_compilation(
const GPUShaderCreateInfo *info, CompilationPriority priority = CompilationPriority::High);
/**
* Returns true if the shader has already been compiled.
*/
bool GPU_shader_async_compilation_is_ready(AsyncCompilationHandle handle);
/**
* Retrieve the compiled shader.
* If the compilation has not finished yet, this call will block the thread until the shader is
* ready. Shaders with compilation errors are returned as null pointers.
* WARNING: The handle will be invalidated by this call, you can't request the same shader twice.
*/
gpu::Shader *GPU_shader_async_compilation_finalize(AsyncCompilationHandle &handle);
/**
* Cancel the compilation of the shader.
* WARNING: The handle will be invalidated by this call.
*/
void GPU_shader_async_compilation_cancel(AsyncCompilationHandle &handle);
/**
* Returns true if there's any shader still being compiled.
* NOTE: This returns true as long as there are batches in the compilation queue.
* It doesn't take into account if compilation is paused.
*/
bool GPU_shader_compiler_has_pending_work();
/**
* Wait until all the requested shaders have been compiled.
*/
void GPU_shader_compiler_wait_for_all();
/**
* Pauses all shader compilations.
*/
void GPU_shader_compiler_pause();
/**
* Resumes compilations after being paused.
*/
void GPU_shader_compiler_resume();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Free
* \{ */
void GPU_shader_free(gpu::Shader *shader);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Binding
* \{ */
/**
* Set the given shader as active shader for the active GPU context.
* It replaces any already bound shader.
* All following draw-calls and dispatches will use this shader.
* Uniform functions need to have the shader bound in order to work. (TODO: until we use
* glProgramUniform)
*/
void GPU_shader_bind(gpu::Shader *shader,
const gpu::shader::SpecializationConstants *constants_state = nullptr);
/**
* Unbind the active shader.
* \note this is a no-op in release builds. But it make sense to actually do it in user land code
* to detect incorrect API usage.
*/
void GPU_shader_unbind();
/**
* Return the currently bound shader to the active GPU context.
* \return null pointer if no shader is bound of if no context is active.
*/
gpu::Shader *GPU_shader_get_bound();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging introspection API.
* \{ */
const char *GPU_shader_get_name(gpu::Shader *shader);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Uniform API.
* \{ */
/**
* Returns binding point location.
* Binding location are given to be set at shader compile time and immutable.
*/
int GPU_shader_get_ubo_binding(gpu::Shader *shader, const char *name);
int GPU_shader_get_ssbo_binding(gpu::Shader *shader, const char *name);
int GPU_shader_get_sampler_binding(gpu::Shader *shader, const char *name);
/**
* Returns uniform location.
* If cached, it is faster than querying the interface for each uniform assignment.
*/
int GPU_shader_get_uniform(gpu::Shader *shader, const char *name);
/**
* Returns specialization constant location.
*/
int GPU_shader_get_constant(gpu::Shader *shader, const char *name);
/**
* Sets a generic push constant (a.k.a. uniform).
* \a length and \a array_size should match the create info push_constant declaration.
*/
void GPU_shader_uniform_float_ex(
gpu::Shader *shader, int location, int length, int array_size, const float *value);
void GPU_shader_uniform_int_ex(
gpu::Shader *shader, int location, int length, int array_size, const int *value);
/**
* Sets a generic push constant (a.k.a. uniform).
* \a length and \a array_size should match the create info push_constant declaration.
* These functions need to have the shader bound in order to work. (TODO: until we use
* glProgramUniform)
*/
void GPU_shader_uniform_1i(gpu::Shader *sh, const char *name, int value);
void GPU_shader_uniform_1b(gpu::Shader *sh, const char *name, bool value);
void GPU_shader_uniform_1f(gpu::Shader *sh, const char *name, float value);
void GPU_shader_uniform_2f(gpu::Shader *sh, const char *name, float x, float y);
void GPU_shader_uniform_3f(gpu::Shader *sh, const char *name, float x, float y, float z);
void GPU_shader_uniform_4f(gpu::Shader *sh, const char *name, float x, float y, float z, float w);
void GPU_shader_uniform_2fv(gpu::Shader *sh, const char *name, const float data[2]);
void GPU_shader_uniform_3fv(gpu::Shader *sh, const char *name, const float data[3]);
void GPU_shader_uniform_4fv(gpu::Shader *sh, const char *name, const float data[4]);
void GPU_shader_uniform_2iv(gpu::Shader *sh, const char *name, const int data[2]);
void GPU_shader_uniform_3iv(gpu::Shader *sh, const char *name, const int data[3]);
void GPU_shader_uniform_mat4(gpu::Shader *sh, const char *name, const float data[4][4]);
void GPU_shader_uniform_mat3(gpu::Shader *sh, const char *name, const float data[3][3]);
void GPU_shader_uniform_mat3_as_mat4(gpu::Shader *sh, const char *name, const float data[3][3]);
void GPU_shader_uniform_1f_array(gpu::Shader *sh, const char *name, int len, const float *val);
void GPU_shader_uniform_2fv_array(gpu::Shader *sh,
const char *name,
int len,
const float (*val)[2]);
void GPU_shader_uniform_4fv_array(gpu::Shader *sh,
const char *name,
int len,
const float (*val)[4]);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Attribute API.
*
* Used to create #GPUVertexFormat from the shader's vertex input layout.
* \{ */
uint GPU_shader_get_attribute_len(const gpu::Shader *shader);
uint GPU_shader_get_ssbo_input_len(const gpu::Shader *shader);
int GPU_shader_get_attribute(const gpu::Shader *shader, const char *name);
bool GPU_shader_get_attribute_info(const gpu::Shader *shader,
int attr_location,
char r_name[256],
int *r_type);
bool GPU_shader_get_ssbo_input_info(const gpu::Shader *shader,
int ssbo_location,
char r_name[256]);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Specialization API.
*
* Used to allow specialization constants.
* IMPORTANT: All constants must be specified before binding a shader that needs specialization.
* Otherwise, it will produce undefined behavior.
* \{ */
/* Return the default constants.
* All constants available for this shader should fit the returned structure. */
const gpu::shader::SpecializationConstants &GPU_shader_get_default_constant_state(gpu::Shader *sh);
using AsyncSpecializationHandle = int64_t;
struct ShaderSpecialization {
gpu::Shader *shader;
gpu::shader::SpecializationConstants constants;
};
/**
* Request the async compilation of a shader specialization, allowing the backend to use
* multi-threaded compilation.
* Returns a handle that can be used to poll if the compilation has finished.
* A NULL handle indicates no compilation of any variant was possible (likely due to
* some state being currently available) and so no batch was created. Compilation
* of the specialized variant will instead occur at draw/dispatch time.
* NOTE: This function is asynchronous on OpenGL and Metal and a no-op on Vulkan.
* Batches are processed one by one in FIFO order.
* WARNING: Binding a specialization before the batch finishes will fail.
*/
AsyncSpecializationHandle GPU_shader_async_specialization(
const ShaderSpecialization &specialization,
CompilationPriority priority = CompilationPriority::High);
/**
* Returns true if the specialization has finished its compilation.
* WARNING: Invalidates the handle if it returns true.
*/
bool GPU_shader_async_specialization_is_ready(AsyncSpecializationHandle &handle);
/**
* Cancel the specialization compilation.
* WARNING: The handle will be invalidated by this call.
*/
void GPU_shader_async_specialization_cancel(AsyncSpecializationHandle &handle);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Legacy API
*
* All of this section is deprecated and should be ported to use the API described above.
* \{ */
/**
* Shader cache warming.
* For each shader, rendering APIs perform a two-step compilation:
*
* * The first stage is Front-End compilation which only needs to be performed once, and generates
* a portable intermediate representation. This happens during `gpu::Shader::finalize()`.
*
* * The second is Back-End compilation which compiles a device-specific executable shader
* program. This compilation requires some contextual pipeline state which is baked into the
* executable shader source, producing a Pipeline State Object (PSO). In OpenGL, backend
* compilation happens in the background, within the driver, but can still incur runtime stutters.
* In Metal/Vulkan, PSOs are compiled explicitly. These are currently resolved within the backend
* based on the current pipeline state and can incur runtime stalls when they occur.
*
* Shader Cache warming uses the specified parent shader set using `GPU_shader_set_parent(..)` as a
* template reference for pre-compiling Render Pipeline State Objects (PSOs) outside of the main
* render pipeline.
*
* PSOs require descriptors containing information on the render state for a given shader, which
* includes input vertex data layout and output pixel formats, along with some state such as
* blend mode and color output masks. As this state information is usually consistent between
* similar draws, we can assign a parent shader and use this shader's cached pipeline state's to
* prime compilations.
*
* Shaders do not necessarily have to be similar in functionality to be used as a parent, so long
* as the #GPUVertFormat and #gpu::FrameBuffer which they are used with remain the same.
* Other bindings such as textures, uniforms and UBOs are all assigned independently as dynamic
* state.
*
* This function should be called asynchronously, mitigating the impact of run-time stuttering from
* dynamic compilation of PSOs during normal rendering.
*
* \param: shader: The shader whose cache to warm.
* \param limit: The maximum number of PSOs to compile within a call. Specifying
* a limit <= 0 will compile a PSO for all cached PSOs in the parent shader. */
void GPU_shader_warm_cache(gpu::Shader *shader, int limit);
/* We expect the parent shader to be compiled and already have some cached PSOs when being assigned
* as a reference. Ensure the parent shader still exists when `GPU_shader_cache_warm(..)` is
* called. */
void GPU_shader_set_parent(gpu::Shader *shader, gpu::Shader *parent);
/**
* Indexed commonly used uniform name for faster lookup into the uniform cache.
*/
enum GPUUniformBuiltin {
GPU_UNIFORM_MODEL = 0, /* mat4 ModelMatrix */
GPU_UNIFORM_VIEW, /* mat4 ViewMatrix */
GPU_UNIFORM_MODELVIEW, /* mat4 ModelViewMatrix */
GPU_UNIFORM_PROJECTION, /* mat4 ProjectionMatrix */
GPU_UNIFORM_VIEWPROJECTION, /* mat4 ViewProjectionMatrix */
GPU_UNIFORM_MVP, /* mat4 ModelViewProjectionMatrix */
GPU_UNIFORM_MODEL_INV, /* mat4 ModelMatrixInverse */
GPU_UNIFORM_VIEW_INV, /* mat4 ViewMatrixInverse */
GPU_UNIFORM_MODELVIEW_INV, /* mat4 ModelViewMatrixInverse */
GPU_UNIFORM_PROJECTION_INV, /* mat4 ProjectionMatrixInverse */
GPU_UNIFORM_VIEWPROJECTION_INV, /* mat4 ViewProjectionMatrixInverse */
GPU_UNIFORM_NORMAL, /* mat3 NormalMatrix */
GPU_UNIFORM_CLIPPLANES, /* vec4 WorldClipPlanes[] */
GPU_UNIFORM_COLOR, /* vec4 color */
GPU_UNIFORM_BASE_INSTANCE, /* int baseInstance */
GPU_UNIFORM_SRGB_TRANSFORM, /* bool srgbTarget */
GPU_UNIFORM_SCENE_LINEAR_XFORM, /* float3x3 gpu_scene_linear_to_xyz */
};
#define GPU_NUM_UNIFORMS (GPU_UNIFORM_SCENE_LINEAR_XFORM + 1)
/**
* TODO: To be moved as private API. Not really used outside of gpu_matrix.cc and doesn't really
* offer a noticeable performance boost.
*/
int GPU_shader_get_builtin_uniform(gpu::Shader *shader, int builtin);
/**
* Compile all statically defined shaders and print a report to the console.
*
* This is used for platform support, where bug reports can list all failing shaders.
*/
void GPU_shader_compile_static();
void GPU_shader_cache_dir_clear_old();
/** DEPRECATED: Use hard-coded buffer location instead. */
enum GPUUniformBlockBuiltin {
GPU_UNIFORM_BLOCK_VIEW = 0, /* viewBlock */
GPU_UNIFORM_BLOCK_MODEL, /* modelBlock */
GPU_UNIFORM_BLOCK_INFO, /* infoBlock */
GPU_UNIFORM_BLOCK_DRW_VIEW,
GPU_UNIFORM_BLOCK_DRW_MODEL,
GPU_UNIFORM_BLOCK_DRW_INFOS,
GPU_UNIFORM_BLOCK_DRW_CLIPPING,
GPU_NUM_UNIFORM_BLOCKS, /* Special value, denotes number of builtin uniforms block. */
};
/** DEPRECATED: Kept only because of Python GPU API. */
int GPU_shader_get_uniform_block(gpu::Shader *shader, const char *name);
/** \} */
#define GPU_SHADER_FREE_SAFE(shader) \
do { \
if (shader != nullptr) { \
GPU_shader_free(shader); \
shader = nullptr; \
} \
} while (0)
namespace gpu {
/* gpu::Shader wrapper that makes compilation threadsafe.
* The compilation is deferred until the first get() call.
* Concurrently using the shader from multiple threads is still unsafe. */
class StaticShader : NonCopyable {
private:
std::string info_name_;
std::atomic<gpu::Shader *> shader_ = nullptr;
/* TODO: Failed compilation detection should be supported by the gpu::Shader API. */
std::atomic<bool> failed_ = false;
std::mutex mutex_;
/* Handle for async compilation. */
AsyncCompilationHandle compilation_handle_ = 0;
void move(StaticShader &&other)
{
std::scoped_lock lock1(mutex_);
std::scoped_lock lock2(other.mutex_);
BLI_assert(shader_ == nullptr && info_name_.empty());
std::swap(info_name_, other.info_name_);
/* No std::swap support for atomics. */
shader_.exchange(other.shader_.exchange(shader_));
failed_.exchange(other.failed_.exchange(failed_));
std::swap(compilation_handle_, other.compilation_handle_);
}
public:
StaticShader(std::string info_name) : info_name_(info_name) {}
StaticShader() = default;
StaticShader(StaticShader &&other)
{
move(std::move(other));
}
StaticShader &operator=(StaticShader &&other)
{
move(std::move(other));
return *this;
};
~StaticShader()
{
if (compilation_handle_) {
GPU_shader_async_compilation_cancel(compilation_handle_);
}
GPU_SHADER_FREE_SAFE(shader_);
}
/* Schedule the shader to be compile in a worker thread. */
void ensure_compile_async()
{
if (is_ready()) {
return;
}
std::scoped_lock lock(mutex_);
if (compilation_handle_) {
if (GPU_shader_async_compilation_is_ready(compilation_handle_)) {
shader_ = GPU_shader_async_compilation_finalize(compilation_handle_);
failed_ = shader_ == nullptr;
}
return;
}
if (!shader_ && !failed_ && !compilation_handle_) {
BLI_assert(!info_name_.empty());
const GPUShaderCreateInfo *create_info = GPU_shader_create_info_get(info_name_.c_str());
compilation_handle_ = GPU_shader_async_compilation(create_info);
}
}
bool is_ready()
{
return shader_ || failed_;
}
gpu::Shader *get()
{
if (is_ready()) {
return shader_;
}
std::scoped_lock lock(mutex_);
if (!shader_ && !failed_) {
if (compilation_handle_) {
shader_ = GPU_shader_async_compilation_finalize(compilation_handle_);
}
else {
BLI_assert(!info_name_.empty());
shader_ = GPU_shader_create_from_info_name(info_name_.c_str());
}
failed_ = shader_ == nullptr;
}
return shader_;
}
/* For batch compiled shaders. */
/* TODO: Find a better way to handle this. */
void set(gpu::Shader *shader)
{
std::scoped_lock lock(mutex_);
BLI_assert(shader_ == nullptr);
shader_ = shader;
}
};
/* Thread-safe container for StaticShader cache classes.
* The class instance creation is deferred until the first get() call. */
template<typename T> class StaticShaderCache {
std::atomic<T *> cache_ = nullptr;
std::mutex mutex_;
public:
~StaticShaderCache()
{
BLI_assert(cache_ == nullptr);
}
template<typename... Args> T &get(Args &&...constructor_args)
{
if (cache_) {
return *cache_;
}
std::lock_guard lock(mutex_);
if (cache_ == nullptr) {
cache_ = new T(std::forward<Args>(constructor_args)...);
}
return *cache_;
}
void release()
{
if (!cache_) {
return;
}
std::lock_guard lock(mutex_);
if (cache_) {
delete cache_;
cache_ = nullptr;
}
}
std::lock_guard<std::mutex> lock_guard()
{
return std::lock_guard(mutex_);
}
};
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,211 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Set of shaders used for interface drawing.
*
* 2D shaders are not expected to work in 3D.
* 3D shaders can work with 2D geometry and matrices.
*
* `INST` suffix means instance, which means the shader is build to leverage instancing
* capabilities to reduce the number of draw-calls.
*
* For full list of parameters, search for the associated #ShaderCreateInfo.
* Example: `GPU_SHADER_ICON` is defined by `GPU_SHADER_CREATE_INFO(gpu_shader_icon)`
* Some parameters are builtins and are set automatically (ex: `ModelViewProjectionMatrix`).
*/
#pragma once
namespace blender {
namespace gpu {
class Shader;
} // namespace gpu
enum GPUBuiltinShader {
/** Glyph drawing shader used by the BLF module. */
GPU_SHADER_TEXT = 0,
/** Draws keyframe markers. All markers shapes are supported through a single shader. */
GPU_SHADER_KEYFRAME_SHAPE,
/** Draw solid mesh with a single distant light using a clamped simple dot product. */
GPU_SHADER_SIMPLE_LIGHTING,
/** Draw an icon, leaving a semi-transparent rectangle on top of the icon. */
GPU_SHADER_ICON,
/** Draw a texture with a uniform color multiplied. */
GPU_SHADER_2D_IMAGE_RECT_COLOR,
/** Draw a texture with a desaturation factor. */
GPU_SHADER_2D_IMAGE_DESATURATE_COLOR,
/** Draw a group of texture rectangle with an associated color multiplied. */
GPU_SHADER_ICON_MULTI,
/** Draw a two color checker based on screen position (not UV coordinates). */
GPU_SHADER_2D_CHECKER,
/** Draw diagonal stripes with two alternating colors. */
GPU_SHADER_2D_DIAG_STRIPES,
/** Draw dashed lines with custom dash length and uniform color. */
GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR,
/** Draw triangles / lines / points with only depth output. */
GPU_SHADER_3D_DEPTH_ONLY,
/** Merge viewport overlay texture with the render output. */
GPU_SHADER_2D_IMAGE_OVERLAYS_MERGE,
GPU_SHADER_2D_IMAGE_OVERLAYS_STEREO_MERGE,
/** Merge viewport overlay texture with the render output. */
GPU_SHADER_2D_IMAGE_SHUFFLE_COLOR,
/** Used for drawing of annotations (former grease pencil). */
GPU_SHADER_GPENCIL_STROKE,
/** Draw rounded area borders with silky smooth anti-aliasing without any over-draw. */
GPU_SHADER_2D_AREA_BORDERS,
/** Multi usage widget shaders for drawing buttons and other UI elements. */
GPU_SHADER_2D_WIDGET_BASE,
GPU_SHADER_2D_WIDGET_BASE_INST,
GPU_SHADER_2D_WIDGET_SHADOW,
/** Draw a node socket given it's bounding rectangle. All socket shapes are supported through
* a single shader. */
GPU_SHADER_2D_NODE_SOCKET,
GPU_SHADER_2D_NODE_SOCKET_INST,
/** Draw a node link given an input quadratic Bezier curve. */
GPU_SHADER_2D_NODELINK,
/** Draw round points with per vertex size and color. */
GPU_SHADER_3D_POINT_VARYING_SIZE_VARYING_COLOR,
/** Draw round points with a uniform size. Disabling blending will disable AA. */
GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA,
GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA,
/** Draw round points with a uniform size and an outline. Disabling blending will disable AA. */
GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_OUTLINE_AA,
/** Draw geometry with uniform color. Has an additional clip plane parameter. */
GPU_SHADER_3D_CLIPPED_UNIFORM_COLOR,
/** Draw wide lines with uniform color. Has an additional clip plane parameter. */
GPU_SHADER_3D_POLYLINE_CLIPPED_UNIFORM_COLOR,
/** Draw strip widgets in sequencer timeline. */
GPU_SHADER_SEQUENCER_STRIPS,
/** Draw strip thumbnails in sequencer timeline. */
GPU_SHADER_SEQUENCER_THUMBS,
/** Rasterize sequencer scope points into buffers via compute. */
GPU_SHADER_SEQUENCER_SCOPE_RASTER,
/** Resolve rasterized scope point buffers to display. */
GPU_SHADER_SEQUENCER_SCOPE_RESOLVE,
/** Draw sequencer zebra pattern (overexposed regions). */
GPU_SHADER_SEQUENCER_ZEBRA,
/** Compute shaders to generate 2d index buffers (mainly for curve drawing). */
GPU_SHADER_INDEXBUF_POINTS,
GPU_SHADER_INDEXBUF_LINES,
GPU_SHADER_INDEXBUF_TRIS,
/** Compute shaders to generate mipmaps. */
GPU_SHADER_2D_UPDATE_MIPMAPS_UNORM_8,
GPU_SHADER_2D_UPDATE_MIPMAPS_UNORM_8_LAYERED,
GPU_SHADER_2D_UPDATE_MIPMAPS_UNORM_8_8_8_8,
GPU_SHADER_2D_UPDATE_MIPMAPS_UNORM_8_8_8_8_LAYERED,
GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_16,
GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_16_LAYERED,
GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_16_16_16_16,
GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_16_16_16_16_LAYERED,
GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_32,
GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_32_LAYERED,
GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_32_32_32_32,
GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_32_32_32_32_LAYERED,
GPU_SHADER_2D_UPDATE_MIPMAPS_SRGBA_8_8_8_8,
GPU_SHADER_2D_UPDATE_MIPMAPS_SRGBA_8_8_8_8_LAYERED,
/**
* ----------------------- Shaders exposed through pyGPU module -----------------------
*
* Avoid breaking the interface of these shaders as they are used by addons.
* Polyline versions are used for drawing wide lines (> 1px width).
*/
/**
* Take a 3D position and color for each vertex without color interpolation.
*
* \param color: in vec4
* \param pos: in vec3
*/
GPU_SHADER_3D_FLAT_COLOR,
GPU_SHADER_3D_POLYLINE_FLAT_COLOR,
GPU_SHADER_3D_POINT_FLAT_COLOR,
/**
* Take a 3D position and color for each vertex with perspective correct interpolation.
*
* \param color: in vec4
* \param pos: in vec3
*/
GPU_SHADER_3D_SMOOTH_COLOR,
GPU_SHADER_3D_POLYLINE_SMOOTH_COLOR,
/**
* Take a single color for all the vertices and a 3D position for each vertex.
*
* \param color: uniform vec4
* \param pos: in vec3
*/
GPU_SHADER_3D_UNIFORM_COLOR,
GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR,
GPU_SHADER_3D_POINT_UNIFORM_COLOR,
/**
* Draw a sRGB color space texture in 3D.
* Texture color space is assumed to match the frame-buffer.
* Take a 3D position and a 2D texture coordinate for each vertex.
*
* \param image: uniform sampler2D
* \param texCoord: in vec2
* \param pos: in vec3
*/
GPU_SHADER_3D_IMAGE,
/**
* Draw a scene linear color space texture in 3D.
* Texture value is transformed to the Rec.709 sRGB color space.
* Take a 3D position and a 2D texture coordinate for each vertex.
*
* \param image: uniform sampler2D
* \param texCoord: in vec2
* \param pos: in vec3
*/
GPU_SHADER_3D_IMAGE_SCENE_LINEAR_TO_REC709_SRGB,
/**
* Draw a sRGB color space (with Rec.709 primaries) texture in 3D.
* Take a 3D position and color for each vertex with linear interpolation in window space.
*
* \param color: uniform vec4
* \param image: uniform sampler2D
* \param texCoord: in vec2
* \param pos: in vec3
*/
GPU_SHADER_3D_IMAGE_COLOR,
/**
* Draw a scene linear color space texture in 3D.
* Texture value is transformed to the Rec.709 sRGB color space.
* Take a 3D position and color for each vertex with linear interpolation in window space.
*
* \param color: uniform vec4
* \param image: uniform sampler2D
* \param texCoord: in vec2
* \param pos: in vec3
*/
GPU_SHADER_3D_IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB,
};
#define GPU_SHADER_BUILTIN_LEN (GPU_SHADER_3D_IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB + 1)
/** Support multiple configurations. */
enum GPUShaderConfig {
GPU_SHADER_CFG_DEFAULT = 0,
GPU_SHADER_CFG_CLIPPED = 1,
};
#define GPU_SHADER_CFG_LEN (GPU_SHADER_CFG_CLIPPED + 1)
gpu::Shader *GPU_shader_get_builtin_shader_with_config(GPUBuiltinShader shader,
GPUShaderConfig sh_cfg);
gpu::Shader *GPU_shader_get_builtin_shader(GPUBuiltinShader shader);
void GPU_shader_builtin_warm_up();
void GPU_shader_free_builtin_shaders();
} // namespace blender

View File

@@ -0,0 +1,267 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
struct TestOutputRawData;
#endif
/* NOTE: float3 has differing stride and alignment rules across different GPU back-ends. If 12 byte
* stride and alignment is essential, use `packed_float3` to avoid data read issues. This is
* required in the common use-case where a float3 and an int/float are paired together for optimal
* data transfer. */
enum [[host_shared]] GPUKeyframeShapes : uint32_t {
GPU_KEYFRAME_SHAPE_DIAMOND = (1u << 0u),
GPU_KEYFRAME_SHAPE_CIRCLE = (1u << 1u),
GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL = (1u << 2u),
GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL = (1u << 3u),
GPU_KEYFRAME_SHAPE_INNER_DOT = (1u << 4u),
GPU_KEYFRAME_SHAPE_ARROW_END_MAX = (1u << 8u),
GPU_KEYFRAME_SHAPE_ARROW_END_MIN = (1u << 9u),
GPU_KEYFRAME_SHAPE_ARROW_END_MIXED = (1u << 10u),
GPU_KEYFRAME_SHAPE_SQUARE = (GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL |
GPU_KEYFRAME_SHAPE_CLIPPED_HORIZONTAL),
};
#define MAX_SOCKET_PARAMETERS 4
#define MAX_SOCKET_INSTANCE 32
/* Node Socket shader parameters. Must match the shader layout of "gpu_shader_2D_node_socket". */
struct [[host_shared]] NodeSocketShaderParameters {
float4 rect;
float4 color_inner;
float4 color_outline;
float outline_thickness;
float outline_offset;
float shape;
float aspect;
};
/* Per link data. */
struct [[host_shared]] NodeLinkData {
float4 start_color;
float4 end_color;
float2 bezier_P0;
float2 bezier_P1;
float2 bezier_P2;
float2 bezier_P3;
uint color_ids;
float dash_length;
float dash_factor;
float dash_alpha;
float dim_factor;
float thickness;
float aspect;
bool32_t do_arrow;
bool32_t do_muted;
bool32_t has_back_link;
float _pad0;
float _pad1;
};
/* Data common to all links. */
struct [[host_shared]] NodeLinkUniformData {
float4 colors[6];
float aspect;
float arrow_size;
float2 _pad;
};
struct [[host_shared]] GPencilStrokeData {
float2 viewport;
float pixsize;
float objscale;
int caps_start;
int caps_end;
bool32_t fill_stroke;
float _pad;
};
struct [[host_shared]] GPUClipPlanes {
float4x4 ClipModelMatrix;
float4 world[6];
};
struct [[host_shared]] SimpleLightingData {
float4 l_color;
packed_float3 light;
float _pad;
};
#define MAX_CALLS 16
struct [[host_shared]] MultiIconCallData {
float4 calls_data[MAX_CALLS * 3];
};
#define GPU_SEQ_STRIP_DRAW_DATA_LEN 256
enum [[host_shared]] GPUSeqFlags : uint32_t {
GPU_SEQ_FLAG_BACKGROUND = (1u << 0u),
GPU_SEQ_FLAG_SINGLE_IMAGE = (1u << 1u),
GPU_SEQ_FLAG_COLOR_BAND = (1u << 2u),
GPU_SEQ_FLAG_TRANSITION = (1u << 3u),
GPU_SEQ_FLAG_LOCKED = (1u << 4u),
GPU_SEQ_FLAG_MISSING_TITLE = (1u << 5u),
GPU_SEQ_FLAG_MISSING_CONTENT = (1u << 6u),
GPU_SEQ_FLAG_SELECTED = (1u << 7u),
GPU_SEQ_FLAG_ACTIVE = (1u << 8u),
GPU_SEQ_FLAG_HIGHLIGHT = (1u << 9u),
GPU_SEQ_FLAG_BORDER = (1u << 10u),
GPU_SEQ_FLAG_SELECTED_LH = (1u << 11u),
GPU_SEQ_FLAG_SELECTED_RH = (1u << 12u),
GPU_SEQ_FLAG_OVERLAP = (1u << 15u),
GPU_SEQ_FLAG_CLAMPED = (1u << 16u),
GPU_SEQ_FLAG_ANY_HANDLE = GPU_SEQ_FLAG_SELECTED_LH | GPU_SEQ_FLAG_SELECTED_RH
};
/* Glyph for text rendering. */
struct [[host_shared]] GlyphQuad {
int4 position;
float4 glyph_color; /* Cannot be name `color` because of metal macros. */
int2 glyph_size;
int offset;
uint flags;
};
/* VSE per-strip data for timeline rendering. */
struct [[host_shared]] SeqStripDrawData {
/* Horizontal strip positions (1.0 is one frame). */
/* Left and right strip sides. */
float left_handle;
float right_handle;
/* Start and end of actual content (only relevant for strips that have holdout regions). */
float content_start;
float content_end;
float handle_width;
/* Vertical strip positions (1.0 is one channel). */
float bottom;
float top;
float strip_content_top; /* Content coordinate, i.e. below title bar if there is one. */
uint flags; /* GPUSeqFlags bitmask. */
/* Strip colors, each is uchar4 packed with equivalent of packUnorm4x8. */
uint col_background;
uint col_outline;
uint col_color_band;
uint col_transition_in;
uint col_transition_out;
float _pad0;
float _pad1;
};
/* clang-format off */ /* Keep one line. Avoid issues with shader error line. */
BLI_STATIC_ASSERT(sizeof(SeqStripDrawData) * GPU_SEQ_STRIP_DRAW_DATA_LEN <= 16384, "SeqStripDrawData UBO must not exceed minspec UBO size (16384)")
/* clang-format on */
/* VSE per-thumbnail data for timeline rendering. */
struct [[host_shared]] SeqStripThumbData {
/* Strip rectangle positions. */
float left;
float right;
float bottom;
float top;
/* Thumbnail rectangle positions. */
float x1;
float y1;
float x2;
float y2;
/* Thumbnail UVs. */
float u1;
float v1;
float u2;
float v2;
float4 tint_color;
};
/* clang-format off */ /* Keep one line. Avoid issues with shader error line. */
BLI_STATIC_ASSERT(sizeof(SeqStripThumbData) * GPU_SEQ_STRIP_DRAW_DATA_LEN <= 16384, "SeqStripThumbData UBO must not exceed minspec UBO size (16384)")
/* clang-format on */
/* VSE global data for timeline rendering. */
struct [[host_shared]] SeqContextDrawData {
float round_radius;
float pixelsize;
uint col_back;
float _pad0;
};
/* VSE scope point rasterizer data. */
struct [[host_shared]] SeqScopeRasterData {
uint col_r;
uint col_g;
uint col_b;
uint col_a;
};
struct [[host_shared]] GreasePencilStrokeData {
packed_float3 position;
float stroke_thickness;
float4 stroke_color;
};
enum [[host_shared]] TestStatus : uint32_t {
TEST_STATUS_NONE = 0u,
TEST_STATUS_PASSED = 1u,
TEST_STATUS_FAILED = 2u,
};
enum [[host_shared]] TestType : uint32_t {
TEST_TYPE_BOOL = 0u,
TEST_TYPE_UINT = 1u,
TEST_TYPE_INT = 2u,
TEST_TYPE_FLOAT = 3u,
TEST_TYPE_IVEC2 = 4u,
TEST_TYPE_IVEC3 = 5u,
TEST_TYPE_IVEC4 = 6u,
TEST_TYPE_UVEC2 = 7u,
TEST_TYPE_UVEC3 = 8u,
TEST_TYPE_UVEC4 = 9u,
TEST_TYPE_VEC2 = 10u,
TEST_TYPE_VEC3 = 11u,
TEST_TYPE_VEC4 = 12u,
TEST_TYPE_MAT2X2 = 13u,
TEST_TYPE_MAT2X3 = 14u,
TEST_TYPE_MAT2X4 = 15u,
TEST_TYPE_MAT3X2 = 16u,
TEST_TYPE_MAT3X3 = 17u,
TEST_TYPE_MAT3X4 = 18u,
TEST_TYPE_MAT4X2 = 19u,
TEST_TYPE_MAT4X3 = 20u,
TEST_TYPE_MAT4X4 = 21u,
};
/** \note Contains arrays of scalar. To be use only with SSBOs to avoid padding issues. */
struct [[host_shared]] TestOutputRawData {
uint data[16];
};
struct [[host_shared]] TestOutput {
struct TestOutputRawData expect;
struct TestOutputRawData result;
/** TestStatus. */
uint status;
/** Line error in the GLSL file. */
int line;
/** TestType of expect and result. */
uint type;
int _pad0;
};
#ifdef GPU_SHADER
TestOutput test_output(TestOutputRawData expect, TestOutputRawData result, bool status, uint type)
{
TestOutput test;
test.expect = expect;
test.result = result;
test.status = status ? TEST_STATUS_PASSED : TEST_STATUS_FAILED;
test.type = type;
return test;
}
#endif

View File

@@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Glue definition to make shared declaration of struct & functions work in both C / C++ and GLSL.
* We use the same vector and matrix types as Blender C++. Some math functions are defined to use
* the float version to match the GLSL syntax.
* This file can be used for C & C++ code and the syntax used should follow the same rules.
* Some preprocessing is done by the GPU back-end to make it GLSL compatible.
*
* IMPORTANT:
* - Always use `u` suffix for enum values. GLSL do not support implicit cast.
* - Define all values. This is in order to simplify custom pre-processor code.
* - (C++ only) Always use `uint32_t` as underlying type (`enum MyEnum : uint32_t`).
* - (C only) do NOT use the enum type inside UBO/SSBO structs and use `uint` instead.
* - Use float suffix by default for float literals to avoid double promotion in C++.
* - Pack one float or int after a vec3/ivec3 to fulfill alignment rules.
*
* NOTE: Due to alignment restriction and buggy drivers, do not try to use mat3 inside structs.
* NOTE: (UBO only) Do not use arrays of float. They are padded to arrays of vec4 and are not worth
* it. This does not apply to SSBO.
*
* IMPORTANT: Do not forget to align mat4, vec3 and vec4 to 16 bytes, and vec2 to 8 bytes.
*
* NOTE: You can use bool type using bool32_t a int boolean type matching the GLSL type.
*/
#pragma once
#ifdef GLSL_CPP_STUBS
# include "gpu_shader_compat.hh"
/* Silence macros when compiling for shaders. */
# define BLI_STATIC_ASSERT(cond, msg)
# define BLI_STATIC_ASSERT_ALIGN(type_, align_)
# define BLI_STATIC_ASSERT_SIZE(type_, size_)
# define ENUM_OPERATORS(a)
# define UNUSED_VARS(a) (void)a
/* Math function renaming. */
# define cosf cos
# define sinf sin
# define tanf tan
# define acosf acos
# define asinf asin
# define atanf atan
# define floorf floor
# define ceilf ceil
# define sqrtf sqrt
# define expf exp
#elif defined(GPU_SHADER)
# include "gpu_shader_compat.hh"
/* Silence macros when compiling for shaders. */
# define BLI_STATIC_ASSERT(cond, msg)
# define BLI_STATIC_ASSERT_ALIGN(type_, align_)
# define BLI_STATIC_ASSERT_SIZE(type_, size_)
# define ENUM_OPERATORS(a)
# define UNUSED_VARS(a)
/* Math function renaming. */
# define cosf cos
# define sinf sin
# define tanf tan
# define acosf acos
# define asinf asin
# define atanf atan
# define floorf floor
# define ceilf ceil
# define sqrtf sqrt
# define expf exp
#else /* C / C++ */
# ifndef GPU_SHADER /* Avoid parsing this into shader code. */
# include "BLI_assert.h"
# include "BLI_enum_flags.hh"
# include "BLI_sys_types.h"
# include <math.h>
# include "BLI_math_matrix_types.hh"
# include "BLI_math_vector_types.hh"
using bool32_t = int32_t;
// using bool2 = blender::int2; /* Size is not consistent across backend. */
// using bool3 = blender::int3; /* Size is not consistent across backend. */
// using bool4 = blender::int4; /* Size is not consistent across backend. */
using blender::float2;
using blender::float4;
using blender::int2;
using blender::int4;
using blender::uint2;
using blender::uint4;
/** IMPORTANT: Do not use in shared struct. Use packed_(float/int/uint)3 instead.
* Here for static functions usage only. */
using blender::float3;
using blender::int3;
using blender::uint3;
/** Packed types are needed for MSL which have different alignment rules for float3. */
using packed_float3 = blender::float3;
using packed_int3 = blender::int3;
using packed_uint3 = blender::uint3;
using blender::float2x2;
// using blender::float3x2; /* Does not follow alignment rules of GPU. */
using blender::float4x2;
// using blender::float2x3; /* Does not follow alignment rules of GPU. */
// using blender::float3x3; /* Does not follow alignment rules of GPU. */
// using blender::float4x3; /* Does not follow alignment rules of GPU. */
using blender::float2x4;
using blender::float3x4;
using blender::float4x4;
/**
* Wrapper type for members of unions in host shared structure.
* Passthrough implementation when compiled with host code.
* See gpu_shader_compat_cxx.hh for the C++ stub implementation.
* This implementation needs to have the correct base type and can safely cast to it.
*/
template<typename T> struct union_t {
T data;
const T &operator()() const
{
return *reinterpret_cast<const T *>(this);
}
T &operator()()
{
return *reinterpret_cast<T *>(this);
}
};
/* Mute the following attributes. Avoid warning about unknown attribute.
* These are parsed by our shader translation tool. */
/* To be used on struct. Means the layout is to be used with uniform or storage buffers. */
# define host_shared
# endif
#endif

View File

@@ -0,0 +1,243 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_sys_types.h"
namespace blender {
/** Opaque type hiding gpu::Fence. */
struct GPUFence;
enum GPUWriteMask {
GPU_WRITE_NONE = 0,
GPU_WRITE_RED = (1 << 0),
GPU_WRITE_GREEN = (1 << 1),
GPU_WRITE_BLUE = (1 << 2),
GPU_WRITE_ALPHA = (1 << 3),
GPU_WRITE_DEPTH = (1 << 4),
GPU_WRITE_STENCIL = (1 << 5),
GPU_WRITE_COLOR = (GPU_WRITE_RED | GPU_WRITE_GREEN | GPU_WRITE_BLUE | GPU_WRITE_ALPHA),
};
ENUM_OPERATORS(GPUWriteMask)
enum GPUBarrier {
/* Texture Barrier. */
/** All written texture prior to this barrier can be bound as frame-buffer attachment. */
GPU_BARRIER_FRAMEBUFFER = (1 << 0),
/** All written texture prior to this barrier can be bound as image. */
GPU_BARRIER_SHADER_IMAGE_ACCESS = (1 << 1),
/** All written texture prior to this barrier can be bound as sampler. */
GPU_BARRIER_TEXTURE_FETCH = (1 << 2),
/** All written texture prior to this barrier can be read or updated with CPU memory. */
GPU_BARRIER_TEXTURE_UPDATE = (1 << 3),
/** All written texture prior to this barrier can be read or updated with PBO. */
// GPU_BARRIER_PIXEL_BUFFER = (1 << 4), /* Not implemented yet. */
/* Buffer Barrier. */
/** All written buffer prior to this barrier can be bound as indirect command buffer. */
GPU_BARRIER_COMMAND = (1 << 10),
/** All written buffer prior to this barrier can be bound as SSBO. */
GPU_BARRIER_SHADER_STORAGE = (1 << 11),
/** All written buffer prior to this barrier can be bound as VBO. */
GPU_BARRIER_VERTEX_ATTRIB_ARRAY = (1 << 12),
/** All written buffer prior to this barrier can be bound as IBO. */
GPU_BARRIER_ELEMENT_ARRAY = (1 << 13),
/** All written buffer prior to this barrier can be bound as UBO. */
GPU_BARRIER_UNIFORM = (1 << 14),
/** All written buffer prior to this barrier can be read or updated with CPU memory. */
GPU_BARRIER_BUFFER_UPDATE = (1 << 15),
/** All written persistent mapped buffer prior to this barrier can be read or updated. */
// GPU_BARRIER_CLIENT_MAPPED_BUFFER = (1 << 15), /* Not implemented yet. */
};
ENUM_OPERATORS(GPUBarrier)
/* NOTE: For Metal and Vulkan only.
* TODO(Metal): Update barrier calls to use stage flags. */
enum GPUStageBarrierBits {
GPU_BARRIER_STAGE_VERTEX = (1 << 0),
GPU_BARRIER_STAGE_FRAGMENT = (1 << 1),
GPU_BARRIER_STAGE_COMPUTE = (1 << 2),
GPU_BARRIER_STAGE_ANY_GRAPHICS = (GPU_BARRIER_STAGE_VERTEX | GPU_BARRIER_STAGE_FRAGMENT),
GPU_BARRIER_STAGE_ANY = (GPU_BARRIER_STAGE_VERTEX | GPU_BARRIER_STAGE_FRAGMENT |
GPU_BARRIER_STAGE_COMPUTE),
};
ENUM_OPERATORS(GPUStageBarrierBits)
/**
* Defines the fixed pipeline blending equation.
* SRC is the output color from the shader.
* DST is the color from the frame-buffer.
* The blending equation is:
* `(SRC * A) + (DST * B)`.
* The blend mode will modify the A and B parameters.
*/
enum GPUBlend {
GPU_BLEND_NONE = 0,
/** Pre-multiply variants will _NOT_ multiply rgb output by alpha. */
GPU_BLEND_ALPHA,
GPU_BLEND_ALPHA_PREMULT,
GPU_BLEND_ADDITIVE,
GPU_BLEND_ADDITIVE_PREMULT,
GPU_BLEND_MULTIPLY,
GPU_BLEND_SUBTRACT,
/** Replace logic op: SRC * (1 - DST)
* NOTE: Does not modify alpha. */
GPU_BLEND_INVERT,
/** Stores min(SRC, DST) per component. */
GPU_BLEND_MIN,
/** Stores max(SRC, DST) per component. */
GPU_BLEND_MAX,
/** Order independent transparency.
* NOTE: Cannot be used as is. Needs special setup (frame-buffer, shader ...). */
GPU_BLEND_OIT,
/** Special blend to add color under and multiply DST color by SRC alpha. */
GPU_BLEND_BACKGROUND,
/** Custom blend parameters using dual source blending : SRC0 + SRC1 * DST
* NOTE: Can only be used with _ONE_ Draw Buffer and shader needs to be specialized. */
GPU_BLEND_CUSTOM,
GPU_BLEND_ALPHA_UNDER_PREMUL,
/** Multiplies every channel (alpha included) by `1 - SRC.a`. Used for piercing a hole using an
* image alpha channel. */
GPU_BLEND_OVERLAY_MASK_FROM_ALPHA,
/**
* Alpha channel is interpreted as transmittance (aka transparency) and not alpha.
* To be used with a frame-buffer with alpha cleared to 1 for full transparency.
* Equivalent to: `DST.rgba * SRC.a + float4(SRC.rgb, 0.0)`.
*/
GPU_BLEND_TRANSPARENCY,
};
enum GPUDepthTest {
GPU_DEPTH_NONE = 0,
GPU_DEPTH_ALWAYS, /* Used to draw to the depth buffer without really testing. */
GPU_DEPTH_LESS,
GPU_DEPTH_LESS_EQUAL, /* Default. */
GPU_DEPTH_EQUAL,
GPU_DEPTH_GREATER,
GPU_DEPTH_GREATER_EQUAL,
};
enum GPUStencilTest {
GPU_STENCIL_NONE = 0,
GPU_STENCIL_ALWAYS,
GPU_STENCIL_EQUAL,
GPU_STENCIL_NEQUAL,
};
enum GPUStencilOp {
GPU_STENCIL_OP_NONE = 0,
GPU_STENCIL_OP_REPLACE,
/** Special values for stencil shadows. */
GPU_STENCIL_OP_COUNT_DEPTH_PASS,
GPU_STENCIL_OP_COUNT_DEPTH_FAIL,
};
enum GPUFaceCullTest {
GPU_CULL_NONE = 0, /* Culling disabled. */
GPU_CULL_FRONT,
GPU_CULL_BACK,
};
enum GPUProvokingVertex {
GPU_VERTEX_LAST = 0, /* Default. */
GPU_VERTEX_FIRST = 1, /* Follow Blender loop order. */
};
void GPU_blend(GPUBlend blend);
void GPU_face_culling(GPUFaceCullTest culling);
void GPU_depth_test(GPUDepthTest test);
void GPU_stencil_test(GPUStencilTest test);
void GPU_provoking_vertex(GPUProvokingVertex vert);
void GPU_front_facing(bool invert);
void GPU_scissor_test(bool enable);
void GPU_line_smooth(bool enable);
/**
* \note By convention, this is set as needed and not reset back to 1.0.
* This means code that draws lines must always set the line width beforehand,
* but is not expected to restore its previous value.
*/
void GPU_line_width(float width);
void GPU_logic_op_xor_set(bool enable);
void GPU_point_size(float size);
void GPU_polygon_smooth(bool enable);
/**
* Programmable point size:
* - Shaders set their own point size when enabled
* - Use GPU_point_size when disabled.
*
* TODO: remove and use program point size everywhere.
*/
void GPU_program_point_size(bool enable);
void GPU_scissor(int x, int y, int width, int height);
void GPU_scissor_get(int coords[4]);
void GPU_viewport(int x, int y, int width, int height);
void GPU_viewport_size_get_f(float coords[4]);
void GPU_viewport_size_get_i(int coords[4]);
void GPU_write_mask(GPUWriteMask mask);
void GPU_color_mask(bool r, bool g, bool b, bool a);
void GPU_depth_mask(bool depth);
bool GPU_depth_mask_get();
void GPU_clip_distances(int distances_enabled);
void GPU_state_set(GPUWriteMask write_mask,
GPUBlend blend,
GPUFaceCullTest culling_test,
GPUDepthTest depth_test,
GPUStencilTest stencil_test,
GPUStencilOp stencil_op,
GPUProvokingVertex provoking_vert);
void GPU_stencil_reference_set(uint reference);
void GPU_stencil_write_mask_set(uint write_mask);
void GPU_stencil_compare_mask_set(uint compare_mask);
/* Sets the depth range to be 0..1. Only have effect with the OpenGL backend. Have no effect if
* glClipControl is not supported. Shaders used for drawing with this state must use
* BuiltinBits::CLIP_CONTROL for their vertex shader to be patched. */
void GPU_clip_control_unit_range(bool enable);
GPUFaceCullTest GPU_face_culling_get();
GPUBlend GPU_blend_get();
GPUDepthTest GPU_depth_test_get();
GPUWriteMask GPU_write_mask_get();
uint GPU_stencil_mask_get();
GPUStencilTest GPU_stencil_test_get();
/**
* \note Already pre-multiplied by `U.pixelsize`.
*/
float GPU_line_width_get();
bool GPU_line_smooth_get();
void GPU_flush();
void GPU_finish();
void GPU_apply_state();
/**
* A barrier _must_ be issued _after_ a shader arbitrary write to a buffer or a
* texture (i.e: using imageStore, imageAtomics, or SSBO). Otherwise, the written value may not
* appear updated to the next user of this resource.
*
* The type of barrier must be chosen depending on the _future_ use of the memory that was written
* by the shader.
*/
void GPU_memory_barrier(GPUBarrier barrier);
GPUFence *GPU_fence_create();
void GPU_fence_free(GPUFence *fence);
void GPU_fence_signal(GPUFence *fence);
void GPU_fence_wait(GPUFence *fence);
} // namespace blender

View File

@@ -0,0 +1,98 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Storage buffers API. Used to handle many way bigger buffers than Uniform buffers update at once.
* Make sure that the data structure is compatible with what the implementation expect.
* (see "7.8 Shader Buffer Variables and Shader Storage Blocks" from the OpenGL spec for more info
* about std430 layout)
* Rule of thumb: Padding to 16bytes, don't use vec3.
*/
#pragma once
#include "GPU_texture.hh"
#include "GPU_vertex_buffer.hh"
namespace blender {
namespace gpu {
class StorageBuf;
} // namespace gpu
gpu::StorageBuf *GPU_storagebuf_create_ex(size_t size,
const void *data,
GPUUsageType usage,
const char *name);
#define GPU_storagebuf_create(size) \
GPU_storagebuf_create_ex(size, nullptr, GPU_USAGE_DYNAMIC, __func__);
void GPU_storagebuf_free(gpu::StorageBuf *ssbo);
/**
* Limit the size of the storage buffer.
*
* Backends can optimize data transfers using the size that is actually used.
*/
void GPU_storagebuf_usage_size_set(gpu::StorageBuf *ssbo, size_t size);
void GPU_storagebuf_update(gpu::StorageBuf *ssbo, const void *data);
void GPU_storagebuf_bind(gpu::StorageBuf *ssbo, int slot);
void GPU_storagebuf_unbind(gpu::StorageBuf *ssbo);
/**
* Resets the internal slot usage tracking. But there is no guarantee that
* this actually undo the bindings for the next draw call. Only has effect when G_DEBUG_GPU is set.
*/
void GPU_storagebuf_debug_unbind_all();
void GPU_storagebuf_clear_to_zero(gpu::StorageBuf *ssbo);
/**
* Clear the content of the buffer using the given #clear_value. #clear_value will be used as a
* repeatable pattern of 32bits.
*/
void GPU_storagebuf_clear(gpu::StorageBuf *ssbo, uint32_t clear_value);
/**
* Explicitly sync updated storage buffer contents back to host within the GPU command stream. This
* ensures any changes made by the GPU are visible to the host.
* NOTE: This command is only valid for host-visible storage buffers.
*/
void GPU_storagebuf_sync_to_host(gpu::StorageBuf *ssbo);
/**
* Read back content of the buffer to CPU for inspection.
* Slow! Only use for inspection / debugging.
*
* NOTE: If GPU_storagebuf_sync_to_host is called, this command is synchronized against that call.
* If pending GPU updates to the storage buffer are not yet visible to the host, the command will
* stall until dependent GPU work has completed.
*
* Otherwise, this command is synchronized against this call and will stall the CPU until the
* buffer content can be read by the host.
*/
void GPU_storagebuf_read(gpu::StorageBuf *ssbo, void *data);
/**
* \brief Copy a part of a vertex buffer to a storage buffer.
*
* \param ssbo: destination storage buffer
* \param src: source vertex buffer
* \param dst_offset: where to start copying to (in bytes).
* \param src_offset: where to start copying from (in bytes).
* \param copy_size: byte size of the segment to copy.
*/
void GPU_storagebuf_copy_sub_from_vertbuf(
gpu::StorageBuf *ssbo, gpu::VertBuf *src, uint dst_offset, uint src_offset, uint copy_size);
/**
* Ensure the ssbo is ready to be used as an indirect buffer in `GPU_batch_draw_indirect`.
* NOTE: Internally, this is only required for the OpenGL backend.
*/
void GPU_storagebuf_sync_as_indirect_buffer(gpu::StorageBuf *ssbo);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* A `gpu::TextureFromPool` is a wrapper around backend specific texture objects whose usage is
* transient and can be shared between parts of an engine or across several parts of blender.
*/
#pragma once
#include "BLI_math_vector_types.hh"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "GPU_texture.hh"
namespace blender::gpu {
class TexturePool {
public:
virtual ~TexturePool() = default;
/* Return the texture pool from the active GPUContext.
* Only valid if a context is active. */
static TexturePool &get();
/* Acquire a texture of a particular type from the pool. */
virtual Texture *acquire_texture_1d(int extent,
int mip_len,
TextureFormat format,
eGPUTextureUsage usage,
const char *name = nullptr) = 0;
virtual Texture *acquire_texture_1d_array(int extent,
int layer_len,
int mip_len,
TextureFormat format,
eGPUTextureUsage usage,
const char *name = nullptr) = 0;
virtual Texture *acquire_texture_2d(int2 extent,
int mip_len,
TextureFormat format,
eGPUTextureUsage usage,
const char *name = nullptr) = 0;
virtual Texture *acquire_texture_2d_array(int2 extent,
int layer_len,
int mip_len,
TextureFormat format,
eGPUTextureUsage usage,
const char *name = nullptr) = 0;
virtual Texture *acquire_texture_3d(int3 extent,
int mip_len,
TextureFormat format,
eGPUTextureUsage usage,
const char *name = nullptr) = 0;
virtual Texture *acquire_texture_cube(int extent,
int mip_len,
TextureFormat format,
eGPUTextureUsage usage,
const char *name = nullptr) = 0;
virtual Texture *acquire_texture_cube_array(int extent,
int layer_len,
int mip_len,
TextureFormat format,
eGPUTextureUsage usage,
const char *name = nullptr) = 0;
/* Release the texture back into the pool so it can be reused. */
virtual void release_texture(Texture *tex) = 0;
/* Validate acquired texture counters and release unused textures.
* If `force_free` is true, free unused texture memory inside the pool. */
virtual void reset(bool force_free = false) = 0;
/* Modify the internal counter of an acquired texture.
* Used by `TextureFromPool::retain()` in `DRW_gpu_wrapper.hh`. */
virtual void offset_users_count(Texture *tex, int offset) = 0;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Uniform buffers API. Used to handle many uniforms update at once.
* Make sure that the data structure is compatible with what the implementation expect.
* (see "7.6.2.2 Standard Uniform Block Layout" from the OpenGL spec for more info about std140
* layout)
* Rule of thumb: Padding to 16bytes, don't use vec3, don't use arrays of anything that is not vec4
* aligned.
*/
#pragma once
#include "BLI_sys_types.h"
#include "DNA_listBase.h"
namespace blender {
struct GPUInput;
namespace gpu {
class UniformBuf;
} // namespace gpu
gpu::UniformBuf *GPU_uniformbuf_create_ex(size_t size, const void *data, const char *name);
/**
* Create UBO from inputs list.
*
* \param inputs: ListBaseT<LinkData>
* \return nullptr if failed to create or if `inputs` is empty.
*/
gpu::UniformBuf *GPU_uniformbuf_create_from_list(ListBaseT<LinkData> *inputs, const char *name);
#define GPU_uniformbuf_create(size) GPU_uniformbuf_create_ex(size, nullptr, __func__);
void GPU_uniformbuf_free(gpu::UniformBuf *ubo);
void GPU_uniformbuf_update(gpu::UniformBuf *ubo, const void *data);
void GPU_uniformbuf_bind(gpu::UniformBuf *ubo, int slot);
void GPU_uniformbuf_bind_as_ssbo(gpu::UniformBuf *ubo, int slot);
void GPU_uniformbuf_unbind(gpu::UniformBuf *ubo);
/**
* Resets the internal slot usage tracking. But there is no guarantee that
* this actually undo the bindings for the next draw call. Only has effect when G_DEBUG_GPU is set.
*/
void GPU_uniformbuf_debug_unbind_all();
void GPU_uniformbuf_clear_to_zero(gpu::UniformBuf *ubo);
#define GPU_UBO_BLOCK_NAME "node_tree"
#define GPU_ATTRIBUTE_UBO_BLOCK_NAME "unf_attrs"
#define GPU_LAYER_ATTRIBUTE_UBO_BLOCK_NAME "drw_layer_attrs"
constexpr static int GPU_NODE_TREE_UBO_SLOT = 0;
#define GPU_UBO_FREE_SAFE(ubo) \
do { \
if (ubo != nullptr) { \
GPU_uniformbuf_free(ubo); \
ubo = nullptr; \
} \
} while (0)
} // namespace blender

View File

@@ -0,0 +1,362 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU vertex buffer
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_math_base.h"
#include "BLI_span.hh"
#include "BLI_virtual_array.hh"
#include "GPU_common.hh"
#include "GPU_vertex_format.hh"
namespace blender {
enum GPUVertBufStatus {
/** Initial state. */
GPU_VERTBUF_INVALID = 0,
/** Was init with a vertex format. */
GPU_VERTBUF_INIT = (1 << 0),
/** Data has been touched and need to be re-uploaded. */
GPU_VERTBUF_DATA_DIRTY = (1 << 1),
/** The buffer has been created inside GPU memory. */
GPU_VERTBUF_DATA_UPLOADED = (1 << 2),
};
ENUM_OPERATORS(GPUVertBufStatus)
/**
* How to create a #VertBuf:
* 1) verts = GPU_vertbuf_calloc()
* 2) GPU_vertformat_attr_add(verts->format, ...)
* 3) GPU_vertbuf_data_alloc(verts, vertex_len) <-- finalizes/packs vertex format
* 4) GPU_vertbuf_attr_fill(verts, pos, application_pos_buffer)
*/
enum GPUUsageType {
/* can be extended to support more types */
GPU_USAGE_STREAM = 0,
GPU_USAGE_STATIC = 1, /* do not keep data in memory */
GPU_USAGE_DYNAMIC = 2,
GPU_USAGE_DEVICE_ONLY = 3, /* Do not do host->device data transfers. */
/** Extended usage flags. */
/* Flag for vertex buffers used for textures. Skips additional padding/compaction to ensure
* format matches the texture exactly. Can be masked with other properties, and is stripped
* during VertBuf::init. */
GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY = 1 << 3,
};
ENUM_OPERATORS(GPUUsageType);
namespace gpu {
class VertBuf;
} // namespace gpu
void GPU_vertbuf_discard(gpu::VertBuf *);
gpu::VertBuf *GPU_vertbuf_calloc();
gpu::VertBuf *GPU_vertbuf_create_with_format_ex(const GPUVertFormat &format, GPUUsageType usage);
static inline gpu::VertBuf *GPU_vertbuf_create_with_format(const GPUVertFormat &format)
{
return GPU_vertbuf_create_with_format_ex(format, GPU_USAGE_STATIC);
}
namespace gpu {
class VertBufDeleter {
public:
void operator()(VertBuf *vbo)
{
GPU_vertbuf_discard(vbo);
}
};
using VertBufPtr = std::unique_ptr<gpu::VertBuf, gpu::VertBufDeleter>;
/**
* Implementation of Vertex Buffers.
* Base class which is then specialized for each implementation (GL, VK, ...).
*/
class VertBuf {
public:
static size_t memory_usage;
GPUVertFormat format = {};
/** Number of verts we want to draw. */
uint vertex_len = 0;
/** Number of verts data. */
uint vertex_alloc = 0;
/** Status flag. */
GPUVertBufStatus flag = GPU_VERTBUF_INVALID;
#ifndef NDEBUG
/** Usage including extended usage flags. */
GPUUsageType extended_usage_ = GPU_USAGE_STATIC;
#endif
protected:
/** NULL indicates data in VRAM (unmapped) */
uchar *data_ = nullptr;
/** Usage hint for GL optimization. */
GPUUsageType usage_ = GPU_USAGE_STATIC;
private:
/** This counter will only avoid freeing the #VertBuf, not the data. */
int handle_refcount_ = 1;
public:
VertBuf();
virtual ~VertBuf();
template<typename FormatT>
static VertBufPtr from_size_with_format(const int size, GPUUsageType usage = GPU_USAGE_STATIC)
{
BLI_assert(size > 0);
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(FormatT::format(), usage));
/* GPU formats needs to be aligned to 4 bytes. */
buf->allocate(ceil_to_multiple_u(size * sizeof(FormatT), 4) / sizeof(FormatT));
return buf;
}
template<typename T>
static VertBufPtr from_size(const int size, GPUUsageType usage = GPU_USAGE_STATIC)
{
return from_size_with_format<GenericVertexFormat<T>>(size, usage);
}
template<typename T> static VertBufPtr from_span(const Span<T> data)
{
BLI_assert(!data.is_empty());
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
GenericVertexFormat<T>::format(), GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
/* GPU formats needs to be aligned to 4 bytes. */
buf->allocate(ceil_to_multiple_u(data.size_in_bytes(), 4) / sizeof(GenericVertexFormat<T>));
buf->data<T>().slice(0, data.size()).copy_from(data);
return buf;
}
template<typename T> static VertBufPtr from_varray(const VArray<T> &array)
{
BLI_assert(!array.is_empty());
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
GenericVertexFormat<T>::format(), GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
/* GPU formats needs to be aligned to 4 bytes. */
buf->allocate(ceil_to_multiple_u(array.size() * sizeof(T), 4) /
sizeof(GenericVertexFormat<T>));
array.materialize(buf->data<T>().slice(0, array.size()));
return buf;
}
template<typename T> static VertBufPtr device_only(uint size)
{
BLI_assert(size > 0);
VertBufPtr buf = VertBufPtr(GPU_vertbuf_create_with_format_ex(
GenericVertexFormat<T>::format(),
GPU_USAGE_DEVICE_ONLY | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY));
buf->allocate(size);
return buf;
}
void init(const GPUVertFormat &format, GPUUsageType usage);
void clear();
/* Data management. */
void allocate(uint vert_len);
void resize(uint vert_len);
void upload();
virtual void bind_as_ssbo(uint binding) = 0;
virtual void bind_as_texture(uint binding) = 0;
virtual void wrap_handle(uint64_t handle) = 0;
/* Size of the data allocated. */
size_t size_alloc_get() const
{
BLI_assert(this->format.packed);
return size_t(this->vertex_alloc) * this->format.stride;
}
/* Size of the data uploaded to the GPU. */
size_t size_used_get() const
{
BLI_assert(format.packed);
return size_t(this->vertex_len) * this->format.stride;
}
void reference_add()
{
handle_refcount_++;
}
void reference_remove()
{
BLI_assert(handle_refcount_ > 0);
handle_refcount_--;
if (handle_refcount_ == 0) {
delete this;
}
}
GPUUsageType get_usage_type() const
{
return usage_;
}
/**
* Returns access to the data allocated for the vertex buffer. The size of the data type must
* match the data type used on the GPU.
*/
template<typename T> MutableSpan<T> data()
{
return MutableSpan<uchar>(data_, this->size_alloc_get()).cast<T>();
}
virtual void update_sub(uint start, uint len, const void *data) = 0;
virtual void read(void *data) const = 0;
protected:
virtual void acquire_data() = 0;
virtual void resize_data() = 0;
virtual void release_data() = 0;
virtual void upload_data() = 0;
};
} // namespace gpu
/**
* (Download and) fill data with the data from the vertex buffer.
* NOTE: caller is responsible to reserve enough memory of the data parameter.
*/
void GPU_vertbuf_read(const gpu::VertBuf *verts, void *data);
/** Same as discard but does not free. */
void GPU_vertbuf_clear(gpu::VertBuf *verts);
/**
* Avoid gpu::VertBuf data-block being free but not its data.
*/
void GPU_vertbuf_handle_ref_add(gpu::VertBuf *verts);
void GPU_vertbuf_handle_ref_remove(gpu::VertBuf *verts);
void GPU_vertbuf_init_with_format_ex(gpu::VertBuf &verts,
const GPUVertFormat &format,
GPUUsageType);
void GPU_vertbuf_init_build_on_device(gpu::VertBuf &verts,
const GPUVertFormat &format,
uint v_len);
gpu::VertBuf *GPU_vertbuf_create_on_device(const GPUVertFormat &format, uint v_len);
#define GPU_vertbuf_init_with_format(verts, format) \
GPU_vertbuf_init_with_format_ex(verts, format, GPU_USAGE_STATIC)
/**
* Create a new allocation, discarding any existing data.
*/
void GPU_vertbuf_data_alloc(gpu::VertBuf &verts, uint v_len);
/**
* Resize buffer keeping existing data.
*/
void GPU_vertbuf_data_resize(gpu::VertBuf &verts, uint v_len);
/**
* Set vertex count but does not change allocation.
* Only this many verts will be uploaded to the GPU and rendered.
* This is useful for streaming data.
*/
void GPU_vertbuf_data_len_set(gpu::VertBuf &verts, uint v_len);
/**
* The most important #set_attr variant is the untyped one. Get it right first.
* It takes a void* so the app developer is responsible for matching their app data types
* to the vertex attribute's type and component count. They're in control of both, so this
* should not be a problem.
*/
void GPU_vertbuf_attr_set(gpu::VertBuf *, uint a_idx, uint v_idx, const void *data);
/** Fills a whole vertex (all attributes). Data must match packed layout. */
void GPU_vertbuf_vert_set(gpu::VertBuf *verts, uint v_idx, const void *data);
/**
* Tightly packed, non interleaved input data.
*/
void GPU_vertbuf_attr_fill(gpu::VertBuf *, uint a_idx, const void *data);
void GPU_vertbuf_attr_fill_stride(gpu::VertBuf *, uint a_idx, uint stride, const void *data);
/**
* For low level access only.
*
* \note This is obsolete, use #VertBuf::data<T>() instead.
*/
struct GPUVertBufRaw {
uint size;
uint stride;
unsigned char *data;
unsigned char *data_init;
#ifndef NDEBUG
/* Only for overflow check */
unsigned char *_data_end;
#endif
};
GPU_INLINE void *GPU_vertbuf_raw_step(GPUVertBufRaw *a)
{
unsigned char *data = a->data;
a->data += a->stride;
#ifndef NDEBUG
BLI_assert(data < a->_data_end);
#endif
return static_cast<void *>(data);
}
GPU_INLINE uint GPU_vertbuf_raw_used(const GPUVertBufRaw *a)
{
return ((a->data - a->data_init) / a->stride);
}
void GPU_vertbuf_attr_get_raw_data(gpu::VertBuf *, uint a_idx, GPUVertBufRaw *access);
const GPUVertFormat *GPU_vertbuf_get_format(const gpu::VertBuf *verts);
uint GPU_vertbuf_get_vertex_alloc(const gpu::VertBuf *verts);
uint GPU_vertbuf_get_vertex_len(const gpu::VertBuf *verts);
GPUVertBufStatus GPU_vertbuf_get_status(const gpu::VertBuf *verts);
void GPU_vertbuf_tag_dirty(gpu::VertBuf *verts);
/**
* Should be rename to #GPU_vertbuf_data_upload.
*/
void GPU_vertbuf_use(gpu::VertBuf *);
void GPU_vertbuf_bind_as_ssbo(gpu::VertBuf *verts, int binding);
void GPU_vertbuf_bind_as_texture(gpu::VertBuf *verts, int binding);
void GPU_vertbuf_wrap_handle(gpu::VertBuf *verts, uint64_t handle);
/**
* XXX: do not use!
* This is just a wrapper for the use of the Hair refine workaround.
* To be used with #GPU_vertbuf_use().
*/
void GPU_vertbuf_update_sub(gpu::VertBuf *verts, uint start, uint len, const void *data);
/* Metrics */
uint GPU_vertbuf_get_memory_usage();
/* Macros */
#define GPU_VERTBUF_DISCARD_SAFE(verts) \
do { \
if (verts != nullptr) { \
GPU_vertbuf_discard(verts); \
verts = nullptr; \
} \
} while (0)
} // namespace blender

View File

@@ -0,0 +1,412 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU vertex format
*/
#pragma once
#include "BLI_assert.h"
#include "BLI_compiler_compat.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "BLI_sys_types.h"
#include "GPU_format.hh"
namespace blender {
namespace gpu {
enum class VertAttrType : uint8_t {
Invalid = 0,
#define DECLARE(a, b, c, blender_enum, d, e, f, g, h) blender_enum = int(DataFormat::blender_enum),
#define GPU_VERTEX_FORMAT_EXPAND(impl) \
SNORM_8_8_8_8_(impl) \
\
SNORM_16_16_(impl) \
SNORM_16_16_16_16_(impl) \
\
UNORM_8_8_8_8_(impl) \
\
UNORM_16_16_(impl) \
UNORM_16_16_16_16_(impl) \
\
SINT_8_8_8_8_(impl) \
\
SINT_16_16_(impl) \
SINT_16_16_16_16_(impl) \
\
SINT_32_(impl) \
SINT_32_32_(impl) \
SINT_32_32_32_(impl) \
SINT_32_32_32_32_(impl) \
\
UINT_8_8_8_8_(impl) \
\
UINT_16_16_(impl) \
UINT_16_16_16_16_(impl) \
\
UINT_32_(impl) \
UINT_32_32_(impl) \
UINT_32_32_32_(impl) \
UINT_32_32_32_32_(impl) \
\
SFLOAT_32_(impl) \
SFLOAT_32_32_(impl) \
SFLOAT_32_32_32_(impl) \
SFLOAT_32_32_32_32_(impl) \
\
SNORM_10_10_10_2_(impl) \
UNORM_10_10_10_2_(impl) \
\
/* UFLOAT_11_11_10_(impl) Available on Metal (and maybe VK) but not on GL. */ \
/* UFLOAT_9_9_9_EXP_5_(impl) Available on Metal (and maybe VK) but not on GL. */
GPU_VERTEX_FORMAT_EXPAND(DECLARE)
#undef DECLARE
#define DECLARE(a, b, c, blender_enum, d, e, f, g, h) \
blender_enum##_DEPRECATED = int(DataFormat::blender_enum),
/* Deprecated formats. To be removed in 5.0. Needed for python shaders. */
#define GPU_VERTEX_DEPRECATED_FORMAT_EXPAND(impl) \
SNORM_8_(impl) \
SNORM_8_8_(impl) \
SNORM_8_8_8_(impl) \
SNORM_16_(impl) \
SNORM_16_16_16_(impl) \
UNORM_8_(impl) \
UNORM_8_8_(impl) \
UNORM_8_8_8_(impl) \
UNORM_16_(impl) \
UNORM_16_16_16_(impl) \
SINT_8_(impl) \
SINT_8_8_(impl) \
SINT_8_8_8_(impl) \
SINT_16_(impl) \
SINT_16_16_16_(impl) \
UINT_8_(impl) \
UINT_8_8_(impl) \
UINT_8_8_8_(impl) \
UINT_16_(impl) \
UINT_16_16_16_(impl)
GPU_VERTEX_DEPRECATED_FORMAT_EXPAND(DECLARE)
#undef DECLARE
};
/* TODO: Should reuse GPU_VERTEX_FORMAT_EXPAND, but we need to have `s/unorm` types first. */
#define GPU_VERTEX_FORMAT_EXPAND_TYPED(impl) \
SINT_8_8_8_8_(impl) \
\
SINT_16_16_(impl) \
SINT_16_16_16_16_(impl) \
\
SINT_32_(impl) \
SINT_32_32_(impl) \
SINT_32_32_32_(impl) \
SINT_32_32_32_32_(impl) \
\
UINT_8_8_8_8_(impl) \
\
UINT_16_16_(impl) \
UINT_16_16_16_16_(impl) \
\
UINT_32_(impl) \
UINT_32_32_(impl) \
UINT_32_32_32_(impl) \
UINT_32_32_32_32_(impl) \
\
SFLOAT_32_(impl) \
SFLOAT_32_32_(impl) \
SFLOAT_32_32_32_(impl) \
SFLOAT_32_32_32_32_(impl) \
\
/* UFLOAT_11_11_10_(impl) Available on Metal (and maybe VK) but not on GL. */ \
/* UFLOAT_9_9_9_EXP_5_(impl) Available on Metal (and maybe VK) but not on GL. */
/* Must be implemented for each type used in vertex format.
* Should contain the associated VertAttrType just like below. */
template<typename T> struct AttrType {};
#define ATTR_TYPE_MAPPING(_type, b, c, blender_enum, d, e, f, g, h) \
template<> struct AttrType<_type> { \
static constexpr VertAttrType type = VertAttrType::blender_enum; \
};
GPU_VERTEX_FORMAT_EXPAND_TYPED(ATTR_TYPE_MAPPING)
#undef ATTR_TYPE_MAPPING
constexpr DataFormat to_data_format(VertAttrType format)
{
return DataFormat(int(format));
}
class Shader;
} // namespace gpu
constexpr static int GPU_VERT_ATTR_MAX_LEN = 16;
constexpr static int GPU_VERT_ATTR_MAX_NAMES = 6;
constexpr static int GPU_VERT_ATTR_NAMES_BUF_LEN = 256;
constexpr static int GPU_VERT_FORMAT_MAX_NAMES = 63; /* More than enough, actual max is ~30. */
/* Computed as GPU_VERT_ATTR_NAMES_BUF_LEN / 30 (actual max format name). */
constexpr static int GPU_MAX_SAFE_ATTR_NAME = 12;
enum GPUVertCompType {
GPU_COMP_I8 = 0,
GPU_COMP_U8,
GPU_COMP_I16,
GPU_COMP_U16,
GPU_COMP_I32,
GPU_COMP_U32,
GPU_COMP_F32,
GPU_COMP_I10,
/* Warning! adjust GPUVertAttr if changing. */
GPU_COMP_MAX
};
enum GPUVertFetchMode {
GPU_FETCH_FLOAT = 0,
GPU_FETCH_INT,
GPU_FETCH_INT_TO_FLOAT_UNIT, /* 127 (ubyte) -> 0.5 (and so on for other int types) */
/* Warning! adjust GPUVertAttr if changing. */
};
struct GPUVertAttr {
/* To replace fetch_mode, comp_type, comp_len, size. */
struct Type {
gpu::VertAttrType format;
size_t size() const
{
return to_bytesize(to_data_format(format));
};
int comp_len() const
{
return format_component_len(to_data_format(format));
}
GPUVertFetchMode fetch_mode() const;
GPUVertCompType comp_type() const;
} type;
/* from beginning of vertex, in bytes */
uint8_t offset;
/* up to GPU_VERT_ATTR_MAX_NAMES */
uint8_t name_len;
uchar names[GPU_VERT_ATTR_MAX_NAMES];
};
BLI_STATIC_ASSERT(GPU_VERT_ATTR_NAMES_BUF_LEN <= 256,
"We use uchar as index inside the name buffer "
"so GPU_VERT_ATTR_NAMES_BUF_LEN needs to be "
"smaller than GPUVertFormat->name_offset and "
"GPUVertAttr->names maximum value");
struct GPUVertFormat {
/** 0 to 16 (GPU_VERT_ATTR_MAX_LEN). */
uint attr_len : 5;
/** Total count of active vertex attribute names. (max GPU_VERT_FORMAT_MAX_NAMES) */
uint name_len : 6;
/** Stride in bytes, 1 to 1024. */
uint stride : 11;
/** Has the format been packed. */
uint packed : 1;
/** Current offset in names[]. */
uint name_offset : 8;
/** Store each attribute in one contiguous buffer region. */
uint deinterleaved : 1;
GPUVertAttr attrs[GPU_VERT_ATTR_MAX_LEN];
char names[GPU_VERT_ATTR_NAMES_BUF_LEN];
void pack();
uint attribute_add(StringRef name, gpu::VertAttrType type, size_t offset = -1);
};
#define GPU_VERTEX_FORMAT_ADD_ATTR(attr) \
format.attribute_add(#attr, gpu::AttrType<decltype(attr)>::type, offsetof(VertT, attr)); \
BLI_STATIC_ASSERT(offsetof(VertT, attr) < 255, #attr " has offset greater than 255") \
BLI_STATIC_ASSERT(offsetof(VertT, attr) % 4 == 0, #attr " is not aligned to 4 bytes")
#define _ATTR_EXPAND1(a) GPU_VERTEX_FORMAT_ADD_ATTR(a)
#define _ATTR_EXPAND2(a, b) \
_ATTR_EXPAND1(a) \
GPU_VERTEX_FORMAT_ADD_ATTR(b)
#define _ATTR_EXPAND3(a, b, c) \
_ATTR_EXPAND2(a, b) \
GPU_VERTEX_FORMAT_ADD_ATTR(c)
#define _ATTR_EXPAND4(a, b, c, d) \
_ATTR_EXPAND3(a, b, c) \
GPU_VERTEX_FORMAT_ADD_ATTR(d)
#define _ATTR_EXPAND5(a, b, c, d, e) \
_ATTR_EXPAND4(a, b, c, d) \
GPU_VERTEX_FORMAT_ADD_ATTR(e)
#define _ATTR_EXPAND6(a, b, c, d, e, f) \
_ATTR_EXPAND5(a, b, c, d, e) \
GPU_VERTEX_FORMAT_ADD_ATTR(f)
#define _ATTR_EXPAND7(a, b, c, d, e, f, g) \
_ATTR_EXPAND6(a, b, c, d, e, f) \
GPU_VERTEX_FORMAT_ADD_ATTR(g)
#define _ATTR_EXPAND8(a, b, c, d, e, f, g, h) \
_ATTR_EXPAND7(a, b, c, d, e, f, g) \
GPU_VERTEX_FORMAT_ADD_ATTR(h)
#define _ATTR_EXPAND9(a, b, c, d, e, f, g, h, i) \
_ATTR_EXPAND8(a, b, c, d, e, f, g, h) \
GPU_VERTEX_FORMAT_ADD_ATTR(i)
#define _ATTR_EXPAND10(a, b, c, d, e, f, g, h, i, j) \
_ATTR_EXPAND9(a, b, c, d, e, f, g, h, i) \
GPU_VERTEX_FORMAT_ADD_ATTR(j)
#define _ATTR_EXPAND11(a, b, c, d, e, f, g, h, i, j, k) \
_ATTR_EXPAND10(a, b, c, d, e, f, g, h, i, j) \
GPU_VERTEX_FORMAT_ADD_ATTR(k)
#define _ATTR_EXPAND12(a, b, c, d, e, f, g, h, i, j, k, l) \
_ATTR_EXPAND11(a, b, c, d, e, f, g, h, i, j, k) \
GPU_VERTEX_FORMAT_ADD_ATTR(l)
#define _ATTR_EXPAND13(a, b, c, d, e, f, g, h, i, j, k, l, m) \
_ATTR_EXPAND12(a, b, c, d, e, f, g, h, i, j, k, l) \
GPU_VERTEX_FORMAT_ADD_ATTR(m)
#define _ATTR_EXPAND14(a, b, c, d, e, f, g, h, i, j, k, l, m, n) \
_ATTR_EXPAND13(a, b, c, d, e, f, g, h, i, j, k, l, m) \
GPU_VERTEX_FORMAT_ADD_ATTR(n)
#define _ATTR_EXPAND15(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) \
_ATTR_EXPAND14(a, b, c, d, e, f, g, h, i, j, k, l, m, n) \
GPU_VERTEX_FORMAT_ADD_ATTR(o)
#define _ATTR_EXPAND16(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
_ATTR_EXPAND15(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) \
GPU_VERTEX_FORMAT_ADD_ATTR(p)
/* We only support up to GPU_VERT_ATTR_MAX_LEN attribute per format. */
#define GPU_VERTEX_FORMAT_ADD_ATTR_EXPAND(...) VA_NARGS_CALL_OVERLOAD(_ATTR_EXPAND, __VA_ARGS__)
#define GPU_VERTEX_FORMAT_FUNC(_VertT, ...) \
static GPUVertFormat &format() \
{ \
using VertT = _VertT; \
static GPUVertFormat format = {}; \
if (format.attr_len == 0) { \
GPU_VERTEX_FORMAT_ADD_ATTR_EXPAND(__VA_ARGS__) \
format.stride = sizeof(VertT); \
BLI_STATIC_ASSERT(sizeof(VertT) < 1024, "Vertex format is too big") \
format.packed = true; \
BLI_STATIC_ASSERT_ALIGN(VertT, 4) \
} \
return format; \
}
namespace gpu {
/** Generic vertex format for single attribute buffers. */
template<typename T> struct GenericVertexFormat {
T attr;
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
};
template<> struct GenericVertexFormat<int8_t> {
/* This is a workaround to reinterpret int8_t into padded vertex format to be able to upload it
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
* individual bytes. */
uint32_t attr;
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
};
template<> struct GenericVertexFormat<uint8_t> {
/* This is a workaround to reinterpret uint8_t into padded vertex format to be able to upload it
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
* individual bytes. */
uint32_t attr;
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
};
template<> struct GenericVertexFormat<bool> {
/* This is a workaround to reinterpret bool into padded vertex format to be able to upload it
* on any GPU. The shaders then need to read uint32_t and use shifts and mask to decode in
* individual bytes. */
uint32_t attr;
GPU_VERTEX_FORMAT_FUNC(GenericVertexFormat, attr);
};
} // namespace gpu
void GPU_vertformat_clear(GPUVertFormat *);
void GPU_vertformat_copy(GPUVertFormat *dest, const GPUVertFormat &src);
void GPU_vertformat_from_shader(GPUVertFormat *format, const gpu::Shader *shader);
uint GPU_vertformat_attr_add(GPUVertFormat *format, StringRef name, gpu::VertAttrType type);
/* Legacy/unsafe version.
* TODO: Replace by vertex_format_combine. */
uint GPU_vertformat_attr_add_legacy(
GPUVertFormat *, StringRef name, GPUVertCompType, uint comp_len, GPUVertFetchMode);
void GPU_vertformat_alias_add(GPUVertFormat *, StringRef alias);
/**
* Return a vertex format from a single attribute description.
* The attribute ID is ensured to be 0.
*/
GPUVertFormat GPU_vertformat_from_attribute(StringRef name, gpu::VertAttrType type);
/**
* Makes vertex attribute from the next vertices to be accessible in the vertex shader.
* For an attribute named "attr" you can access the next nth vertex using "attr{number}".
* Use this function after specifying all the attributes in the format.
*
* NOTE: This does NOT work when using indexed rendering.
* NOTE: Only works for first attribute name. (this limitation can be changed if needed)
*
* WARNING: this function creates a lot of aliases/attributes, make sure to keep the attribute
* name short to avoid overflowing the name-buffer.
*/
void GPU_vertformat_multiload_enable(GPUVertFormat *format, int load_count);
/**
* Make attribute layout non-interleaved.
* Warning! This does not change data layout!
* Use direct buffer access to fill the data.
* This is for advanced usage.
*
* De-interleaved data means all attribute data for each attribute
* is stored continuously like this:
* 000011112222
* instead of:
* 012012012012
*
* \note This is per attribute de-interleaving, NOT per component.
*/
void GPU_vertformat_deinterleave(GPUVertFormat *format);
int GPU_vertformat_attr_id_get(const GPUVertFormat *, StringRef name);
BLI_INLINE const char *GPU_vertformat_attr_name_get(const GPUVertFormat *format,
const GPUVertAttr *attr,
uint n_idx)
{
return format->names + attr->names[n_idx];
}
/**
* \warning Can only rename using a string with same character count.
* \warning This removes all other aliases of this attribute.
*/
void GPU_vertformat_attr_rename(GPUVertFormat *format, int attr, const char *new_name);
/**
* \warning Always add a prefix to the result of this function as
* the generated string can start with a number and not be a valid attribute name.
*/
void GPU_vertformat_safe_attr_name(StringRef attr_name, char *r_safe_name, uint max_len);
} // namespace blender

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "DNA_scene_types.h"
#include "DNA_vec_types.h"
#include "GPU_framebuffer.hh"
#include "GPU_texture.hh"
namespace blender {
#define GLA_PIXEL_OFS 0.375f
struct DRWData;
struct GPUViewport;
struct GPUOffScreen;
namespace gpu {
class FrameBuffer;
} // namespace gpu
GPUViewport *GPU_viewport_create();
GPUViewport *GPU_viewport_stereo_create();
void GPU_viewport_bind(GPUViewport *viewport, int view, const rcti *rect);
void GPU_viewport_unbind(GPUViewport *viewport);
/**
* Merge and draw the buffers of \a viewport into the currently active framebuffer, performing
* color transform to display space.
*
* \param rect: Coordinates to draw into. By swapping min and max values, drawing can be done
* with inversed axis coordinates (upside down or sideways).
*/
void GPU_viewport_draw_to_screen(GPUViewport *viewport, int view, const rcti *rect);
/**
* Version of #GPU_viewport_draw_to_screen() that lets caller decide if display colorspace
* transform should be performed.
*/
void GPU_viewport_draw_to_screen_ex(GPUViewport *viewport,
int view,
const rcti *rect,
bool display_colorspace,
bool do_overlay_merge);
/**
* Must be executed inside Draw-manager OpenGL Context.
*/
void GPU_viewport_free(GPUViewport *viewport);
void GPU_viewport_colorspace_set(GPUViewport *viewport,
const ColorManagedViewSettings *view_settings,
const ColorManagedDisplaySettings *display_settings,
float dither);
/**
* Should be called from DRW after DRW_gpu_context_enable.
*/
void GPU_viewport_bind_from_offscreen(GPUViewport *viewport,
GPUOffScreen *ofs,
bool is_xr_surface);
/**
* Clear vars assigned from offscreen, so we don't free data owned by `GPUOffScreen`.
*/
void GPU_viewport_unbind_from_offscreen(GPUViewport *viewport,
GPUOffScreen *ofs,
bool display_colorspace,
bool do_overlay_merge);
DRWData **GPU_viewport_data_get(GPUViewport *viewport);
/**
* Merge the stereo textures. `color` and `overlay` texture will be modified.
*/
void GPU_viewport_stereo_composite(GPUViewport *viewport, Stereo3dFormat *stereo_format);
void GPU_viewport_tag_update(GPUViewport *viewport);
bool GPU_viewport_do_update(GPUViewport *viewport);
int GPU_viewport_active_view_get(GPUViewport *viewport);
bool GPU_viewport_is_stereo_get(GPUViewport *viewport);
gpu::Texture *GPU_viewport_color_texture(GPUViewport *viewport, int view);
gpu::Texture *GPU_viewport_overlay_texture(GPUViewport *viewport, int view);
gpu::Texture *GPU_viewport_depth_texture(GPUViewport *viewport);
/**
* Color render and overlay frame-buffers for drawing outside of DRW module.
*/
gpu::FrameBuffer *GPU_viewport_framebuffer_render_get(GPUViewport *viewport);
gpu::FrameBuffer *GPU_viewport_framebuffer_overlay_get(GPUViewport *viewport);
} // namespace blender

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_threads.h"
#include "BLI_vector.hh"
#include "GPU_context.hh"
#include <atomic>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
namespace blender::gpu {
using WorkCallback = void (*)(void *);
using WorkID = uint64_t;
/**
* Abstracts the creation and management of secondary threads with GPU contexts.
* Must be created from the main thread.
* Threads and their context remain alive until destruction.
*/
class GPUWorker {
private:
Vector<std::unique_ptr<std::thread>> threads_;
ThreadQueue *work_queue_;
WorkCallback callback_;
public:
enum class ContextType {
/** Use the main GPU context on the worker threads. */
Main,
/** Use a different secondary GPU context for each worker thread. */
PerThread,
};
/**
* \param threads_count: Number of threads to span.
* \param context_type: The type of context each thread uses.
* \param callback: The callback function that will be called for each acquired work
* (passed as a void pointer).
*/
GPUWorker(uint32_t threads_count, ContextType context_type, WorkCallback callback);
~GPUWorker();
WorkID push_work(void *work, ThreadQueueWorkPriority priority);
bool cancel_work(WorkID id);
bool is_empty();
private:
void run(std::shared_ptr<GPUSecondaryContext> context);
};
} // namespace blender::gpu

View File

@@ -0,0 +1,102 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_backend.hh"
#include "gpu_platform_private.hh"
#include "dummy_batch.hh"
#include "dummy_context.hh"
#include "dummy_framebuffer.hh"
#include "dummy_vertex_buffer.hh"
namespace blender::gpu {
class DummyBackend : public GPUBackend {
public:
DummyBackend()
{
GPG.init(GPU_DEVICE_ANY,
GPU_OS_ANY,
GPU_DRIVER_ANY,
GPU_SUPPORT_LEVEL_UNSUPPORTED,
GPU_BACKEND_NONE,
"Unknown",
"",
"",
GPU_ARCHITECTURE_IMR);
}
void init_resources() override {}
void delete_resources() override {}
void compute_dispatch(int /*groups_x_len*/, int /*groups_y_len*/, int /*groups_z_len*/) override
{
}
void compute_dispatch_indirect(StorageBuf * /*indirect_buf*/) override {}
Context *context_alloc(GHOST_IWindow * /*ghost_window*/,
GHOST_IContext * /*ghost_context*/) override
{
return new DummyContext;
}
Batch *batch_alloc() override
{
return new DummyBatch;
}
Fence *fence_alloc() override
{
return nullptr;
}
FrameBuffer *framebuffer_alloc(const char *name) override
{
return new DummyFrameBuffer(name);
}
IndexBuf *indexbuf_alloc() override
{
return nullptr;
}
PixelBuffer *pixelbuf_alloc(size_t /*size*/) override
{
return nullptr;
}
QueryPool *querypool_alloc() override
{
return nullptr;
}
Shader *shader_alloc(const char * /*name*/) override
{
return nullptr;
}
Texture *texture_alloc(const char * /*name*/) override
{
return nullptr;
}
TexturePool *texturepool_alloc() override
{
return nullptr;
}
UniformBuf *uniformbuf_alloc(size_t /*size*/, const char * /*name*/) override
{
return nullptr;
}
StorageBuf *storagebuf_alloc(size_t /*size*/,
GPUUsageType /*usage*/,
const char * /*name*/) override
{
return nullptr;
}
VertBuf *vertbuf_alloc() override
{
return new DummyVertexBuffer;
}
void shader_cache_dir_clear_old() override {}
void render_begin() override {}
void render_end() override {}
void render_step(bool /*force_resource_release*/) override {}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "GPU_batch.hh"
namespace blender::gpu {
class DummyBatch : public Batch {
public:
void draw(int /*vertex_first*/,
int /*vertex_count*/,
int /*instance_first*/,
int /*instance_count*/) override
{
}
void draw_indirect(StorageBuf * /*indirect_buf*/, intptr_t /*offset*/) override {}
void multi_draw_indirect(StorageBuf * /*indirect_buf*/,
int /*count*/,
intptr_t /*offset*/,
intptr_t /*stride*/) override
{
}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_context_private.hh"
#include "dummy_framebuffer.hh"
namespace blender::gpu {
class DummyContext : public Context {
public:
DummyContext()
{
back_left = active_fb = new DummyFrameBuffer("DummyFramebuffer");
}
~DummyContext() override
{
free_resources();
}
void activate() override {}
void deactivate() override {}
void begin_frame() override {}
void end_frame() override {}
void flush() override {}
void finish() override {}
void memory_statistics_get(int * /*r_total_mem*/, int * /*r_free_mem*/) override {}
void debug_group_begin(const char * /*unused*/, int /*unused*/) override {}
void debug_group_end() override {}
bool debug_capture_begin(const char * /*title*/) override
{
return false;
}
void debug_capture_end() override {}
void *debug_capture_scope_create(const char * /*name*/) override
{
return nullptr;
}
bool debug_capture_scope_begin(void * /*scope*/) override
{
return false;
}
void debug_capture_scope_end(void * /*scope*/) override {}
void debug_unbind_all_ubo() override {}
void debug_unbind_all_ssbo() override {}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,56 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "gpu_framebuffer_private.hh"
namespace blender::gpu {
class DummyFrameBuffer : public FrameBuffer {
public:
DummyFrameBuffer(const char *name) : FrameBuffer(name) {}
void bind(bool /*enabled_srgb*/) override {}
bool check(char /*err_out*/[256]) override
{
return true;
}
void clear(GPUFrameBufferBits /*buffers*/,
const double4 /*clear_color*/,
float /*clear_depth*/,
uint /*clear_stencil*/) override
{
}
void clear_multi(Span<double4> /*clear_cols*/) override {}
void clear_attachment(GPUAttachmentType /*type*/, const double4 /*clear_value*/) override {}
void attachment_set_loadstore_op(GPUAttachmentType /*type*/, GPULoadStore /*ls*/) override {}
void subpass_transition_impl(const GPUAttachmentState /*depth_attachment_state*/,
Span<GPUAttachmentState> /*color_attachment_states*/) override {};
void read(GPUFrameBufferBits /*planes*/,
eGPUDataFormat /*format*/,
const int /*area*/[4],
int /*channel_len*/,
int /*slot*/,
void * /*r_data*/) override
{
}
void blit_to(GPUFrameBufferBits /*planes*/,
int /*src_slot*/,
FrameBuffer * /*dst*/,
int /*dst_slot*/,
int /*dst_offset_x*/,
int /*dst_offset_y*/) override
{
}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_sys_types.h"
#include "GPU_vertex_buffer.hh"
namespace blender::gpu {
class DummyVertexBuffer : public VertBuf {
public:
void bind_as_ssbo(uint /*binding*/) override {}
void bind_as_texture(uint /*binding*/) override {}
void wrap_handle(uint64_t /*handle*/) override {}
void update_sub(uint /*start*/, uint /*len*/, const void * /*data*/) override {}
void read(void * /*data*/) const override {}
protected:
void acquire_data() override
{
MEM_SAFE_DELETE(data_);
data_ = MEM_new_array_uninitialized<uchar>(this->size_alloc_get(), __func__);
}
void resize_data() override {}
void release_data() override
{
MEM_SAFE_DELETE(data_);
}
void upload_data() override {}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPUBackend derived class contain allocators that do not need a context bound.
* The backend is init at startup and is accessible using GPU_backend_get() */
#pragma once
#include "BLI_color_types.hh"
#include "BLI_string_ref.hh"
#include "GHOST_IContext.hh"
#include "GHOST_IWindow.hh"
#include "GPU_vertex_buffer.hh"
namespace blender::gpu {
class Context;
class Batch;
class Fence;
class FrameBuffer;
class IndexBuf;
class PixelBuffer;
class QueryPool;
class Shader;
class ShaderCompiler;
class Texture;
class TexturePool;
class UniformBuf;
class StorageBuf;
class VertBuf;
class GPUBackend {
protected:
ShaderCompiler *compiler_;
public:
virtual ~GPUBackend() = default;
/* Called after the main context creation and activation. */
virtual void init_resources() = 0;
/* Called before the main context deletion and deactivation. */
virtual void delete_resources() = 0;
static GPUBackend *get();
ShaderCompiler *get_compiler()
{
return compiler_;
}
virtual void compute_dispatch(int groups_x_len, int groups_y_len, int groups_z_len) = 0;
virtual void compute_dispatch_indirect(StorageBuf *indirect_buf) = 0;
virtual Context *context_alloc(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context) = 0;
virtual Batch *batch_alloc() = 0;
virtual Fence *fence_alloc() = 0;
virtual FrameBuffer *framebuffer_alloc(const char *name) = 0;
virtual IndexBuf *indexbuf_alloc() = 0;
virtual PixelBuffer *pixelbuf_alloc(size_t size) = 0;
virtual QueryPool *querypool_alloc() = 0;
virtual Shader *shader_alloc(const char *name) = 0;
virtual Texture *texture_alloc(const char *name) = 0;
virtual TexturePool *texturepool_alloc() = 0;
virtual UniformBuf *uniformbuf_alloc(size_t size, const char *name) = 0;
virtual StorageBuf *storagebuf_alloc(size_t size, GPUUsageType usage, const char *name) = 0;
virtual VertBuf *vertbuf_alloc() = 0;
virtual void shader_cache_dir_clear_old() = 0;
/* Render Frame Coordination --
* Used for performing per-frame actions globally */
virtual void render_begin() = 0;
virtual void render_end() = 0;
virtual void render_step(bool force_resource_release = false) = 0;
};
namespace debug {
static ColorTheme4f GPU_DEBUG_GROUP_COLOR_DEFAULT = {};
static inline ColorTheme4f get_debug_group_color(StringRefNull name)
{
if (name == "EEVEE") {
return ColorTheme4f(1.0, 0.5, 0.0, 1.0);
}
if (name == "External") {
return ColorTheme4f(0.0, 0.0, 1.0, 1.0);
}
if (name == "GpencilMode") {
return ColorTheme4f(1.0, 1.0, 0.0, 1.0);
}
if (name == "UV/Image") {
return ColorTheme4f(0.0, 1.0, 1.0, 1.0);
}
if (name == "Overlay") {
return ColorTheme4f(0.0, 1.0, 0.5, 1.0);
}
if (name == "Workbench") {
return ColorTheme4f(0.0, 0.7, 1.0, 1.0);
}
if (name == "Cycles") {
return ColorTheme4f(0.0, 0.5, 1.0, 1.0);
}
if (name == "BackBuffer.Blit") {
return ColorTheme4f(0.5, 0.7, 1.0, 1.0);
}
if (name == "Compositor") {
return ColorTheme4f(1.0, 0.5, 0.7, 1.0);
}
return GPU_DEBUG_GROUP_COLOR_DEFAULT;
}
} // namespace debug
} // namespace blender::gpu

View File

@@ -0,0 +1,551 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU geometry batch
* Contains VAOs + VBOs + Shader representing a drawable entity.
*/
#include "BLI_math_base.h"
#include "BLI_utildefines.h"
#include "GPU_batch.hh"
#include "GPU_batch_presets.hh"
#include "GPU_shader.hh"
#include "GPU_index_buffer.hh"
#include "GPU_vertex_buffer.hh"
#include "gpu_backend.hh"
#include "gpu_context_private.hh"
#include "gpu_debug_private.hh"
#include "gpu_shader_private.hh"
#include <cstring>
namespace blender {
using namespace blender::gpu;
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
void GPU_batch_zero(Batch *batch)
{
std::fill_n(batch->verts, ARRAY_SIZE(batch->verts), nullptr);
batch->elem = nullptr;
batch->flag = GPUBatchFlag(0);
batch->prim_type = GPUPrimType(0);
batch->shader = nullptr;
batch->procedural_vertices = -1;
}
Batch *GPU_batch_calloc()
{
Batch *batch = GPUBackend::get()->batch_alloc();
GPU_batch_zero(batch);
return batch;
}
Batch *GPU_batch_create_ex(GPUPrimType primitive_type,
VertBuf *vertex_buf,
IndexBuf *index_buf,
GPUBatchFlag owns_flag)
{
Batch *batch = GPU_batch_calloc();
GPU_batch_init_ex(batch, primitive_type, vertex_buf, index_buf, owns_flag);
return batch;
}
void GPU_batch_init_ex(Batch *batch,
GPUPrimType primitive_type,
VertBuf *vertex_buf,
IndexBuf *index_buf,
GPUBatchFlag owns_flag)
{
/* Do not pass any other flag */
BLI_assert((owns_flag & ~(GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX)) == 0);
/* Batch needs to be in cleared state. */
BLI_assert((batch->flag & GPU_BATCH_INIT) == 0);
batch->verts[0] = vertex_buf;
for (int v = 1; v < GPU_BATCH_VBO_MAX_LEN; v++) {
batch->verts[v] = nullptr;
}
batch->elem = index_buf;
batch->prim_type = primitive_type;
batch->flag = owns_flag | GPU_BATCH_INIT | GPU_BATCH_DIRTY;
batch->shader = nullptr;
batch->procedural_vertices = -1;
}
Batch *GPU_batch_create_procedural(GPUPrimType primitive_type, int32_t vertex_count)
{
BLI_assert(vertex_count >= 0);
Batch *batch = GPU_batch_calloc();
for (auto &v : batch->verts) {
v = nullptr;
}
batch->elem = nullptr;
batch->prim_type = primitive_type;
batch->flag = GPU_BATCH_INIT | GPU_BATCH_DIRTY;
batch->shader = nullptr;
batch->procedural_vertices = vertex_count;
return batch;
}
void GPU_batch_copy(Batch *batch_dst, Batch *batch_src)
{
GPU_batch_clear(batch_dst);
GPU_batch_init_ex(
batch_dst, GPU_PRIM_POINTS, batch_src->verts[0], batch_src->elem, GPU_BATCH_INVALID);
batch_dst->prim_type = batch_src->prim_type;
for (int v = 1; v < GPU_BATCH_VBO_MAX_LEN; v++) {
batch_dst->verts[v] = batch_src->verts[v];
}
batch_dst->procedural_vertices = batch_src->procedural_vertices;
}
void GPU_batch_clear(Batch *batch)
{
if (batch->flag & GPU_BATCH_OWNS_INDEX) {
GPU_indexbuf_discard(batch->elem);
}
if (batch->flag & GPU_BATCH_OWNS_VBO_ANY) {
for (int v = 0; (v < GPU_BATCH_VBO_MAX_LEN) && batch->verts[v]; v++) {
if (batch->flag & (GPU_BATCH_OWNS_VBO << v)) {
GPU_VERTBUF_DISCARD_SAFE(batch->verts[v]);
}
}
}
batch->flag = GPU_BATCH_INVALID;
batch->procedural_vertices = -1;
}
void GPU_batch_discard(Batch *batch)
{
GPU_batch_clear(batch);
delete batch;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Buffers Management
* \{ */
void GPU_batch_elembuf_set(Batch *batch, gpu::IndexBuf *index_buf, bool own_ibo)
{
BLI_assert(index_buf);
batch->flag |= GPU_BATCH_DIRTY;
if (batch->elem && (batch->flag & GPU_BATCH_OWNS_INDEX)) {
GPU_indexbuf_discard(batch->elem);
}
batch->elem = index_buf;
SET_FLAG_FROM_TEST(batch->flag, own_ibo, GPU_BATCH_OWNS_INDEX);
}
int GPU_batch_vertbuf_add(Batch *batch, VertBuf *vertex_buf, bool own_vbo)
{
BLI_assert(vertex_buf);
batch->flag |= GPU_BATCH_DIRTY;
for (uint v = 0; v < GPU_BATCH_VBO_MAX_LEN; v++) {
if (batch->verts[v] == nullptr) {
/* for now all VertexBuffers must have same vertex_len */
if (batch->verts[0] != nullptr) {
/* This is an issue for the HACK inside DRW_vbo_request(). */
// BLI_assert(verts->vertex_len == batch->verts[0]->vertex_len);
}
batch->verts[v] = vertex_buf;
SET_FLAG_FROM_TEST(batch->flag, own_vbo, (GPUBatchFlag)(GPU_BATCH_OWNS_VBO << v));
return v;
}
}
/* we only make it this far if there is no room for another VertBuf */
BLI_assert_msg(0, "Not enough VBO slot in batch");
return -1;
}
bool GPU_batch_vertbuf_has(const Batch *batch, const VertBuf *vertex_buf)
{
for (uint v = 0; v < GPU_BATCH_VBO_MAX_LEN; v++) {
if (batch->verts[v] == vertex_buf) {
return true;
}
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Uniform setters
*
* \{ */
void GPU_batch_set_shader(Batch *batch,
gpu::Shader *shader,
const shader::SpecializationConstants *constants_state)
{
batch->shader = shader;
GPU_shader_bind(batch->shader, constants_state);
}
static uint16_t bind_attribute_as_ssbo(const ShaderInterface *interface,
gpu::Shader *shader,
VertBuf *vbo)
{
const GPUVertFormat *format = &vbo->format;
/* We need to support GPU OpenSubdiv meshes. This assert can be enabled back after we refactor
* our OpenSubdiv implementation to output the same layout as the regular mesh extraction. */
// BLI_assert_msg(format->attr_len == 1, "Multi attribute buffers are not supported for now");
char uniform_name[] = "gpu_attr_0";
uint stride = format->stride;
uint offset = 0;
uint16_t bound_attr = 0u;
for (uint a_idx = 0; a_idx < format->attr_len; a_idx++) {
const GPUVertAttr *a = &format->attrs[a_idx];
if (format->deinterleaved) {
offset += ((a_idx == 0) ? 0 : format->attrs[a_idx - 1].type.size()) * vbo->vertex_len;
stride = a->type.size();
}
else {
offset = a->offset;
}
for (uint n_idx = 0; n_idx < a->name_len; n_idx++) {
const char *name = GPU_vertformat_attr_name_get(format, a, n_idx);
const ShaderInput *input = interface->ssbo_get(name);
if (input == nullptr || input->location == -1) {
continue;
}
GPU_vertbuf_bind_as_ssbo(vbo, input->location);
bound_attr |= (1 << input->location);
/* WORKAROUND: This is to support complex format. But ideally this should not be supported.
*/
uniform_name[9] = '0' + input->location;
/* Only support 4byte aligned attributes. */
BLI_assert((stride % 4) == 0);
BLI_assert((offset % 4) == 0);
int descriptor[2] = {int(stride) / 4, int(offset) / 4};
GPU_shader_uniform_2iv(shader, uniform_name, descriptor);
/* WORKAROUND: Fix for polyline workaround. Ideally should be fused with `gpu_attr_0`.
* But for now, changes are a bit too invasive. Will need to be revisited later on. */
char uniform_name_len[] = "gpu_attr_0_len";
uniform_name_len[9] = '0' + input->location;
const int loc = GPU_shader_get_uniform(shader, uniform_name_len);
if (loc != -1) {
int data = a->type.comp_len();
GPU_shader_uniform_int_ex(shader, loc, 1, 1, &data);
}
}
}
return bound_attr;
}
void GPU_batch_bind_as_resources(Batch *batch,
gpu::Shader *shader,
const shader::SpecializationConstants *constants)
{
const ShaderInterface *interface = shader->interface;
if (interface->ssbo_attr_mask_ == 0) {
return;
}
uint16_t ssbo_attributes = interface->ssbo_attr_mask_;
if (ssbo_attributes & (1 << GPU_SSBO_INDEX_BUF_SLOT)) {
/* Ensure binding for setting uniforms. This is required by the OpenGL backend. */
GPU_shader_bind(shader, constants);
if (batch->elem) {
GPU_indexbuf_bind_as_ssbo(batch->elem, GPU_SSBO_INDEX_BUF_SLOT);
GPU_shader_uniform_1b(shader, "gpu_index_no_buffer", false);
GPU_shader_uniform_1b(shader, "gpu_index_16bit", !batch->elem->is_32bit());
GPU_shader_uniform_1i(shader, "gpu_index_base_index", batch->elem->index_base_get());
}
else {
/* Still fulfill the binding requirements even if the buffer will not be read. */
GPU_vertbuf_bind_as_ssbo(batch->verts[0], GPU_SSBO_INDEX_BUF_SLOT);
GPU_shader_uniform_1b(shader, "gpu_index_no_buffer", true);
}
ssbo_attributes &= ~(1 << GPU_SSBO_INDEX_BUF_SLOT);
}
/* Reverse order so first VBO'S have more prevalence (in term of attribute override). */
for (int v = GPU_BATCH_VBO_MAX_LEN - 1; v > -1; v--) {
VertBuf *vbo = batch->verts_(v);
if (vbo) {
ssbo_attributes &= ~bind_attribute_as_ssbo(interface, shader, vbo);
}
}
BLI_assert_msg(ssbo_attributes == 0, "Not all attribute storage buffer fulfilled");
UNUSED_VARS_NDEBUG(ssbo_attributes);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Drawing / Draw-call functions
* \{ */
void GPU_batch_draw_parameter_get(Batch *batch,
int *r_vertex_count,
int *r_vertex_first,
int *r_base_index,
int *r_instance_count)
{
if (batch->procedural_vertices >= 0) {
*r_vertex_count = batch->procedural_vertices;
*r_vertex_first = 0;
*r_base_index = -1;
}
else if (batch->elem) {
*r_vertex_count = batch->elem_()->index_len_get();
*r_vertex_first = batch->elem_()->index_start_get();
*r_base_index = batch->elem_()->index_base_get();
}
else {
*r_vertex_count = batch->verts_(0)->vertex_len;
*r_vertex_first = 0;
*r_base_index = -1;
}
*r_instance_count = 1;
}
IndexRange GPU_batch_draw_expanded_parameter_get(GPUPrimType input_prim_type,
GPUPrimType output_prim_type,
int vertex_count,
int vertex_first,
int output_primitive_cout)
{
int vert_per_original_primitive = indices_per_primitive(input_prim_type);
int vert_per_expanded_primitive = indices_per_primitive(output_prim_type);
int prim_first = vertex_first / vert_per_original_primitive;
int prim_len = vertex_count / vert_per_original_primitive;
BLI_assert_msg(vert_per_original_primitive != -1,
"Primitive expansion only works for primitives with known amount of vertices");
/* WORKAROUND: Needed for polyline_draw_workaround. */
if (input_prim_type == GPU_PRIM_LINE_STRIP) {
prim_len = vertex_count - 1;
}
int out_vertex_first = prim_first * vert_per_expanded_primitive * output_primitive_cout;
int out_vertex_count = prim_len * vert_per_expanded_primitive * output_primitive_cout;
return IndexRange(out_vertex_first, out_vertex_count);
}
static void polyline_draw_workaround(
Batch *batch, int vertex_first, int vertex_count, int instance_first, int instance_count)
{
/* Early out as this can cause crashes on some backend (see #136831). */
if (vertex_count == 0) {
return;
}
/* Check compatible input primitive. */
BLI_assert(ELEM(batch->prim_type, GPU_PRIM_LINES, GPU_PRIM_LINE_STRIP, GPU_PRIM_LINE_LOOP));
GPU_batch_bind_as_resources(batch, batch->shader);
IndexRange range = GPU_batch_draw_expanded_parameter_get(
batch->prim_type, GPU_PRIM_TRIS, vertex_count, vertex_first, 2);
Batch *tri_batch = Context::get()->procedural_triangles_batch_get();
GPU_batch_set_shader(tri_batch, batch->shader);
int vert_stride_count[3] = {(batch->prim_type == GPU_PRIM_LINES) ? 2 : 1, vertex_count, 0};
GPU_shader_uniform_3iv(batch->shader, "gpu_vert_stride_count_offset", vert_stride_count);
/* Assume GPU_FETCH_FLOAT for now. A bit cumbersome to assert for this or to find the correct
* attribute. */
GPU_shader_uniform_1b(batch->shader, "gpu_attr_0_fetch_int", false);
/* Allow byte color fetch. */
const GPUVertFormat *format = GPU_vertbuf_get_format(batch->verts[0]);
int id = GPU_vertformat_attr_id_get(format, "color");
if (id != -1) {
const GPUVertAttr &attr = format->attrs[id];
const bool is_unorm8 = attr.type.format == gpu::VertAttrType::UNORM_8_8_8_8;
BLI_assert_msg(is_unorm8 || attr.type.fetch_mode() == GPU_FETCH_FLOAT,
"color attribute for polylines can only use GPU_FETCH_INT_TO_FLOAT_UNIT or "
"GPU_FETCH_FLOAT");
GPU_shader_uniform_1b(batch->shader, "gpu_attr_1_fetch_unorm8", is_unorm8);
}
GPU_batch_draw_advanced(tri_batch, range.start(), range.size(), instance_first, instance_count);
}
void GPU_batch_draw(Batch *batch)
{
BLI_assert(batch != nullptr);
GPU_shader_bind(batch->shader);
if (batch->shader->is_polyline) {
polyline_draw_workaround(batch, 0, batch->vertex_count_get(), 0, 0);
}
else {
GPU_batch_draw_advanced(batch, 0, 0, 0, 0);
}
}
void GPU_batch_draw_range(Batch *batch, int vertex_first, int vertex_count)
{
BLI_assert(batch != nullptr);
GPU_shader_bind(batch->shader);
if (batch->shader->is_polyline) {
polyline_draw_workaround(batch, vertex_first, vertex_count, 0, 0);
}
else {
GPU_batch_draw_advanced(batch, vertex_first, vertex_count, 0, 0);
}
}
void GPU_batch_draw_instance_range(Batch *batch, int instance_first, int instance_count)
{
BLI_assert(batch != nullptr);
/* Not polyline shaders support instancing. */
BLI_assert(batch->shader->is_polyline == false);
GPU_shader_bind(batch->shader);
GPU_batch_draw_advanced(batch, 0, 0, instance_first, instance_count);
}
void GPU_batch_draw_advanced(
Batch *batch, int vertex_first, int vertex_count, int instance_first, int instance_count)
{
BLI_assert(batch != nullptr);
BLI_assert(Context::get()->shader != nullptr);
Context::get()->assert_framebuffer_shader_compatibility(Context::get()->shader);
if (vertex_count == 0) {
if (batch->procedural_vertices > 0) {
vertex_count = batch->procedural_vertices;
}
else if (batch->elem) {
vertex_count = batch->elem_()->index_len_get();
}
else {
vertex_count = batch->verts_(0)->vertex_len;
}
}
if (instance_count == 0) {
instance_count = 1;
}
if (vertex_count == 0 || instance_count == 0) {
/* Nothing to draw. */
return;
}
#ifndef NDEBUG
debug_validate_binding_image_format();
#endif
batch->draw(vertex_first, vertex_count, instance_first, instance_count);
}
void GPU_batch_draw_indirect(Batch *batch, gpu::StorageBuf *indirect_buf, intptr_t offset)
{
BLI_assert(batch != nullptr);
BLI_assert(indirect_buf != nullptr);
BLI_assert(Context::get()->shader != nullptr);
Context::get()->assert_framebuffer_shader_compatibility(Context::get()->shader);
#ifndef NDEBUG
debug_validate_binding_image_format();
#endif
batch->draw_indirect(indirect_buf, offset);
}
void GPU_batch_multi_draw_indirect(
Batch *batch, gpu::StorageBuf *indirect_buf, int count, intptr_t offset, intptr_t stride)
{
BLI_assert(batch != nullptr);
BLI_assert(indirect_buf != nullptr);
BLI_assert(Context::get()->shader != nullptr);
Context::get()->assert_framebuffer_shader_compatibility(Context::get()->shader);
#ifndef NDEBUG
debug_validate_binding_image_format();
#endif
batch->multi_draw_indirect(indirect_buf, count, offset, stride);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utilities
* \{ */
void GPU_batch_program_set_builtin_with_config(Batch *batch,
GPUBuiltinShader shader_id,
GPUShaderConfig sh_cfg)
{
gpu::Shader *shader = GPU_shader_get_builtin_shader_with_config(shader_id, sh_cfg);
GPU_batch_set_shader(batch, shader);
}
void GPU_batch_program_set_builtin(Batch *batch, GPUBuiltinShader shader_id)
{
GPU_batch_program_set_builtin_with_config(batch, shader_id, GPU_SHADER_CFG_DEFAULT);
}
void GPU_batch_program_set_imm_shader(Batch *batch)
{
GPU_batch_set_shader(batch, immGetShader());
}
gpu::Batch *GPU_batch_procedural_points_get()
{
return gpu::Context::get()->procedural_points_batch_get();
}
gpu::Batch *GPU_batch_procedural_lines_get()
{
return gpu::Context::get()->procedural_lines_batch_get();
}
gpu::Batch *GPU_batch_procedural_triangles_get()
{
return gpu::Context::get()->procedural_triangles_batch_get();
}
gpu::Batch *GPU_batch_procedural_triangle_strips_get()
{
return gpu::Context::get()->procedural_triangle_strips_batch_get();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Init/Exit
* \{ */
void gpu_batch_init()
{
gpu_batch_presets_init();
}
void gpu_batch_exit()
{
gpu_batch_presets_exit();
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,289 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_threads.h"
#include "MEM_guardedalloc.h"
#include "GPU_batch.hh"
#include "GPU_batch_presets.hh" /* Own include. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Local Structures
* \{ */
/* Struct to store 3D Batches and their format */
static struct {
struct {
gpu::Batch *sphere_high;
gpu::Batch *sphere_med;
gpu::Batch *sphere_low;
gpu::Batch *sphere_wire_low;
gpu::Batch *sphere_wire_med;
} batch;
GPUVertFormat format;
struct {
uint pos, nor;
} attr_id;
ThreadMutex mutex;
} g_presets_3d = {{nullptr}};
static struct {
struct {
gpu::Batch *quad;
} batch;
GPUVertFormat format;
struct {
uint pos, col;
} attr_id;
} g_presets_2d = {{nullptr}};
static ListBaseT<LinkData> presets_list = {nullptr, nullptr};
static ListBaseT<LinkData> buffer_list = {nullptr, nullptr};
/** \} */
/* -------------------------------------------------------------------- */
/** \name 3D Primitives
* \{ */
static GPUVertFormat &preset_3d_format()
{
if (g_presets_3d.format.attr_len == 0) {
GPUVertFormat *format = &g_presets_3d.format;
g_presets_3d.attr_id.pos = GPU_vertformat_attr_add(
format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
g_presets_3d.attr_id.nor = GPU_vertformat_attr_add(
format, "nor", gpu::VertAttrType::SFLOAT_32_32_32);
}
return g_presets_3d.format;
}
static GPUVertFormat &preset_2d_format()
{
if (g_presets_2d.format.attr_len == 0) {
GPUVertFormat *format = &g_presets_2d.format;
g_presets_2d.attr_id.pos = GPU_vertformat_attr_add(
format, "pos", gpu::VertAttrType::SFLOAT_32_32);
g_presets_2d.attr_id.col = GPU_vertformat_attr_add(
format, "color", gpu::VertAttrType::SFLOAT_32_32_32_32);
}
return g_presets_2d.format;
}
static void batch_sphere_lat_lon_vert(GPUVertBufRaw *pos_step,
GPUVertBufRaw *nor_step,
float lat,
float lon)
{
float pos[3];
pos[0] = sinf(lat) * cosf(lon);
pos[1] = cosf(lat);
pos[2] = sinf(lat) * sinf(lon);
copy_v3_v3(static_cast<float *>(GPU_vertbuf_raw_step(pos_step)), pos);
copy_v3_v3(static_cast<float *>(GPU_vertbuf_raw_step(nor_step)), pos);
}
gpu::Batch *GPU_batch_preset_sphere(int lod)
{
BLI_assert(lod >= 0 && lod <= 2);
BLI_assert(BLI_thread_is_main());
if (lod == 0) {
return g_presets_3d.batch.sphere_low;
}
if (lod == 1) {
return g_presets_3d.batch.sphere_med;
}
return g_presets_3d.batch.sphere_high;
}
gpu::Batch *GPU_batch_preset_sphere_wire(int lod)
{
BLI_assert(lod >= 0 && lod <= 1);
BLI_assert(BLI_thread_is_main());
if (lod == 0) {
return g_presets_3d.batch.sphere_wire_low;
}
return g_presets_3d.batch.sphere_wire_med;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Create Sphere (3D)
* \{ */
static gpu::Batch *gpu_batch_sphere(int lat_res, int lon_res)
{
const float lon_inc = 2 * M_PI / lon_res;
const float lat_inc = M_PI / lat_res;
float lon, lat;
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(preset_3d_format());
const uint vbo_len = (lat_res - 1) * lon_res * 6;
GPU_vertbuf_data_alloc(*vbo, vbo_len);
GPUVertBufRaw pos_step, nor_step;
GPU_vertbuf_attr_get_raw_data(vbo, g_presets_3d.attr_id.pos, &pos_step);
GPU_vertbuf_attr_get_raw_data(vbo, g_presets_3d.attr_id.nor, &nor_step);
lon = 0.0f;
for (int i = 0; i < lon_res; i++, lon += lon_inc) {
lat = 0.0f;
for (int j = 0; j < lat_res; j++, lat += lat_inc) {
if (j != lat_res - 1) { /* Pole */
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat + lat_inc, lon + lon_inc);
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat + lat_inc, lon);
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat, lon);
}
if (j != 0) { /* Pole */
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat, lon + lon_inc);
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat + lat_inc, lon + lon_inc);
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat, lon);
}
}
}
BLI_assert(vbo_len == GPU_vertbuf_raw_used(&pos_step));
BLI_assert(vbo_len == GPU_vertbuf_raw_used(&nor_step));
return GPU_batch_create_ex(GPU_PRIM_TRIS, vbo, nullptr, GPU_BATCH_OWNS_VBO);
}
static gpu::Batch *batch_sphere_wire(int lat_res, int lon_res)
{
const float lon_inc = 2 * M_PI / lon_res;
const float lat_inc = M_PI / lat_res;
float lon, lat;
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(preset_3d_format());
const uint vbo_len = (lat_res * lon_res * 2) + ((lat_res - 1) * lon_res * 2);
GPU_vertbuf_data_alloc(*vbo, vbo_len);
GPUVertBufRaw pos_step, nor_step;
GPU_vertbuf_attr_get_raw_data(vbo, g_presets_3d.attr_id.pos, &pos_step);
GPU_vertbuf_attr_get_raw_data(vbo, g_presets_3d.attr_id.nor, &nor_step);
lon = 0.0f;
for (int i = 0; i < lon_res; i++, lon += lon_inc) {
lat = 0.0f;
for (int j = 0; j < lat_res; j++, lat += lat_inc) {
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat + lat_inc, lon);
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat, lon);
if (j != lat_res - 1) { /* Pole */
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat + lat_inc, lon + lon_inc);
batch_sphere_lat_lon_vert(&pos_step, &nor_step, lat + lat_inc, lon);
}
}
}
BLI_assert(vbo_len == GPU_vertbuf_raw_used(&pos_step));
BLI_assert(vbo_len == GPU_vertbuf_raw_used(&nor_step));
return GPU_batch_create_ex(GPU_PRIM_LINES, vbo, nullptr, GPU_BATCH_OWNS_VBO);
}
gpu::Batch *GPU_batch_preset_quad()
{
if (!g_presets_2d.batch.quad) {
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(preset_2d_format());
GPU_vertbuf_data_alloc(*vbo, 4);
float pos_data[4][2] = {{0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f}, {1.0f, 1.0f}};
GPU_vertbuf_attr_fill(vbo, g_presets_2d.attr_id.pos, pos_data);
/* Don't fill the color. */
g_presets_2d.batch.quad = GPU_batch_create_ex(
GPU_PRIM_TRI_STRIP, vbo, nullptr, GPU_BATCH_OWNS_VBO);
gpu_batch_presets_register(g_presets_2d.batch.quad);
}
return g_presets_2d.batch.quad;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Preset Registration Management
* \{ */
void gpu_batch_presets_init()
{
BLI_mutex_init(&g_presets_3d.mutex);
/* Hard coded resolution */
g_presets_3d.batch.sphere_low = gpu_batch_sphere(8, 16);
gpu_batch_presets_register(g_presets_3d.batch.sphere_low);
g_presets_3d.batch.sphere_med = gpu_batch_sphere(16, 10);
gpu_batch_presets_register(g_presets_3d.batch.sphere_med);
g_presets_3d.batch.sphere_high = gpu_batch_sphere(32, 24);
gpu_batch_presets_register(g_presets_3d.batch.sphere_high);
g_presets_3d.batch.sphere_wire_low = batch_sphere_wire(6, 8);
gpu_batch_presets_register(g_presets_3d.batch.sphere_wire_low);
g_presets_3d.batch.sphere_wire_med = batch_sphere_wire(8, 16);
gpu_batch_presets_register(g_presets_3d.batch.sphere_wire_med);
}
void gpu_batch_presets_register(gpu::Batch *preset_batch)
{
BLI_mutex_lock(&g_presets_3d.mutex);
BLI_addtail(&presets_list, BLI_genericNodeN(preset_batch));
BLI_mutex_unlock(&g_presets_3d.mutex);
}
void gpu_batch_storage_buffer_register(gpu::StorageBuf *preset_buffer)
{
BLI_mutex_lock(&g_presets_3d.mutex);
BLI_addtail(&buffer_list, BLI_genericNodeN(preset_buffer));
BLI_mutex_unlock(&g_presets_3d.mutex);
}
void gpu_batch_presets_exit()
{
while (LinkData *link = static_cast<LinkData *>(BLI_pophead(&presets_list))) {
gpu::Batch *preset = static_cast<gpu::Batch *>(link->data);
GPU_batch_discard(preset);
MEM_delete(link);
}
while (LinkData *link = static_cast<LinkData *>(BLI_pophead(&buffer_list))) {
gpu::StorageBuf *preset = static_cast<gpu::StorageBuf *>(link->data);
GPU_storagebuf_free(preset);
MEM_delete(link);
}
/* Reset pointers to null for subsequent initializations after tear-down. */
g_presets_2d = {{nullptr}};
g_presets_3d = {{nullptr}};
presets_list = {nullptr, nullptr};
BLI_mutex_end(&g_presets_3d.mutex);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,285 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "MEM_guardedalloc.h"
#include "DNA_vec_types.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_polyfill_2d.h"
#include "BLI_sort_utils.h"
#include "BLI_utildefines.h"
#include "GPU_batch.hh"
#include "GPU_batch_utils.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Polygon Creation (2D)
* \{ */
gpu::Batch *GPU_batch_tris_from_poly_2d_encoded(const uchar *polys_flat,
uint polys_flat_len,
const rctf *rect)
{
const uchar(*polys)[2] = reinterpret_cast<const uchar(*)[2]>(polys_flat);
const uint polys_len = polys_flat_len / 2;
BLI_assert(polys_flat_len == polys_len * 2);
/* Over alloc in both cases */
float (*verts)[2] = MEM_new_array_uninitialized<float[2]>(polys_len, __func__);
float (*verts_step)[2] = verts;
uint(*tris)[3] = MEM_new_array_uninitialized<uint[3]>(polys_len, __func__);
uint(*tris_step)[3] = tris;
const float range_uchar[2] = {
(rect ? (rect->xmax - rect->xmin) : 2.0f) / 255.0f,
(rect ? (rect->ymax - rect->ymin) : 2.0f) / 255.0f,
};
const float min_uchar[2] = {
(rect ? rect->xmin : -1.0f),
(rect ? rect->ymin : -1.0f),
};
uint i_poly = 0;
uint i_vert = 0;
while (i_poly != polys_len) {
for (uint j = 0; j < 2; j++) {
verts[i_vert][j] = min_uchar[j] + (float(polys[i_poly][j]) * range_uchar[j]);
}
i_vert++;
i_poly++;
if (polys[i_poly - 1][0] == polys[i_poly][0] && polys[i_poly - 1][1] == polys[i_poly][1]) {
const uint verts_step_len = (&verts[i_vert]) - verts_step;
BLI_assert(verts_step_len >= 3);
const uint tris_len = (verts_step_len - 2);
BLI_polyfill_calc(verts_step, verts_step_len, -1, tris_step);
/* offset indices */
if (verts_step != verts) {
uint *t = tris_step[0];
const uint offset = (verts_step - verts);
uint tot = tris_len * 3;
while (tot--) {
*t += offset;
t++;
}
BLI_assert(t == tris_step[tris_len]);
}
verts_step += verts_step_len;
tris_step += tris_len;
i_poly++;
/* ignore the duplicate point */
}
}
/* We have vertices and tris, make a batch from this. */
static GPUVertFormat format = {0};
static struct {
uint pos;
} attr_id;
if (format.attr_len == 0) {
attr_id.pos = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32);
}
const uint verts_len = (verts_step - verts);
const uint tris_len = (tris_step - tris);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo, verts_len);
GPUVertBufRaw pos_step;
GPU_vertbuf_attr_get_raw_data(vbo, attr_id.pos, &pos_step);
for (uint i = 0; i < verts_len; i++) {
copy_v2_v2(static_cast<float *>(GPU_vertbuf_raw_step(&pos_step)), verts[i]);
}
GPUIndexBufBuilder elb;
GPU_indexbuf_init(&elb, GPU_PRIM_TRIS, tris_len, verts_len);
for (uint i = 0; i < tris_len; i++) {
GPU_indexbuf_add_tri_verts(&elb, UNPACK3(tris[i]));
}
gpu::IndexBuf *indexbuf = GPU_indexbuf_build(&elb);
MEM_delete(tris);
MEM_delete(verts);
return GPU_batch_create_ex(
GPU_PRIM_TRIS, vbo, indexbuf, GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX);
}
gpu::Batch *GPU_batch_wire_from_poly_2d_encoded(const uchar *polys_flat,
uint polys_flat_len,
const rctf *rect)
{
const uchar(*polys)[2] = reinterpret_cast<const uchar(*)[2]>(polys_flat);
const uint polys_len = polys_flat_len / 2;
BLI_assert(polys_flat_len == polys_len * 2);
/* Over alloc */
/* Lines are pairs of (x, y) byte locations packed into an int32_t. */
int32_t *lines = MEM_new_array_uninitialized<int32_t>(polys_len, __func__);
int32_t *lines_step = lines;
const float range_uchar[2] = {
(rect ? (rect->xmax - rect->xmin) : 2.0f) / 255.0f,
(rect ? (rect->ymax - rect->ymin) : 2.0f) / 255.0f,
};
const float min_uchar[2] = {
(rect ? rect->xmin : -1.0f),
(rect ? rect->ymin : -1.0f),
};
uint i_poly_prev = 0;
uint i_poly = 0;
while (i_poly != polys_len) {
i_poly++;
if (polys[i_poly - 1][0] == polys[i_poly][0] && polys[i_poly - 1][1] == polys[i_poly][1]) {
const uchar(*polys_step)[2] = polys + i_poly_prev;
const uint polys_step_len = i_poly - i_poly_prev;
BLI_assert(polys_step_len >= 2);
for (uint i_prev = polys_step_len - 1, i = 0; i < polys_step_len; i_prev = i++) {
union {
uint16_t as_u16[2];
uint32_t as_u32;
} data;
data.as_u16[0] = *(reinterpret_cast<const uint16_t *>(polys_step[i_prev]));
data.as_u16[1] = *(reinterpret_cast<const uint16_t *>(polys_step[i]));
if (data.as_u16[0] > data.as_u16[1]) {
std::swap(data.as_u16[0], data.as_u16[1]);
}
*lines_step = data.as_u32;
lines_step++;
}
i_poly++;
i_poly_prev = i_poly;
/* ignore the duplicate point */
}
}
uint lines_len = lines_step - lines;
/* Hide Lines (we could make optional) */
{
qsort(lines, lines_len, sizeof(int32_t), BLI_sortutil_cmp_int);
lines_step = lines;
for (uint i_prev = 0, i = 1; i < lines_len; i_prev = i++) {
if (lines[i] != lines[i_prev]) {
*lines_step++ = lines[i_prev];
}
else {
i++;
}
}
*lines_step++ = lines[lines_len - 1];
lines_len = lines_step - lines;
}
/* We have vertices and tris, make a batch from this. */
static GPUVertFormat format = {0};
static struct {
uint pos;
} attr_id;
if (format.attr_len == 0) {
attr_id.pos = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32);
}
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
const uint vbo_len_capacity = lines_len * 2;
GPU_vertbuf_data_alloc(*vbo, vbo_len_capacity);
GPUVertBufRaw pos_step;
GPU_vertbuf_attr_get_raw_data(vbo, attr_id.pos, &pos_step);
for (uint i = 0; i < lines_len; i++) {
union {
uint8_t as_u8_pair[2][2];
uint32_t as_u32;
} data;
data.as_u32 = lines[i];
for (uint k = 0; k < 2; k++) {
float *pos_v2 = static_cast<float *>(GPU_vertbuf_raw_step(&pos_step));
for (uint j = 0; j < 2; j++) {
pos_v2[j] = min_uchar[j] + (float(data.as_u8_pair[k][j]) * range_uchar[j]);
}
}
}
BLI_assert(vbo_len_capacity == GPU_vertbuf_raw_used(&pos_step));
MEM_delete(lines);
return GPU_batch_create_ex(GPU_PRIM_LINES, vbo, nullptr, GPU_BATCH_OWNS_VBO);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Common shapes (3D)
* \{ */
gpu::Batch *GPU_batch_unit_cube()
{
static const std::array<float3, 8> bone_box_verts = {
float3{1.0f, -1.0f, 1.0f},
float3{1.0f, -1.0f, -1.0f},
float3{-1.0f, -1.0f, -1.0f},
float3{-1.0f, -1.0f, 1.0f},
float3{1.0f, 1.0f, 1.0f},
float3{1.0f, 1.0f, -1.0f},
float3{-1.0f, 1.0f, -1.0f},
float3{-1.0f, 1.0f, 1.0f},
};
static const std::array<int3, 12> bone_box_solid_tris = {
int3{0, 2, 1}, /* bottom */
int3{0, 3, 2},
int3{0, 1, 5}, /* sides */
int3{0, 5, 4},
int3{1, 2, 6},
int3{1, 6, 5},
int3{2, 3, 7},
int3{2, 7, 6},
int3{3, 0, 4},
int3{3, 4, 7},
int3{4, 5, 6}, /* top */
int3{4, 6, 7},
};
GPUVertFormat format = {0};
GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
const int tri_len = bone_box_solid_tris.size();
const int vert_len = bone_box_verts.size();
GPU_vertbuf_data_alloc(*vbo, vert_len);
GPUIndexBufBuilder elb;
GPU_indexbuf_init(&elb, GPU_PRIM_TRIS, tri_len, vert_len);
int v = 0;
for (int i = 0; i < vert_len; i++) {
GPU_vertbuf_vert_set(vbo, v++, &bone_box_verts[i]);
}
for (int i = 0; i < tri_len; i++) {
const int3 tri_indices = bone_box_solid_tris[i];
GPU_indexbuf_add_tri_verts(&elb, tri_indices[0], tri_indices[1], tri_indices[2]);
}
return GPU_batch_create_ex(
GPU_PRIM_TRIS, vbo, GPU_indexbuf_build(&elb), GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,237 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Wrap OpenGL features such as textures, shaders and GLSL
* with checks for drivers and GPU support.
*/
#include "DNA_userdef_types.h" /* For `U.glreslimit`. */
#include "GPU_capabilities.hh"
#include "gpu_context_private.hh"
#include "gpu_capabilities_private.hh"
namespace blender {
namespace gpu {
GPUCapabilities GCaps = {};
}
using namespace blender::gpu;
/* -------------------------------------------------------------------- */
/** \name Capabilities
* \{ */
int GPU_max_texture_size()
{
return GCaps.max_texture_size;
}
int GPU_max_texture_3d_size()
{
return GCaps.max_texture_3d_size;
}
uint32_t GPU_max_buffer_texture_size()
{
return GCaps.max_buffer_texture_size;
}
int GPU_texture_size_with_limit(int res)
{
int size = GPU_max_texture_size();
int reslimit = (U.glreslimit != 0) ? min_ii(U.glreslimit, size) : size;
return min_ii(reslimit, res);
}
bool GPU_is_safe_texture_size(int width, int height)
{
const int max_texture_size = GPU_max_texture_size();
return size_t(width) * height <= size_t(max_texture_size) * max_texture_size / 4;
}
int GPU_max_texture_layers()
{
return GCaps.max_texture_layers;
}
int GPU_max_textures()
{
return GCaps.max_textures;
}
int GPU_max_images()
{
return GCaps.max_images;
}
int GPU_max_work_group_count(int index)
{
return GCaps.max_work_group_count[index];
}
int GPU_max_work_group_size(int index)
{
return GCaps.max_work_group_size[index];
}
int GPU_max_uniforms_vert()
{
return GCaps.max_uniforms_vert;
}
int GPU_max_uniforms_frag()
{
return GCaps.max_uniforms_frag;
}
int GPU_max_batch_indices()
{
return GCaps.max_batch_indices;
}
int GPU_max_batch_vertices()
{
return GCaps.max_batch_vertices;
}
int GPU_max_vertex_attribs()
{
return GCaps.max_vertex_attribs;
}
int GPU_max_varying_floats()
{
return GCaps.max_varying_floats;
}
int GPU_extensions_len()
{
return GCaps.extensions_len;
}
const char *GPU_extension_get(int i)
{
return GCaps.extension_get ? GCaps.extension_get(i) : "\0";
}
bool GPU_use_subprocess_compilation()
{
return GCaps.use_subprocess_shader_compilations;
}
int GPU_max_parallel_compilations()
{
return GCaps.max_parallel_compilations;
}
bool GPU_depth_blitting_workaround()
{
return GCaps.depth_blitting_workaround;
}
bool GPU_use_main_context_workaround()
{
return GCaps.use_main_context_workaround;
}
bool GPU_use_hq_normals_workaround()
{
return GCaps.use_hq_normals_workaround;
}
bool GPU_stencil_clasify_buffer_workaround()
{
return GCaps.stencil_clasify_buffer_workaround;
}
bool GPU_geometry_shader_support()
{
return GCaps.geometry_shader_support;
}
bool GPU_hdr_support()
{
return GCaps.hdr_viewport_support;
}
bool GPU_stencil_export_support()
{
return GCaps.stencil_export_support;
}
int GPU_max_shader_storage_buffer_bindings()
{
return GCaps.max_shader_storage_buffer_bindings;
}
int GPU_max_compute_shader_storage_blocks()
{
return GCaps.max_compute_shader_storage_blocks;
}
int GPU_minimum_per_vertex_stride()
{
return GCaps.minimum_per_vertex_stride;
}
size_t GPU_max_uniform_buffer_size()
{
return GCaps.max_uniform_buffer_size;
}
size_t GPU_max_storage_buffer_size()
{
return GCaps.max_storage_buffer_size;
}
size_t GPU_storage_buffer_alignment()
{
return GCaps.storage_buffer_alignment;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Memory statistics
* \{ */
bool GPU_mem_stats_supported()
{
return GCaps.mem_stats_support;
}
void GPU_mem_stats_get(int *r_totalmem, int *r_freemem)
{
Context::get()->memory_statistics_get(r_totalmem, r_freemem);
}
bool GPU_stereo_quadbuffer_support()
{
return Context::get()->front_right != nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Creator arguments overrides
* \{ */
void GPU_compilation_subprocess_override_set(int count)
{
BLI_assert(GCaps.max_parallel_compilations == -1);
GCaps.max_parallel_compilations = count;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_sys_types.h"
namespace blender::gpu {
/**
* This includes both hardware capabilities & workarounds.
* Try to limit these to the implementation code-base (i.e.: `gpu/opengl/`).
* Only add workarounds here if they are common to all implementation or
* if you need access to it outside of the GPU module.
* Same goes for capabilities (i.e.: texture size).
*/
struct GPUCapabilities {
int max_texture_size = 0;
int max_texture_3d_size = 0;
uint32_t max_buffer_texture_size = 0;
int max_texture_layers = 0;
int max_textures = 0;
int max_images = 0;
int max_work_group_count[3] = {0, 0, 0};
int max_work_group_size[3] = {0, 0, 0};
int max_uniforms_vert = 0;
int max_uniforms_frag = 0;
int max_batch_indices = 0;
int max_batch_vertices = 0;
int max_vertex_attribs = 0;
int max_varying_floats = 0;
int max_shader_storage_buffer_bindings = 0;
int max_compute_shader_storage_blocks = 0;
size_t max_uniform_buffer_size = 0;
size_t max_storage_buffer_size = 0;
size_t storage_buffer_alignment = 256;
int extensions_len = 0;
const char *(*extension_get)(int);
bool mem_stats_support = false;
bool geometry_shader_support = false;
bool hdr_viewport_support = false;
bool stencil_export_support = false;
int max_parallel_compilations = -1;
/* OpenGL related workarounds. */
bool depth_blitting_workaround = false;
bool use_main_context_workaround = false;
bool use_hq_normals_workaround = false;
bool stencil_clasify_buffer_workaround = false;
bool texture_pool_workaround = false;
bool use_subprocess_shader_compilations = false;
/* Metal related workarounds. */
/* Minimum per-vertex stride in bytes (For a vertex buffer). */
int minimum_per_vertex_stride = 1;
};
extern GPUCapabilities GCaps;
} // namespace blender::gpu

View File

@@ -0,0 +1,600 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Convert material node-trees to GLSL.
*/
#include "MEM_guardedalloc.h"
#include "DNA_material_types.h"
#include "BLI_span.hh"
#include "BLI_string.h"
#include "BLI_vector.hh"
#include "BKE_cryptomatte.hh"
#include "IMB_colormanagement.hh"
#include "GPU_capabilities.hh"
#include "GPU_shader.hh"
#include "GPU_uniform_buffer.hh"
#include "GPU_vertex_format.hh"
#include "gpu_codegen.hh"
#include "gpu_material_library.hh"
#include "gpu_shader_dependency_private.hh"
#include <cstdarg>
#include <cstring>
namespace blender {
using namespace blender::gpu::shader;
/* -------------------------------------------------------------------- */
/** \name Type > string conversion
* \{ */
static std::ostream &operator<<(std::ostream &stream, const GPUInput *input)
{
switch (input->source) {
case GPU_SOURCE_FUNCTION_CALL:
case GPU_SOURCE_OUTPUT:
return stream << (input->is_zone_io ? "zone" : "tmp") << input->id;
case GPU_SOURCE_CONSTANT:
return stream << (input->is_zone_io ? "zone" : "cons") << input->id;
case GPU_SOURCE_UNIFORM:
return stream << "node_tree.u" << input->id << (input->is_duplicate ? "b" : "");
case GPU_SOURCE_ATTR:
return stream << "var_attrs.v" << input->attr->id;
case GPU_SOURCE_UNIFORM_ATTR:
return stream << "UNI_ATTR(unf_attrs[resource_id].attr" << input->uniform_attr->id << ")";
case GPU_SOURCE_LAYER_ATTR:
return stream << "attr_load_layer(" << input->layer_attr->hash_code << ")";
case GPU_SOURCE_STRUCT:
return stream << "strct" << input->id;
case GPU_SOURCE_TEX:
return stream << input->texture->sampler_name;
case GPU_SOURCE_TEX_TILED_MAPPING:
return stream << input->texture->tiled_mapping_name;
default:
BLI_assert(0);
return stream;
}
}
static std::ostream &operator<<(std::ostream &stream, const GPUOutput *output)
{
return stream << (output->is_zone_io ? "zone" : "tmp") << output->id;
}
/* Print data constructor (i.e: vec2(1.0f, 1.0f)). */
static std::ostream &operator<<(std::ostream &stream, const Span<float> &span)
{
stream << GPUType(span.size()) << "(";
/* Use uint representation to allow exact same bit pattern even if NaN. This is
* because we can pass UINTs as floats for constants. */
const Span<uint32_t> uint_span = span.cast<uint32_t>();
for (const uint32_t &element : uint_span) {
char formatted_float[32];
SNPRINTF(formatted_float, "uintBitsToFloat(%uu)", element);
stream << formatted_float;
if (&element != &uint_span.last()) {
stream << ", ";
}
}
stream << ")";
return stream;
}
/* Trick type to change overload and keep a somewhat nice syntax. */
struct GPUConstant : public GPUInput {};
static std::ostream &operator<<(std::ostream &stream, const GPUConstant *input)
{
stream << Span<float>(input->vec, input->type);
return stream;
}
namespace gpu::shader {
/* Needed to use the << operators from nested namespaces. :(
* https://stackoverflow.com/questions/5195512/namespaces-and-operator-resolution */
using blender::operator<<;
} // namespace gpu::shader
/** \} */
/* -------------------------------------------------------------------- */
/** \name GLSL code generation
* \{ */
const char *GPUCodegenCreateInfo::NameBuffer::append_sampler_name(const char name[32])
{
auto index = sampler_names.size();
sampler_names.append(std::make_unique<NameEntry>());
char *name_buffer = sampler_names[index]->data();
memcpy(name_buffer, name, 32);
return name_buffer;
}
GPUCodegen::GPUCodegen(GPUMaterial *mat_, GPUNodeGraph *graph_, const char *debug_name)
: mat(*mat_), graph(*graph_)
{
BLI_hash_mm2a_init(&hm2a_, GPU_material_uuid_get(&mat));
BLI_hash_mm2a_add_int(&hm2a_, GPU_material_flag(&mat));
create_info = MEM_new<GPUCodegenCreateInfo>(__func__, debug_name);
output.create_info = reinterpret_cast<GPUShaderCreateInfo *>(
static_cast<ShaderCreateInfo *>(create_info));
}
GPUCodegen::~GPUCodegen()
{
MEM_SAFE_DELETE(cryptomatte_input_);
MEM_delete(create_info);
ubo_inputs_.free_no_destruct();
};
bool GPUCodegen::should_optimize_heuristic() const
{
/* If each of the maximal attributes are exceeded, we can optimize, but we should also ensure
* the baseline is met. */
bool do_optimize = (nodes_total_ >= 60 || textures_total_ >= 4 || uniforms_total_ >= 64) &&
(textures_total_ >= 1 && uniforms_total_ >= 8 && nodes_total_ >= 4);
return do_optimize;
}
void GPUCodegen::generate_attribs()
{
if (graph.attributes.is_empty()) {
output.attr_load.clear();
return;
}
GPUCodegenCreateInfo &info = *create_info;
info.interface_generated = MEM_new<StageInterfaceInfo>(__func__, "codegen_iface", "var_attrs");
StageInterfaceInfo &iface = *info.interface_generated;
info.vertex_out(iface);
/* Input declaration, loading / assignment to interface and geometry shader passthrough. */
std::stringstream load_ss;
/* Index of the attribute as ordered in graph.attributes. */
int attr_n = 0;
int slot = 15;
for (GPUMaterialAttribute &attr : graph.attributes) {
if (slot == -1) {
BLI_assert_msg(0, "Too many attributes");
break;
}
STRNCPY(info.name_buffer.attr_names[slot], attr.input_name);
SNPRINTF(info.name_buffer.var_names[slot], "v%d", attr.id);
StringRefNull attr_name = info.name_buffer.attr_names[slot];
StringRefNull var_name = info.name_buffer.var_names[slot];
GPUType input_type, iface_type;
load_ss << "var_attrs." << var_name;
if (attr.is_hair_length || attr.is_hair_intercept) {
iface_type = input_type = GPU_FLOAT;
load_ss << " = attr_load_" << input_type << "(domain, " << attr_name << ", " << attr_n
<< ");\n";
}
else {
switch (attr.type) {
case CD_ORCO:
/* Need vec4 to detect usage of default attribute. */
input_type = GPU_VEC4;
iface_type = GPU_VEC3;
load_ss << " = attr_load_orco(domain, " << attr_name << ", " << attr_n << ");\n";
break;
case CD_TANGENT:
iface_type = input_type = GPU_VEC4;
load_ss << " = attr_load_tangent(domain, " << attr_name << ", " << attr_n << ");\n";
break;
default:
iface_type = input_type = GPU_VEC4;
load_ss << " = attr_load_" << input_type << "(domain, " << attr_name << ", " << attr_n
<< ");\n";
break;
}
}
attr_n++;
info.vertex_in(slot--, to_type(input_type), attr_name);
iface.smooth(to_type(iface_type), var_name);
}
output.attr_load = load_ss.str();
}
void GPUCodegen::generate_resources()
{
GPUCodegenCreateInfo &info = *create_info;
std::stringstream ss;
/* Textures. */
int slot = 0;
for (GPUMaterialTexture &tex : graph.textures) {
if (tex.colorband) {
const char *name = info.name_buffer.append_sampler_name(tex.sampler_name);
info.sampler(slot++, ImageType::Float1DArray, name, Frequency::BATCH);
}
else if (tex.sky) {
const char *name = info.name_buffer.append_sampler_name(tex.sampler_name);
info.sampler(0, ImageType::Float2DArray, name, Frequency::BATCH);
}
else if (tex.tiled_mapping_name[0] != '\0') {
const char *name = info.name_buffer.append_sampler_name(tex.sampler_name);
info.sampler(slot++, ImageType::Float2DArray, name, Frequency::BATCH);
const char *name_mapping = info.name_buffer.append_sampler_name(tex.tiled_mapping_name);
info.sampler(slot++, ImageType::Float1DArray, name_mapping, Frequency::BATCH);
}
else {
const char *name = info.name_buffer.append_sampler_name(tex.sampler_name);
info.sampler(slot++, ImageType::Float2D, name, Frequency::BATCH);
}
}
/* Increment heuristic. */
textures_total_ = slot;
if (!ubo_inputs_.is_empty()) {
const char *linted_struct_suffix = "_host_shared_";
/* NOTE: generate_uniform_buffer() should have sorted the inputs before this. */
ss << "struct NodeTree {\n";
for (LinkData &link : ubo_inputs_) {
GPUInput *input = static_cast<GPUInput *>(link.data);
if (input->source == GPU_SOURCE_CRYPTOMATTE) {
ss << input->type << " crypto_hash;\n";
}
else {
ss << input->type << " u" << input->id << (input->is_duplicate ? "b" : "") << ";\n";
}
}
ss << "};\n";
ss << "#define NodeTree" << linted_struct_suffix << " NodeTree\n";
ss << "#define NodeTree" << linted_struct_suffix << "uniform_ NodeTree\n";
ss << "\n";
info.uniform_buf(GPU_NODE_TREE_UBO_SLOT, "NodeTree", GPU_UBO_BLOCK_NAME, Frequency::BATCH);
}
if (!graph.uniform_attrs.list.is_empty()) {
ss << "struct UniformAttrs {\n";
for (GPUUniformAttr &attr : graph.uniform_attrs.list) {
ss << "vec4 attr" << attr.id << ";\n";
}
ss << "};\n\n";
/* TODO(fclem): Use the macro for length. Currently not working for EEVEE. */
/* DRW_RESOURCE_CHUNK_LEN = 512 */
info.uniform_buf(2, "UniformAttrs", GPU_ATTRIBUTE_UBO_BLOCK_NAME "[512]", Frequency::BATCH);
}
if (!graph.layer_attrs.is_empty()) {
info.additional_info("draw_layer_attributes");
}
info.typedef_source_generated = ss.str();
}
void GPUCodegen::node_serialize(Set<StringRefNull> &used_libraries,
std::stringstream &eval_ss,
const GPUNode *node)
{
gpu_material_library_use_function(used_libraries, node->name);
auto source_reference = [&](GPUInput *input) {
BLI_assert(ELEM(input->source, GPU_SOURCE_OUTPUT, GPU_SOURCE_ATTR));
/* These inputs can have non matching types. Do conversion. */
GPUType to = input->type;
GPUType from = (input->source == GPU_SOURCE_ATTR) ? input->attr->gputype :
input->link->output->type;
if (from != to) {
/* Use defines declared inside codegen_lib (e.g. vec4_from_float). */
eval_ss << to << "_from_" << from << "(";
}
if (input->source == GPU_SOURCE_ATTR) {
eval_ss << input;
}
else {
eval_ss << input->link->output;
}
if (from != to) {
/* Special case that needs luminance coefficients as argument. */
if (from == GPU_VEC4 && to == GPU_FLOAT) {
float coefficients[3];
IMB_colormanagement_get_luminance_coefficients(coefficients);
eval_ss << ", " << Span<float>(coefficients, 3);
}
eval_ss << ")";
}
};
/* Declare constants. */
for (GPUInput &input : node->inputs) {
auto type = [&]() {
/* Don't declare zone io variables twice. */
std::stringstream ss;
if (!input.is_duplicate) {
ss << input.type;
}
return ss.str();
};
switch (input.source) {
case GPU_SOURCE_FUNCTION_CALL:
eval_ss << type() << " " << &input << "; " << input.function_call << &input << ");\n";
break;
case GPU_SOURCE_STRUCT:
eval_ss << input.type << " " << &input << " = CLOSURE_DEFAULT;\n";
break;
case GPU_SOURCE_CONSTANT:
if (!input.is_duplicate) {
eval_ss << type() << " " << &input << " = " << static_cast<GPUConstant *>(&input)
<< ";\n";
}
break;
case GPU_SOURCE_OUTPUT:
case GPU_SOURCE_ATTR:
if (input.is_zone_io) {
eval_ss << type() << " " << &input << " = ";
source_reference(&input);
eval_ss << ";\n";
}
break;
default:
if (input.is_zone_io && (!input.is_duplicate || !input.link)) {
eval_ss << type() << " zone" << input.id << " = " << &input << ";\n";
}
break;
}
}
/* Declare temporary variables for node output storage. */
for (GPUOutput &output : node->outputs) {
if (output.is_zone_io) {
break;
}
eval_ss << output.type << " " << &output << ";\n";
}
/* Function call. */
eval_ss << node->name << "(";
/* Input arguments. */
for (GPUInput &input : node->inputs) {
if (input.is_zone_io) {
break;
}
switch (input.source) {
case GPU_SOURCE_OUTPUT:
case GPU_SOURCE_ATTR: {
source_reference(&input);
break;
}
default:
eval_ss << &input;
break;
}
GPUOutput *output = static_cast<GPUOutput *>(node->outputs.first);
if ((input.next && !input.next->is_zone_io) || (output && !output->is_zone_io)) {
eval_ss << ", ";
}
}
/* Output arguments. */
for (GPUOutput &output : node->outputs) {
if (output.is_zone_io) {
break;
}
eval_ss << &output;
if (output.next && !output.next->is_zone_io) {
eval_ss << ", ";
}
}
eval_ss << ");\n\n";
/* Increment heuristic. */
nodes_total_++;
}
static Vector<StringRefNull> set_to_vector_stable(Set<StringRefNull> &set)
{
Vector<StringRefNull> source_files;
for (const StringRefNull &str : set) {
source_files.append(str);
}
/* Sort dependencies to avoid random order causing shader caching to fail (see #108289). */
std::ranges::sort(source_files);
return source_files;
}
GPUGraphOutput GPUCodegen::graph_serialize(GPUNodeTag tree_tag,
GPUNodeLink *output_link,
const char *output_default)
{
if (output_link == nullptr && output_default == nullptr) {
return {};
}
Set<StringRefNull> used_libraries;
std::stringstream eval_ss;
bool has_nodes = false;
/* NOTE: The node order is already top to bottom (or left to right in node editor)
* because of the evaluation order inside ntreeExecGPUNodes(). */
for (GPUNode &node : graph.nodes) {
if ((node.tag & tree_tag) == 0) {
continue;
}
node_serialize(used_libraries, eval_ss, &node);
has_nodes = true;
}
if (!has_nodes) {
return {};
}
if (output_link) {
eval_ss << "return " << output_link->output << ";\n";
}
else {
/* Default output in case there are only AOVs. */
eval_ss << "return " << output_default << ";\n";
}
std::string str = eval_ss.str();
BLI_hash_mm2a_add(&hm2a_, reinterpret_cast<const uchar *>(str.c_str()), str.size());
return {str, set_to_vector_stable(used_libraries)};
}
GPUGraphOutput GPUCodegen::graph_serialize(GPUNodeTag tree_tag)
{
std::stringstream eval_ss;
Set<StringRefNull> used_libraries;
for (GPUNode &node : graph.nodes) {
if (node.tag & tree_tag) {
node_serialize(used_libraries, eval_ss, &node);
}
}
std::string str = eval_ss.str();
BLI_hash_mm2a_add(&hm2a_, reinterpret_cast<const uchar *>(str.c_str()), str.size());
return {str, set_to_vector_stable(used_libraries)};
}
void GPUCodegen::generate_cryptomatte()
{
cryptomatte_input_ = MEM_new_zeroed<GPUInput>(__func__);
cryptomatte_input_->type = GPU_FLOAT;
cryptomatte_input_->source = GPU_SOURCE_CRYPTOMATTE;
float material_hash = 0.0f;
Material *material = GPU_material_get_material(&mat);
if (material) {
bke::cryptomatte::CryptomatteHash hash(material->id.name + 2,
BLI_strnlen(material->id.name + 2, MAX_NAME - 2));
material_hash = hash.float_encoded();
}
cryptomatte_input_->vec[0] = material_hash;
BLI_addtail(&ubo_inputs_, BLI_genericNodeN(cryptomatte_input_));
}
void GPUCodegen::generate_uniform_buffer()
{
/* Extract uniform inputs. */
for (GPUNode &node : graph.nodes) {
for (GPUInput &input : node.inputs) {
if (input.source == GPU_SOURCE_UNIFORM && !input.link) {
/* We handle the UBO uniforms separately. */
BLI_addtail(&ubo_inputs_, BLI_genericNodeN(&input));
uniforms_total_++;
}
}
}
if (!ubo_inputs_.is_empty()) {
/* This sorts the inputs based on size. */
GPU_material_uniform_buffer_create(&mat, &ubo_inputs_);
}
}
/* Sets id for unique names for all inputs, resources and temp variables. */
void GPUCodegen::set_unique_ids()
{
Map<int, GPUNode *> zone_starts;
Map<int, GPUNode *> zone_ends;
int id = 1;
for (GPUNode &node : graph.nodes) {
for (GPUInput &input : node.inputs) {
input.id = id++;
}
for (GPUOutput &output : node.outputs) {
output.id = id++;
}
if (node.zone_index != -1) {
auto &map = node.is_zone_end ? zone_ends : zone_starts;
map.add(node.zone_index, &node);
}
}
auto find_zone_io = [](auto first) {
while (first && !first->is_zone_io && first->next) {
first = first->next;
}
return first;
};
/* Assign the same id to inputs and outputs of start and end zones. */
for (GPUNode *end : zone_ends.values()) {
GPUInput *end_input = find_zone_io(static_cast<GPUInput *>(end->inputs.first));
GPUOutput *end_output = find_zone_io(static_cast<GPUOutput *>(end->outputs.first));
GPUNode *start = zone_starts.lookup(end->zone_index);
GPUInput *start_input = find_zone_io(static_cast<GPUInput *>(start->inputs.first));
GPUOutput *start_output = find_zone_io(static_cast<GPUOutput *>(start->outputs.first));
for (; start_input; start_input = start_input->next,
start_output = start_output->next,
end_input = end_input->next,
end_output = end_output->next)
{
start_output->id = start_input->id;
end_input->id = start_input->id;
end_output->id = start_input->id;
}
}
}
void GPUCodegen::generate_graphs()
{
set_unique_ids();
output.surface = graph_serialize(
GPU_NODE_TAG_SURFACE | GPU_NODE_TAG_AOV, graph.outlink_surface, "CLOSURE_DEFAULT");
output.volume = graph_serialize(GPU_NODE_TAG_VOLUME, graph.outlink_volume, "CLOSURE_DEFAULT");
output.displacement = graph_serialize(
GPU_NODE_TAG_DISPLACEMENT, graph.outlink_displacement, nullptr);
output.thickness = graph_serialize(GPU_NODE_TAG_THICKNESS, graph.outlink_thickness, nullptr);
if (!graph.outlink_compositor.is_empty()) {
output.composite = graph_serialize(GPU_NODE_TAG_COMPOSITOR);
}
if (!graph.material_functions.is_empty()) {
for (GPUNodeGraphFunctionLink &func_link : graph.material_functions) {
std::stringstream eval_ss;
/* Untag every node in the graph to avoid serializing nodes from other functions */
for (GPUNode &node : graph.nodes) {
node.tag &= ~GPU_NODE_TAG_FUNCTION;
}
/* Tag only the nodes needed for the current function */
gpu_nodes_tag(&graph, func_link.outlink, GPU_NODE_TAG_FUNCTION);
GPUGraphOutput graph = graph_serialize(GPU_NODE_TAG_FUNCTION, func_link.outlink);
eval_ss << "float " << func_link.name << "() {\n" << graph.serialized << "}\n\n";
output.material_functions.append({eval_ss.str(), graph.dependencies});
}
/* Leave the function tags as they were before serialization */
for (GPUNodeGraphFunctionLink &funclink : graph.material_functions) {
gpu_nodes_tag(&graph, funclink.outlink, GPU_NODE_TAG_FUNCTION);
}
}
for (GPUMaterialAttribute &attr : graph.attributes) {
BLI_hash_mm2a_add(&hm2a_, reinterpret_cast<uchar *>(attr.name), strlen(attr.name));
}
hash_ = BLI_hash_mm2a_end(&hm2a_);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Generate shader code from the intermediate node graph.
*/
#pragma once
#include "BLI_hash_mm2a.hh"
#include "BLI_listbase.h"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "GPU_material.hh"
#include "GPU_vertex_format.hh"
#include "gpu_node_graph.hh"
#include "gpu_shader_create_info.hh"
#include <sstream>
#include <string>
namespace blender::gpu::shader {
struct GPUCodegenCreateInfo : ShaderCreateInfo {
struct NameBuffer {
using NameEntry = std::array<char, 32>;
/** Duplicate attribute names to avoid reference the GPUNodeGraph directly. */
char attr_names[16][GPU_MAX_SAFE_ATTR_NAME + 1];
char var_names[16][8];
Vector<std::unique_ptr<NameEntry>, 16> sampler_names;
/* Returns the appended name memory location */
const char *append_sampler_name(const char name[32]);
};
/** Optional generated interface. */
StageInterfaceInfo *interface_generated = nullptr;
/** Optional name buffer containing names referenced by StringRefNull. */
NameBuffer name_buffer;
GPUCodegenCreateInfo(const char *name) : ShaderCreateInfo(name) {};
~GPUCodegenCreateInfo()
{
MEM_delete(interface_generated);
}
};
class GPUCodegen {
public:
GPUMaterial &mat;
GPUNodeGraph &graph;
GPUCodegenOutput output = {};
GPUCodegenCreateInfo *create_info = nullptr;
private:
uint32_t hash_ = 0;
BLI_HashMurmur2A hm2a_;
ListBaseT<LinkData> ubo_inputs_ = {nullptr, nullptr};
GPUInput *cryptomatte_input_ = nullptr;
/** Cache parameters for complexity heuristic. */
uint nodes_total_ = 0;
uint textures_total_ = 0;
uint uniforms_total_ = 0;
public:
GPUCodegen(GPUMaterial *mat_, GPUNodeGraph *graph_, const char *debug_name);
~GPUCodegen();
void generate_graphs();
void generate_cryptomatte();
void generate_uniform_buffer();
void generate_attribs();
void generate_resources();
uint32_t hash_get() const
{
return hash_;
}
/* Heuristic determined during pass codegen for whether a
* more optimal variant of this material should be compiled. */
bool should_optimize_heuristic() const;
private:
void set_unique_ids();
void node_serialize(Set<StringRefNull> &used_libraries,
std::stringstream &eval_ss,
const GPUNode *node);
GPUGraphOutput graph_serialize(GPUNodeTag tree_tag,
GPUNodeLink *output_link,
const char *output_default = nullptr);
GPUGraphOutput graph_serialize(GPUNodeTag tree_tag);
};
} // namespace blender::gpu::shader

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "GPU_compute.hh"
#include "gpu_backend.hh"
#include "gpu_debug_private.hh"
namespace blender {
void GPU_compute_dispatch(gpu::Shader *shader,
uint groups_x_len,
uint groups_y_len,
uint groups_z_len,
const gpu::shader::SpecializationConstants *constants_state)
{
gpu::GPUBackend &gpu_backend = *gpu::GPUBackend::get();
GPU_shader_bind(shader, constants_state);
#ifndef NDEBUG
gpu::debug_validate_binding_image_format();
#endif
gpu_backend.compute_dispatch(groups_x_len, groups_y_len, groups_z_len);
}
void GPU_compute_dispatch_indirect(gpu::Shader *shader,
gpu::StorageBuf *indirect_buf_,
const gpu::shader::SpecializationConstants *constants_state)
{
gpu::GPUBackend &gpu_backend = *gpu::GPUBackend::get();
gpu::StorageBuf *indirect_buf = reinterpret_cast<gpu::StorageBuf *>(indirect_buf_);
GPU_shader_bind(shader, constants_state);
#ifndef NDEBUG
gpu::debug_validate_binding_image_format();
#endif
gpu_backend.compute_dispatch_indirect(indirect_buf);
}
} // namespace blender

View File

@@ -0,0 +1,779 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Manage GL vertex array IDs in a thread-safe way
* Use these instead of glGenBuffers & its friends
* - alloc must be called from a thread that is bound
* to the context that will be used for drawing with
* this VAO.
* - free can be called from any thread
*/
#include "BKE_global.hh"
#include "BLI_assert.h"
#include "BLI_threads.h"
#include "BLI_vector_set.hh"
#include "DNA_userdef_types.h"
#include "GHOST_IContext.hh"
#include "GHOST_ISystem.hh"
#include "GHOST_Types.hh"
#include "GPU_context.hh"
#include "GPU_batch.hh"
#include "GPU_pass.hh"
#include "gpu_backend.hh"
#include "gpu_context_private.hh"
#include "gpu_matrix_private.hh"
#include "gpu_private.hh"
#include "gpu_shader_private.hh"
#ifdef WITH_VULKAN_BACKEND
# include "vk_backend.hh"
#endif
#ifdef WITH_OPENGL_BACKEND
# include "gl_backend.hh"
# include "gl_context.hh"
#endif
#ifdef WITH_METAL_BACKEND
# include "mtl_backend.hh"
#endif
#include "dummy_backend.hh"
#include "draw_debug.hh"
#include <cstdio>
#include <mutex>
namespace blender {
using namespace blender::gpu;
static thread_local Context *active_ctx = nullptr;
static Mutex backend_users_mutex;
static int num_backend_users = 0;
static void gpu_backend_create();
static void gpu_backend_discard();
/* -------------------------------------------------------------------- */
/** \name gpu::Context methods
* \{ */
namespace gpu {
Context::Context()
{
thread_ = pthread_self();
is_active_ = false;
matrix_state = GPU_matrix_state_create();
texture_pool = GPUBackend::get()->texturepool_alloc();
context_id = Context::context_counter;
Context::context_counter++;
}
Context::~Context()
{
/* Derived class should have called free_resources already. */
BLI_assert(front_left == nullptr);
BLI_assert(back_left == nullptr);
BLI_assert(front_right == nullptr);
BLI_assert(back_right == nullptr);
BLI_assert(texture_pool == nullptr);
/** IMPORTANT: Do not free resources (texture, batch, buffers) in this function. These objects
* are likely to reference the GL/VK/MTLContext which is already destroyed at this point. */
GPU_matrix_state_discard(matrix_state);
delete state_manager;
delete imm;
}
void Context::free_resources()
{
delete front_left;
delete back_left;
delete front_right;
delete back_right;
front_left = nullptr;
back_left = nullptr;
front_right = nullptr;
back_right = nullptr;
GPU_BATCH_DISCARD_SAFE(procedural_points_batch);
GPU_BATCH_DISCARD_SAFE(procedural_lines_batch);
GPU_BATCH_DISCARD_SAFE(procedural_triangles_batch);
GPU_BATCH_DISCARD_SAFE(procedural_triangle_strips_batch);
GPU_VERTBUF_DISCARD_SAFE(dummy_vbo);
delete texture_pool;
texture_pool = nullptr;
}
bool Context::is_active_on_thread()
{
return (this == active_ctx) && pthread_equal(pthread_self(), thread_);
}
Context *Context::get()
{
return active_ctx;
}
VertBuf *Context::dummy_vbo_get()
{
if (this->dummy_vbo) {
return this->dummy_vbo;
}
/* TODO(fclem): get rid of this dummy VBO. */
GPUVertFormat format = {0};
GPU_vertformat_attr_add(&format, "dummy", gpu::VertAttrType::SFLOAT_32);
this->dummy_vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*this->dummy_vbo, 1);
return this->dummy_vbo;
}
Batch *Context::procedural_points_batch_get()
{
if (procedural_points_batch) {
return procedural_points_batch;
}
procedural_points_batch = GPU_batch_create(GPU_PRIM_POINTS, dummy_vbo_get(), nullptr);
return procedural_points_batch;
}
Batch *Context::procedural_lines_batch_get()
{
if (procedural_lines_batch) {
return procedural_lines_batch;
}
procedural_lines_batch = GPU_batch_create(GPU_PRIM_LINES, dummy_vbo_get(), nullptr);
return procedural_lines_batch;
}
Batch *Context::procedural_triangles_batch_get()
{
if (procedural_triangles_batch) {
return procedural_triangles_batch;
}
procedural_triangles_batch = GPU_batch_create(GPU_PRIM_TRIS, dummy_vbo_get(), nullptr);
return procedural_triangles_batch;
}
Batch *Context::procedural_triangle_strips_batch_get()
{
if (procedural_triangle_strips_batch) {
return procedural_triangle_strips_batch;
}
procedural_triangle_strips_batch = GPU_batch_create(
GPU_PRIM_TRI_STRIP, dummy_vbo_get(), nullptr);
return procedural_triangle_strips_batch;
}
} // namespace gpu
/** \} */
/* -------------------------------------------------------------------- */
GPUContext *GPU_context_create(GHOST_IWindow *ghost_window, GHOST_IContext *ghost_context)
{
{
std::scoped_lock lock(backend_users_mutex);
if (num_backend_users == 0) {
/* Automatically create backend when first context is created. */
gpu_backend_create();
}
num_backend_users++;
}
Context *ctx = GPUBackend::get()->context_alloc(ghost_window, ghost_context);
GPU_context_active_set(wrap(ctx));
draw::DebugDraw::get().acquire();
return wrap(ctx);
}
void GPU_context_discard(GPUContext *ctx_)
{
Context *ctx = unwrap(ctx_);
BLI_assert(active_ctx == ctx);
draw::DebugDraw::get().release();
GPUBackend *backend = GPUBackend::get();
/* Flush any remaining printf while making sure we are inside render boundaries. */
backend->render_begin();
printf_end(ctx);
backend->render_end();
delete ctx;
active_ctx = nullptr;
{
std::scoped_lock lock(backend_users_mutex);
num_backend_users--;
BLI_assert(num_backend_users >= 0);
if (num_backend_users == 0) {
/* Discard backend when last context is discarded. */
gpu_backend_discard();
}
}
}
void GPU_context_active_set(GPUContext *ctx_)
{
Context *ctx = unwrap(ctx_);
if (active_ctx) {
GPU_shader_unbind();
active_ctx->deactivate();
}
active_ctx = ctx;
if (ctx) {
ctx->activate();
/* It can happen that the previous context drew with a different color-space.
* In the case where the new context is drawing with the same shader that was previously bound
* (shader binding optimization), the uniform would not be set again because the dirty flag
* would not have been set (since the color space of this new context never changed). The
* shader would reuse the same color-space as the previous context frame-buffer (see #137855).
*/
ctx->shader_builtin_srgb_is_dirty = true;
}
}
GPUContext *GPU_context_active_get()
{
return wrap(Context::get());
}
void GPU_context_begin_frame(GPUContext *ctx)
{
gpu::Context *_ctx = unwrap(ctx);
if (_ctx) {
_ctx->begin_frame();
}
}
void GPU_context_end_frame(GPUContext *ctx)
{
gpu::Context *_ctx = unwrap(ctx);
if (_ctx) {
_ctx->end_frame();
}
}
void GPU_context_debug_pipeline_creation(GPUContext *ctx, bool enable)
{
gpu::Context *_ctx = unwrap(ctx);
if (_ctx) {
_ctx->debug_pipeline_creation = enable;
}
}
/* -------------------------------------------------------------------- */
/** \name Main context global mutex
*
* Used to avoid crash on some old drivers.
* \{ */
static Mutex main_context_mutex;
void GPU_context_main_lock()
{
main_context_mutex.lock();
}
void GPU_context_main_unlock()
{
main_context_mutex.unlock();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPU Begin/end work blocks
*
* Used to explicitly define a per-frame block within which GPU work will happen.
* Used for global autoreleasepool flushing in Metal
* \{ */
void GPU_render_begin()
{
GPUBackend *backend = GPUBackend::get();
BLI_assert(backend);
/* WORKAROUND: Currently a band-aid for the heist production. Has no side effect for GL backend
* but should be fixed for Metal. */
if (backend) {
backend->render_begin();
printf_begin(active_ctx);
}
}
void GPU_render_end()
{
GPUBackend *backend = GPUBackend::get();
BLI_assert(backend);
if (backend) {
printf_end(active_ctx);
backend->render_end();
}
}
void GPU_render_step(bool force_resource_release)
{
GPUBackend *backend = GPUBackend::get();
BLI_assert(backend);
if (backend) {
printf_end(active_ctx);
backend->render_step(force_resource_release);
printf_begin(active_ctx);
}
GPU_pass_cache_update();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Backend selection
* \{ */
static GPUBackendType g_backend_type = GPU_BACKEND_OPENGL;
static std::optional<GPUBackendType> g_backend_type_override = std::nullopt;
static std::optional<bool> g_backend_type_supported = std::nullopt;
static std::optional<GHOST_GPUDevice> g_preferred_device_override = std::nullopt;
static std::optional<int> g_vsync_override = std::nullopt;
static GPUBackend *g_backend = nullptr;
static GHOST_ISystem *g_ghost_system = nullptr;
void GPU_backend_ghost_system_set(GHOST_ISystem *ghost_system_handle)
{
g_ghost_system = ghost_system_handle;
}
GHOST_ISystem *GPU_backend_ghost_system_get()
{
return g_ghost_system;
}
void GPU_backend_type_selection_set(const GPUBackendType backend)
{
g_backend_type = backend;
g_backend_type_supported = std::nullopt;
}
int GPU_backend_vsync_get()
{
return g_vsync_override.value();
}
void GPU_backend_vsync_set_override(const int vsync)
{
g_vsync_override = vsync;
}
bool GPU_backend_vsync_is_overridden()
{
return g_vsync_override.has_value();
}
GPUBackendType GPU_backend_type_selection_get()
{
return g_backend_type;
}
void GPU_backend_type_selection_set_override(const GPUBackendType backend_type)
{
g_backend_type_override = backend_type;
}
bool GPU_backend_type_selection_is_overridden()
{
return g_backend_type_override.has_value();
}
void GPU_backend_preferred_device_set_override(const int index,
const uint32_t vendor_id,
const uint32_t device_id)
{
BLI_assert(index >= 0);
GHOST_GPUDevice device = {};
device.is_override = true;
device.fail_on_invalid_override = false;
device.index = index;
device.vendor_id = vendor_id;
device.device_id = device_id;
g_preferred_device_override = device;
}
GHOST_GPUDevice GPU_backend_preferred_device_get()
{
if (g_preferred_device_override.has_value()) {
GHOST_GPUDevice device = g_preferred_device_override.value();
device.fail_on_invalid_override = (G.debug & G_DEBUG_GPU_DEVICE_NO_FALLBACK) != 0;
device.fallback_index = U.gpu_preferred_index;
device.fallback_vendor_id = U.gpu_preferred_vendor_id;
device.fallback_device_id = U.gpu_preferred_device_id;
return device;
}
GHOST_GPUDevice device = {};
device.is_override = false;
device.fail_on_invalid_override = false;
device.index = U.gpu_preferred_index;
device.vendor_id = U.gpu_preferred_vendor_id;
device.device_id = U.gpu_preferred_device_id;
device.fallback_index = device.index;
device.fallback_vendor_id = device.vendor_id;
device.fallback_device_id = device.device_id;
return device;
}
static const char *gpu_backend_type_name(const GPUBackendType backend_type)
{
switch (backend_type) {
case GPU_BACKEND_OPENGL:
return "OpenGL";
case GPU_BACKEND_VULKAN:
return "Vulkan";
case GPU_BACKEND_METAL:
return "Metal";
case GPU_BACKEND_NONE:
return "None";
case GPU_BACKEND_ANY:
break;
}
return "Unknown";
}
#ifdef WITH_VULKAN_BACKEND
void GPU_vulkan_supported_devices_print(FILE *fp)
{
blender::gpu::VKBackend::supported_devices_print(fp);
}
#endif
bool GPU_backend_type_selection_detect()
{
VectorSet<GPUBackendType> backends_to_check;
if (g_backend_type_override.has_value()) {
backends_to_check.add(*g_backend_type_override);
}
#if defined(WITH_OPENGL_BACKEND)
backends_to_check.add(GPU_BACKEND_OPENGL);
#elif defined(WITH_METAL_BACKEND)
backends_to_check.add(GPU_BACKEND_METAL);
#endif
#if defined(WITH_VULKAN_BACKEND)
backends_to_check.add(GPU_BACKEND_VULKAN);
#endif
for (const GPUBackendType backend_type : backends_to_check) {
GPU_backend_type_selection_set(backend_type);
if (GPU_backend_supported()) {
if (g_preferred_device_override.has_value() && backend_type != GPU_BACKEND_VULKAN) {
fprintf(stderr,
"Warning: '--gpu-device' is only supported for the Vulkan backend. "
"Ignoring the device override for the selected %s backend and falling back to the "
"stored GPU preference.\n",
gpu_backend_type_name(backend_type));
g_preferred_device_override.reset();
}
return true;
}
G.f |= G_FLAG_GPU_BACKEND_FALLBACK;
}
GPU_backend_type_selection_set(GPU_BACKEND_NONE);
return false;
}
static bool gpu_backend_supported()
{
switch (g_backend_type) {
case GPU_BACKEND_OPENGL:
#ifdef WITH_OPENGL_BACKEND
return true;
#else
return false;
#endif
case GPU_BACKEND_VULKAN:
#ifdef WITH_VULKAN_BACKEND
return VKBackend::is_supported();
#else
return false;
#endif
case GPU_BACKEND_METAL:
#ifdef WITH_METAL_BACKEND
return MTLBackend::metal_is_supported();
#else
return false;
#endif
case GPU_BACKEND_NONE:
return true;
default:
BLI_assert(false && "No backend specified");
return false;
}
}
bool GPU_backend_supported()
{
if (!g_backend_type_supported.has_value()) {
g_backend_type_supported = gpu_backend_supported();
}
return *g_backend_type_supported;
}
static void gpu_backend_create()
{
BLI_assert(g_backend == nullptr);
BLI_assert(GPU_backend_supported());
switch (g_backend_type) {
#ifdef WITH_OPENGL_BACKEND
case GPU_BACKEND_OPENGL:
g_backend = MEM_new<GLBackend>(__func__);
break;
#endif
#ifdef WITH_VULKAN_BACKEND
case GPU_BACKEND_VULKAN:
g_backend = MEM_new<VKBackend>(__func__);
break;
#endif
#ifdef WITH_METAL_BACKEND
case GPU_BACKEND_METAL:
g_backend = MEM_new<MTLBackend>(__func__);
break;
#endif
case GPU_BACKEND_NONE:
g_backend = MEM_new<DummyBackend>(__func__);
break;
default:
BLI_assert(0);
break;
}
}
void gpu_backend_init_resources()
{
BLI_assert(g_backend);
g_backend->init_resources();
}
void gpu_backend_delete_resources()
{
BLI_assert(g_backend);
g_backend->delete_resources();
}
void gpu_backend_discard()
{
/* TODO: assert no resource left. */
MEM_delete(g_backend);
g_backend = nullptr;
}
GPUBackendType GPU_backend_get_type()
{
#ifdef WITH_OPENGL_BACKEND
if (g_backend && dynamic_cast<GLBackend *>(g_backend) != nullptr) {
return GPU_BACKEND_OPENGL;
}
#endif
#ifdef WITH_METAL_BACKEND
if (g_backend && dynamic_cast<MTLBackend *>(g_backend) != nullptr) {
return GPU_BACKEND_METAL;
}
#endif
#ifdef WITH_VULKAN_BACKEND
if (g_backend && dynamic_cast<VKBackend *>(g_backend) != nullptr) {
return GPU_BACKEND_VULKAN;
}
#endif
return GPU_BACKEND_NONE;
}
const char *GPU_backend_get_name()
{
return gpu_backend_type_name(GPU_backend_get_type());
}
GPUBackend *GPUBackend::get()
{
return g_backend;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPUSecondaryContext
* \{ */
namespace gpu {
static GHOST_TDrawingContextType ghost_context_type()
{
switch (GPU_backend_type_selection_get()) {
#ifdef WITH_OPENGL_BACKEND
case GPU_BACKEND_OPENGL:
return GHOST_kDrawingContextTypeOpenGL;
#endif
#ifdef WITH_VULKAN_BACKEND
case GPU_BACKEND_VULKAN:
return GHOST_kDrawingContextTypeVulkan;
#endif
#ifdef WITH_METAL_BACKEND
case GPU_BACKEND_METAL:
return GHOST_kDrawingContextTypeMetal;
#endif
default:
BLI_assert_unreachable();
return GHOST_kDrawingContextTypeNone;
}
}
GPUSecondaryContextData GPU_create_secondary_context()
{
/* Contexts can only be created on the main thread. */
BLI_assert(BLI_thread_is_main());
GHOST_IContext *main_thread_ghost_context = GHOST_IContext::getActiveDrawingContext();
GPUContext *main_thread_gpu_context = GPU_context_active_get();
/* GPU settings for context creation. */
GHOST_GPUSettings gpu_settings = {0};
gpu_settings.context_type = ghost_context_type();
if (G.debug & G_DEBUG_GPU) {
gpu_settings.flags |= GHOST_gpuDebugContext;
}
gpu_settings.preferred_device = GPU_backend_preferred_device_get();
/* Grab the system handle. */
GHOST_ISystem *ghost_system = GPU_backend_ghost_system_get();
BLI_assert(ghost_system);
/* Create a Ghost GPU Context using the system handle. */
GHOST_IContext *ghost_context = ghost_system->createOffscreenContext(gpu_settings);
BLI_assert(ghost_context);
/* Activate it so GPU_context_create has a valid device for info queries. */
ghost_context->activateDrawingContext();
/* Create a GPU context for the secondary thread to use. */
GPUContext *gpu_context = GPU_context_create(nullptr, ghost_context);
BLI_assert(gpu_context);
/* Release the Ghost GPU Context from this thread. */
const GHOST_TSuccess success = ghost_context->releaseDrawingContext();
BLI_assert(success);
UNUSED_VARS_NDEBUG(success);
/* Restore the main thread contexts.
* (required as the above context creation also makes it active).
* Contexts can be unset when called from the event system (#157456) */
if (main_thread_ghost_context) {
main_thread_ghost_context->activateDrawingContext();
}
if (main_thread_gpu_context) {
GPU_context_active_set(main_thread_gpu_context);
}
return GPUSecondaryContextData{.ghost_context = ghost_context, .gpu_context = gpu_context};
}
void GPU_activate_secondary_context(const GPUSecondaryContextData &data)
{
data.ghost_context->activateDrawingContext();
GPU_context_active_set(data.gpu_context);
}
void GPU_deactivate_secondary_context(const GPUSecondaryContextData &data)
{
GPU_context_active_set(nullptr);
data.ghost_context->releaseDrawingContext();
}
void GPU_destroy_secondary_context(GPUSecondaryContextData &data)
{
GHOST_IContext *cur_ghost_context = GHOST_IContext::getActiveDrawingContext();
GPUContext *cur_gpu_context = GPU_context_active_get();
GPU_activate_secondary_context(data);
GPU_context_discard(data.gpu_context);
data.ghost_context->releaseDrawingContext();
GHOST_ISystem *ghost_system = GPU_backend_ghost_system_get();
ghost_system->disposeContext(data.ghost_context);
/* Restore previous GHOST/GPU contexts, unless they are the same
* as what is being destroyed. */
if (cur_ghost_context && cur_ghost_context != data.ghost_context) {
cur_ghost_context->activateDrawingContext();
}
if (cur_gpu_context != data.gpu_context) {
GPU_context_active_set(cur_gpu_context);
}
data.ghost_context = nullptr;
data.gpu_context = nullptr;
}
GPUSecondaryContext::GPUSecondaryContext()
{
data_ = GPU_create_secondary_context();
}
GPUSecondaryContext::~GPUSecondaryContext()
{
/* Contexts should be destructed on the thread they were activated. */
BLI_assert(!BLI_thread_is_main());
GPU_context_discard(data_.gpu_context);
data_.ghost_context->releaseDrawingContext();
GHOST_ISystem *ghost_system = GPU_backend_ghost_system_get();
BLI_assert(ghost_system);
ghost_system->disposeContext(data_.ghost_context);
}
void GPUSecondaryContext::activate()
{
/* Contexts need to be activated in the thread they're going to be used. */
BLI_assert(!BLI_thread_is_main());
data_.ghost_context->activateDrawingContext();
GPU_context_active_set(data_.gpu_context);
}
/** \} */
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,181 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* This interface allow GPU to manage GL objects for multiple context and threads.
*/
#pragma once
#include "BKE_global.hh"
#include "GPU_batch.hh"
#include "GPU_context.hh"
#include "GPU_texture_pool.hh"
#include "gpu_debug_private.hh"
#include "gpu_framebuffer_private.hh"
#include "gpu_immediate_private.hh"
#include "gpu_shader_private.hh"
#include "gpu_state_private.hh"
#include <pthread.h>
class GHOST_IWindow;
namespace blender {
struct GPUMatrixState;
namespace gpu {
class Context {
public:
/** State management */
Shader *shader = nullptr;
FrameBuffer *active_fb = nullptr;
GPUMatrixState *matrix_state = nullptr;
StateManager *state_manager = nullptr;
Immediate *imm = nullptr;
/**
* All 4 window frame-buffers.
* None of them are valid in an off-screen context.
* Right frame-buffers are only available if using stereo rendering.
* Front frame-buffers contains (in principle, but not always) the last frame color.
* Default frame-buffer is back_left.
*/
FrameBuffer *back_left = nullptr;
FrameBuffer *front_left = nullptr;
FrameBuffer *back_right = nullptr;
FrameBuffer *front_right = nullptr;
DebugStack debug_stack;
bool debug_is_capturing = false;
bool debug_pipeline_creation = false;
/* GPUContext counter used to assign a unique ID to each GPUContext.
* NOTE(Metal): This is required by the Metal Backend, as a bug exists in the global OS shader
* cache wherein compilation of identical source from two distinct threads can result in an
* invalid cache collision, result in a broken shader object. Appending the unique context ID
* onto compiled sources ensures the source hashes are different. */
static inline std::atomic<int> context_counter = 0;
int context_id = 0;
/* Used as a stack. Each render_begin/end pair will push pop from the stack. */
Vector<StorageBuf *> printf_buf;
/** Dummy VBO to feed the procedural batches. */
VertBuf *dummy_vbo = nullptr;
/** Dummy batches for procedural geometry rendering. */
Batch *procedural_points_batch = nullptr;
Batch *procedural_lines_batch = nullptr;
Batch *procedural_triangles_batch = nullptr;
Batch *procedural_triangle_strips_batch = nullptr;
/** Texture pool used to recycle temporary texture (or render target) memory. */
TexturePool *texture_pool = nullptr;
/** Global state to avoid setting the srgb builtin uniform for every shader bind. */
int shader_builtin_srgb_transform = 0;
bool shader_builtin_srgb_is_dirty = false;
protected:
/** Thread on which this context is active. */
pthread_t thread_;
bool is_active_;
/** Can be nullptr for off-screen contexts. */
GHOST_IWindow *ghost_window_;
public:
Context();
virtual ~Context();
static Context *get();
virtual void activate() = 0;
virtual void deactivate() = 0;
virtual void begin_frame() = 0;
virtual void end_frame() = 0;
/* Will push all pending commands to the GPU. */
virtual void flush() = 0;
/* Will wait until the GPU has finished executing all command. */
virtual void finish() = 0;
virtual void memory_statistics_get(int *r_total_mem, int *r_free_mem) = 0;
virtual void debug_group_begin(const char * /*name*/, int /*index*/) {};
virtual void debug_group_end() {};
/* Returns true if capture successfully started. */
virtual bool debug_capture_begin(const char *title) = 0;
virtual void debug_capture_end() = 0;
virtual void *debug_capture_scope_create(const char *name) = 0;
virtual bool debug_capture_scope_begin(void *scope) = 0;
virtual void debug_capture_scope_end(void *scope) = 0;
/* Consider all buffers slot empty after these call for error checking.
* But doesn't really free them. */
virtual void debug_unbind_all_ubo() = 0;
virtual void debug_unbind_all_ssbo() = 0;
bool is_active_on_thread();
VertBuf *dummy_vbo_get();
Batch *procedural_points_batch_get();
Batch *procedural_lines_batch_get();
Batch *procedural_triangles_batch_get();
Batch *procedural_triangle_strips_batch_get();
/* When using `--debug-gpu`, assert that the shader fragments write to all the writable
* attachments of the bound frame-buffer. */
void assert_framebuffer_shader_compatibility(Shader *sh)
{
if (!(G.debug & G_DEBUG_GPU)) {
return;
}
if (!(state_manager->state.write_mask & GPUWriteMask::GPU_WRITE_COLOR)) {
return;
}
uint16_t fragment_output_bits = sh->fragment_output_bits;
uint16_t fb_attachments_bits = active_fb->get_color_attachments_bitset();
if ((fb_attachments_bits & ~fragment_output_bits) != 0) {
std::string msg;
msg = msg + "Shader (" + sh->name_get() + ") does not write to all frame-buffer (" +
active_fb->name_get() + ") color attachments";
BLI_assert_msg(false, msg.c_str());
std::cerr << msg << std::endl;
}
}
protected:
/**
* Derived classes should call this from the destructor,
* as freeing textures and frame-buffers may need the derived context to be valid.
*/
void free_resources();
};
/* Syntactic sugar. */
static inline GPUContext *wrap(Context *ctx)
{
return reinterpret_cast<GPUContext *>(ctx);
}
static inline Context *unwrap(GPUContext *ctx)
{
return reinterpret_cast<Context *>(ctx);
}
static inline const Context *unwrap(const GPUContext *ctx)
{
return reinterpret_cast<const Context *>(ctx);
}
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,226 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Debug features of OpenGL.
*/
#include "BKE_global.hh"
#include "BLI_string.h"
#include "gpu_context_private.hh"
#include "GPU_debug.hh"
namespace blender {
using namespace blender::gpu;
void GPU_debug_group_begin(const char *name)
{
if (!(G.debug & G_DEBUG_GPU) && !G.profile_gpu) {
return;
}
Context *ctx = Context::get();
DebugStack &stack = ctx->debug_stack;
stack.append(StringRef(name));
ctx->debug_group_begin(name, stack.size());
}
void GPU_debug_group_end()
{
if (!(G.debug & G_DEBUG_GPU) && !G.profile_gpu) {
return;
}
Context *ctx = Context::get();
ctx->debug_stack.pop_last();
ctx->debug_group_end();
}
void GPU_debug_get_groups_names(int name_buf_len, char *r_name_buf)
{
Context *ctx = Context::get();
if (ctx == nullptr) {
return;
}
DebugStack &stack = ctx->debug_stack;
if (stack.is_empty()) {
r_name_buf[0] = '\0';
return;
}
size_t len = 0;
for (StringRef &name : stack) {
len += BLI_snprintf_rlen(r_name_buf + len, name_buf_len - len, "%s > ", name.data());
}
r_name_buf[len - 3] = '\0';
}
std::string GPU_debug_get_groups_names(IndexRange levels)
{
Context *ctx = Context::get();
if (ctx == nullptr) {
return "";
}
DebugStack &stack = ctx->debug_stack;
if (stack.is_empty()) {
return "";
}
std::string result;
int i = 0;
for (StringRef &name : stack) {
if (levels.contains(i++)) {
result += name;
result += " > ";
}
}
return result.substr(0, result.size() - 3);
}
bool GPU_debug_group_match(const char *ref)
{
/* Otherwise there will be no names. */
BLI_assert(G.debug & G_DEBUG_GPU);
Context *ctx = Context::get();
if (ctx == nullptr) {
return false;
}
const DebugStack &stack = ctx->debug_stack;
for (const StringRef &name : stack) {
if (name == ref) {
return true;
}
}
return false;
}
void GPU_debug_capture_begin(const char *title)
{
/* GPU Frame capture is only enabled when --debug-gpu is specified. */
if (!(G.debug & G_DEBUG_GPU)) {
return;
}
Context *ctx = Context::get();
if (ctx && !ctx->debug_is_capturing) {
ctx->debug_is_capturing = ctx->debug_capture_begin(title);
/* Call GPU_finish to ensure all desired GPU commands occur within the capture boundary. */
GPU_finish();
}
}
void GPU_debug_capture_end()
{
/* GPU Frame capture is only enabled when --debug-gpu is specified. */
if (!(G.debug & G_DEBUG_GPU)) {
return;
}
Context *ctx = Context::get();
if (ctx && ctx->debug_is_capturing) {
/* Call GPU_finish to ensure all desired GPU commands occur within the capture boundary. */
GPU_finish();
ctx->debug_capture_end();
ctx->debug_is_capturing = false;
}
}
void *GPU_debug_capture_scope_create(const char *name)
{
/* GPU Frame capture is only enabled when --debug-gpu is specified. */
if (!(G.debug & G_DEBUG_GPU)) {
return nullptr;
}
Context *ctx = Context::get();
if (!ctx) {
return nullptr;
}
return ctx->debug_capture_scope_create(name);
}
bool GPU_debug_capture_scope_begin(void *scope)
{
/* Early exit if scope does not exist or not in debug mode. */
if (!(G.debug & G_DEBUG_GPU) || !scope) {
return false;
}
Context *ctx = Context::get();
if (!ctx) {
return false;
}
/* Declare beginning of capture scope region. */
bool scope_capturing = ctx->debug_capture_scope_begin(scope);
if (scope_capturing && !ctx->debug_is_capturing) {
/* Call GPU_finish to ensure all desired GPU commands occur within the capture boundary. */
GPU_finish();
ctx->debug_is_capturing = true;
}
return ctx->debug_is_capturing;
}
void GPU_debug_capture_scope_end(void *scope)
{
/* Early exit if scope does not exist or not in debug mode. */
if (!(G.debug & G_DEBUG_GPU) || !scope) {
return;
}
Context *ctx = Context::get();
if (!ctx) {
return;
}
/* If capturing, call GPU_finish to ensure all desired GPU commands occur within the capture
* boundary. */
if (ctx->debug_is_capturing) {
GPU_finish();
ctx->debug_is_capturing = false;
}
/* Declare end of capture scope region. */
ctx->debug_capture_scope_end(scope);
}
namespace gpu {
void debug_validate_binding_image_format()
{
if (!(G.debug & G_DEBUG_GPU)) {
return;
}
const auto &texture_formats_state = Context::get()->state_manager->image_formats;
const auto &texture_formats_shader = Context::get()->shader->interface->image_formats_;
for (int image_unit = 0; image_unit < GPU_MAX_IMAGE; image_unit++) {
TextureWriteFormat format_state = texture_formats_state[image_unit];
TextureWriteFormat format_shader = texture_formats_shader[image_unit];
if (format_state != TextureWriteFormat::Invalid &&
format_shader == TextureWriteFormat::Invalid)
{
/* It is allowed for an image to be bound in the state manager but to be unused in the
* shader. */
continue;
}
if (UNLIKELY(texture_formats_shader[image_unit] != texture_formats_state[image_unit])) {
fprintf(
stderr,
"Error in GPU_debug_validate_binding_image_format: Image format mismatch detected for "
"shader '%s' at binding %d (shader format '%s' vs. bound texture format '%s').\n",
Context::get()->shader->name_get().c_str(),
image_unit,
GPU_texture_format_name(to_texture_format(texture_formats_shader[image_unit])),
GPU_texture_format_name(to_texture_format(texture_formats_state[image_unit])));
BLI_assert_unreachable();
}
}
}
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* This interface allow GPU to manage GL objects for multiple context and threads.
*/
#pragma once
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
namespace blender::gpu {
using DebugStack = Vector<StringRef>;
void debug_validate_binding_image_format();
} // namespace blender::gpu

View File

@@ -0,0 +1,897 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "MEM_guardedalloc.h"
#include "BLI_math_base.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "CLG_log.h"
#include "GPU_capabilities.hh"
#include "GPU_texture.hh"
#include "gpu_backend.hh"
#include "gpu_context_private.hh"
#include "gpu_texture_private.hh"
#include "gpu_framebuffer_private.hh"
namespace blender {
namespace gpu {
static CLG_LogRef LOG = {"gpu.framebuffer"};
/* -------------------------------------------------------------------- */
/** \name Constructor / Destructor
* \{ */
FrameBuffer::FrameBuffer(const char *name)
{
if (name) {
STRNCPY(name_, name);
}
else {
name_[0] = '\0';
}
/* Force config on first use. */
dirty_attachments_ = true;
dirty_state_ = true;
for (GPUAttachment &attachment : attachments_) {
attachment.tex = nullptr;
attachment.mip = -1;
attachment.layer = -1;
}
}
FrameBuffer::~FrameBuffer()
{
for (GPUAttachment &attachment : attachments_) {
if (attachment.tex != nullptr) {
reinterpret_cast<Texture *>(attachment.tex)->detach_from(this);
}
}
#ifndef GPU_NO_USE_PY_REFERENCES
if (this->py_ref) {
*this->py_ref = nullptr;
}
#endif
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Attachments Management
* \{ */
void FrameBuffer::attachment_set(GPUAttachmentType type, const GPUAttachment &new_attachment)
{
if (new_attachment.mip == -1) {
return; /* GPU_ATTACHMENT_LEAVE */
}
if (type >= GPU_FB_MAX_ATTACHMENT) {
fprintf(stderr,
"GPUFramebuffer: Error: Trying to attach texture to type %d but maximum slot is %d.\n",
type - GPU_FB_COLOR_ATTACHMENT0,
GPU_FB_MAX_COLOR_ATTACHMENT);
return;
}
if (new_attachment.tex) {
if (new_attachment.layer > 0) {
BLI_assert(GPU_texture_is_cube(new_attachment.tex) ||
GPU_texture_is_array(new_attachment.tex));
}
if (GPU_texture_has_stencil_format(new_attachment.tex)) {
BLI_assert(ELEM(type, GPU_FB_DEPTH_STENCIL_ATTACHMENT));
}
else if (GPU_texture_has_depth_format(new_attachment.tex)) {
BLI_assert(ELEM(type, GPU_FB_DEPTH_ATTACHMENT));
}
}
GPUAttachment &attachment = attachments_[type];
set_color_attachment_bit(type, new_attachment.tex != nullptr);
if (attachment.tex == new_attachment.tex && attachment.layer == new_attachment.layer &&
attachment.mip == new_attachment.mip)
{
return; /* Exact same texture already bound here. */
}
/* Unbind previous and bind new. */
/* TODO(fclem): cleanup the casts. */
if (attachment.tex) {
reinterpret_cast<Texture *>(attachment.tex)->detach_from(this);
}
/* Might be null if this is for unbinding. */
if (new_attachment.tex) {
reinterpret_cast<Texture *>(new_attachment.tex)->attach_to(this, type);
}
else {
/* GPU_ATTACHMENT_NONE */
}
attachment = new_attachment;
dirty_attachments_ = true;
}
void FrameBuffer::attachment_remove(GPUAttachmentType type)
{
attachments_[type] = GPU_ATTACHMENT_NONE;
dirty_attachments_ = true;
set_color_attachment_bit(type, false);
}
void FrameBuffer::subpass_transition(const GPUAttachmentState depth_attachment_state,
Span<GPUAttachmentState> color_attachment_states)
{
/* NOTE: Depth is not supported as input attachment because the Metal API doesn't support it and
* because depth is not compatible with the framebuffer fetch implementation. */
BLI_assert(depth_attachment_state != GPU_ATTACHMENT_READ);
if (!attachments_[GPU_FB_DEPTH_ATTACHMENT].tex &&
!attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex)
{
BLI_assert(depth_attachment_state == GPU_ATTACHMENT_IGNORE);
}
BLI_assert(color_attachment_states.size() <= GPU_FB_MAX_COLOR_ATTACHMENT);
for (int i : IndexRange(GPU_FB_MAX_COLOR_ATTACHMENT)) {
GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0 + i;
if (this->attachments_[type].tex) {
BLI_assert(i < color_attachment_states.size());
set_color_attachment_bit(type, color_attachment_states[i] == GPU_ATTACHMENT_WRITE);
}
else {
set_color_attachment_bit(type, false);
BLI_assert(i >= color_attachment_states.size() ||
color_attachment_states[i] != GPU_ATTACHMENT_READ);
}
}
subpass_transition_impl(depth_attachment_state, color_attachment_states);
}
void FrameBuffer::load_store_config_array(const GPULoadStore *load_store_actions, uint actions_len)
{
/* Follows attachment structure of GPU_framebuffer_config_array/GPU_framebuffer_ensure_config */
const GPULoadStore &depth_action = load_store_actions[0];
Span<GPULoadStore> color_attachment_actions(load_store_actions + 1, actions_len - 1);
BLI_assert(color_attachment_actions.size() <= GPU_FB_MAX_COLOR_ATTACHMENT);
if (!attachments_[GPU_FB_DEPTH_ATTACHMENT].tex &&
!attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex)
{
BLI_assert(depth_action.load_action == GPU_LOADACTION_DONT_CARE &&
depth_action.store_action == GPU_STOREACTION_DONT_CARE);
}
if (this->attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT].tex) {
this->attachment_set_loadstore_op(GPU_FB_DEPTH_STENCIL_ATTACHMENT, depth_action);
}
if (this->attachments_[GPU_FB_DEPTH_ATTACHMENT].tex) {
this->attachment_set_loadstore_op(GPU_FB_DEPTH_ATTACHMENT, depth_action);
}
for (int i : IndexRange(GPU_FB_MAX_COLOR_ATTACHMENT)) {
GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0 + i;
if (this->attachments_[type].tex) {
BLI_assert(i < color_attachment_actions.size());
this->attachment_set_loadstore_op(type, color_attachment_actions[i]);
}
else {
BLI_assert(i >= color_attachment_actions.size() ||
(color_attachment_actions[i].load_action == GPU_LOADACTION_DONT_CARE &&
color_attachment_actions[i].store_action == GPU_STOREACTION_DONT_CARE));
}
}
}
uint FrameBuffer::get_bits_per_pixel()
{
uint total_bits = 0;
for (GPUAttachment &attachment : attachments_) {
Texture *tex = reinterpret_cast<Texture *>(attachment.tex);
if (tex != nullptr) {
int bits = to_bytesize(tex->format_get()) * to_component_len(tex->format_get());
total_bits += bits;
}
}
return total_bits;
}
/** \} */
} // namespace gpu
/* -------------------------------------------------------------------- */
/** \name C-API
* \{ */
using namespace blender::gpu;
gpu::FrameBuffer *GPU_framebuffer_create(const char *name)
{
/* We generate the FB object later at first use in order to
* create the frame-buffer in the right opengl context. */
return GPUBackend::get()->framebuffer_alloc(name);
}
void GPU_framebuffer_free(gpu::FrameBuffer *fb)
{
delete fb;
}
const char *GPU_framebuffer_get_name(gpu::FrameBuffer *fb)
{
return fb->name_get();
}
/* ---------- Binding ----------- */
void GPU_framebuffer_bind(gpu::FrameBuffer *fb)
{
const bool enable_srgb = true;
/* Disable custom loadstore and bind. */
fb->set_use_explicit_loadstore(false);
fb->bind(enable_srgb);
}
void GPU_framebuffer_bind_loadstore(gpu::FrameBuffer *fb,
const GPULoadStore *load_store_actions,
uint actions_len)
{
const bool enable_srgb = true;
/* Bind with explicit loadstore state */
fb->set_use_explicit_loadstore(true);
fb->bind(enable_srgb);
/* Update load store */
fb->load_store_config_array(load_store_actions, actions_len);
}
void GPU_framebuffer_subpass_transition_array(gpu::FrameBuffer *fb,
const GPUAttachmentState *attachment_states,
uint attachment_len)
{
fb->subpass_transition(attachment_states[0],
Span<GPUAttachmentState>(attachment_states + 1, attachment_len - 1));
}
void GPU_framebuffer_bind_no_srgb(gpu::FrameBuffer *fb)
{
const bool enable_srgb = false;
fb->bind(enable_srgb);
}
void GPU_backbuffer_bind(GPUBackBuffer back_buffer_type)
{
Context *ctx = Context::get();
if (back_buffer_type == GPU_BACKBUFFER_LEFT) {
ctx->back_left->bind(false);
}
else {
ctx->back_right->bind(false);
}
}
void GPU_framebuffer_restore()
{
Context::get()->back_left->bind(false);
}
gpu::FrameBuffer *GPU_framebuffer_active_get()
{
Context *ctx = Context::get();
return ctx ? ctx->active_fb : nullptr;
}
gpu::FrameBuffer *GPU_framebuffer_back_get()
{
Context *ctx = Context::get();
return ctx ? ctx->back_left : nullptr;
}
bool GPU_framebuffer_bound(gpu::FrameBuffer *gpu_fb)
{
return (gpu_fb == GPU_framebuffer_active_get());
}
/* ---------- Attachment Management ----------- */
bool GPU_framebuffer_check_valid(gpu::FrameBuffer *gpu_fb, char err_out[256])
{
return gpu_fb->check(err_out);
}
static void gpu_framebuffer_texture_attach_ex(gpu::FrameBuffer *gpu_fb,
GPUAttachment attachment,
int slot)
{
Texture *tex = reinterpret_cast<Texture *>(attachment.tex);
GPUAttachmentType type = tex->attachment_type(slot);
gpu_fb->attachment_set(type, attachment);
}
void GPU_framebuffer_texture_attach(gpu::FrameBuffer *fb, gpu::Texture *tex, int slot, int mip)
{
GPUAttachment attachment = GPU_ATTACHMENT_TEXTURE_MIP(tex, mip);
gpu_framebuffer_texture_attach_ex(fb, attachment, slot);
}
void GPU_framebuffer_texture_layer_attach(
gpu::FrameBuffer *fb, gpu::Texture *tex, int slot, int layer, int mip)
{
GPUAttachment attachment = GPU_ATTACHMENT_TEXTURE_LAYER_MIP(tex, layer, mip);
gpu_framebuffer_texture_attach_ex(fb, attachment, slot);
}
void GPU_framebuffer_texture_cubeface_attach(
gpu::FrameBuffer *fb, gpu::Texture *tex, int slot, int face, int mip)
{
GPUAttachment attachment = GPU_ATTACHMENT_TEXTURE_CUBEFACE_MIP(tex, face, mip);
gpu_framebuffer_texture_attach_ex(fb, attachment, slot);
}
void GPU_framebuffer_texture_detach(gpu::FrameBuffer *fb, gpu::Texture *tex)
{
tex->detach_from(fb);
}
void GPU_framebuffer_config_array(gpu::FrameBuffer *fb,
const GPUAttachment *config,
int config_len)
{
const GPUAttachment &depth_attachment = config[0];
Span<GPUAttachment> color_attachments(config + 1, config_len - 1);
if (depth_attachment.mip == -1) {
/* GPU_ATTACHMENT_LEAVE */
}
else if (depth_attachment.tex == nullptr) {
/* GPU_ATTACHMENT_NONE: Need to clear both targets. */
fb->attachment_set(GPU_FB_DEPTH_STENCIL_ATTACHMENT, depth_attachment);
fb->attachment_set(GPU_FB_DEPTH_ATTACHMENT, depth_attachment);
}
else {
GPUAttachmentType type = GPU_texture_has_stencil_format(depth_attachment.tex) ?
GPU_FB_DEPTH_STENCIL_ATTACHMENT :
GPU_FB_DEPTH_ATTACHMENT;
fb->attachment_set(type, depth_attachment);
}
GPUAttachmentType type = GPU_FB_COLOR_ATTACHMENT0;
for (const GPUAttachment &attachment : color_attachments) {
fb->attachment_set(type, attachment);
++type;
}
}
void GPU_framebuffer_default_size(gpu::FrameBuffer *gpu_fb, int width, int height)
{
gpu_fb->default_size_set(width, height);
}
int2 GPU_framebuffer_extent_get(gpu::FrameBuffer *gpu_fb)
{
return gpu_fb->size_get();
}
/* ---------- Viewport & Scissor Region ----------- */
void GPU_framebuffer_viewport_set(gpu::FrameBuffer *gpu_fb, int x, int y, int width, int height)
{
int viewport_rect[4] = {x, y, width, height};
gpu_fb->viewport_set(viewport_rect);
}
void GPU_framebuffer_multi_viewports_set(gpu::FrameBuffer *gpu_fb,
const int viewport_rects[GPU_MAX_VIEWPORTS][4])
{
gpu_fb->viewport_multi_set(viewport_rects);
}
void GPU_framebuffer_viewport_get(gpu::FrameBuffer *gpu_fb, int r_viewport[4])
{
gpu_fb->viewport_get(r_viewport);
}
void GPU_framebuffer_viewport_reset(gpu::FrameBuffer *gpu_fb)
{
gpu_fb->viewport_reset();
}
/* ---------- Frame-buffer Operations ----------- */
/* Check for any loss of data when casting. */
static void check_clear_color(const Texture *tex, double4 clear_value, int attachment)
{
GPUTextureFormatFlag flag = tex->format_flag_get();
if ((flag & GPU_FORMAT_INTEGER) && (flag & GPU_FORMAT_SIGNED)) {
int4 data = int4(clear_value);
if (double4(data) != clear_value) {
CLOG_WARN(&LOG,
"Lossy conversion when clearing integer attachment %d: "
"(%f, %f, %f, %f) > (%d, %d, %d, %d)",
attachment,
UNPACK4(clear_value),
UNPACK4(data));
}
}
else if ((flag & GPU_FORMAT_INTEGER) && !(flag & GPU_FORMAT_SIGNED)) {
uint4 data = uint4(clear_value);
if (double4(data) != clear_value) {
CLOG_WARN(&LOG,
"Lossy conversion when clearing unsigned integer attachment %d: "
"(%f, %f, %f, %f) > (%u, %u, %u, %u)",
attachment,
UNPACK4(clear_value),
UNPACK4(data));
}
}
}
void GPU_framebuffer_clear(gpu::FrameBuffer *gpu_fb,
GPUFrameBufferBits buffers,
const double4 clear_col,
float clear_depth,
uint clear_stencil)
{
BLI_assert_msg(gpu_fb->get_use_explicit_loadstore() == false,
"Using GPU_framebuffer_clear_* functions in conjunction with custom load-store "
"state via GPU_framebuffer_bind_ex is invalid.");
if ((G.debug & G_DEBUG_GPU) && (buffers & GPU_COLOR_BIT)) {
for (int i = 0; i < GPU_FB_MAX_COLOR_ATTACHMENT; i++) {
Texture *tex = gpu_fb->color_tex(i);
if (tex != nullptr) {
check_clear_color(tex, clear_col, i);
}
}
}
gpu_fb->clear(buffers, clear_col, clear_depth, clear_stencil);
}
void GPU_framebuffer_clear_color(gpu::FrameBuffer *fb, const double4 clear_col)
{
GPU_framebuffer_clear(fb, GPU_COLOR_BIT, clear_col, 0.0f, 0x00);
}
void GPU_framebuffer_clear_depth(gpu::FrameBuffer *fb, float clear_depth)
{
GPU_framebuffer_clear(fb, GPU_DEPTH_BIT, double4{}, clear_depth, 0x00);
}
void GPU_framebuffer_clear_color_depth(gpu::FrameBuffer *fb,
const double4 clear_col,
float clear_depth)
{
GPU_framebuffer_clear(fb, GPU_COLOR_BIT | GPU_DEPTH_BIT, clear_col, clear_depth, 0x00);
}
void GPU_framebuffer_clear_stencil(gpu::FrameBuffer *fb, uint clear_stencil)
{
GPU_framebuffer_clear(fb, GPU_STENCIL_BIT, double4{}, 0.0f, clear_stencil);
}
void GPU_framebuffer_clear_depth_stencil(gpu::FrameBuffer *fb,
float clear_depth,
uint clear_stencil)
{
GPU_framebuffer_clear(
fb, GPU_DEPTH_BIT | GPU_STENCIL_BIT, double4{}, clear_depth, clear_stencil);
}
void GPU_framebuffer_clear_color_depth_stencil(gpu::FrameBuffer *fb,
const double4 clear_col,
float clear_depth,
uint clear_stencil)
{
GPU_framebuffer_clear(
fb, GPU_COLOR_BIT | GPU_DEPTH_BIT | GPU_STENCIL_BIT, clear_col, clear_depth, clear_stencil);
}
void GPU_framebuffer_multi_clear(gpu::FrameBuffer *fb, Span<double4> clear_colors)
{
BLI_assert_msg(fb->get_use_explicit_loadstore() == false,
"Using GPU_framebuffer_clear_* functions in conjunction with custom load-store "
"state via GPU_framebuffer_bind_ex is invalid.");
if (G.debug & G_DEBUG_GPU) {
for (int i = 0; i < GPU_FB_MAX_COLOR_ATTACHMENT; i++) {
Texture *tex = fb->color_tex(i);
if (tex != nullptr) {
check_clear_color(tex, clear_colors[i], i);
}
}
}
fb->clear_multi(clear_colors);
}
void GPU_clear_color(float red, float green, float blue, float alpha)
{
BLI_assert_msg(Context::get()->active_fb->get_use_explicit_loadstore() == false,
"Using GPU_framebuffer_clear_* functions in conjunction with custom load-store "
"state via GPU_framebuffer_bind_ex is invalid.");
double4 clear_col = {red, green, blue, alpha};
Context::get()->active_fb->clear(GPU_COLOR_BIT, clear_col, 0.0f, 0x0);
}
void GPU_clear_depth(float depth)
{
BLI_assert_msg(Context::get()->active_fb->get_use_explicit_loadstore() == false,
"Using GPU_framebuffer_clear_* functions in conjunction with custom load-store "
"state via GPU_framebuffer_bind_ex is invalid.");
Context::get()->active_fb->clear(GPU_DEPTH_BIT, double4{}, depth, 0x0);
}
void GPU_framebuffer_read_depth(
gpu::FrameBuffer *fb, int x, int y, int w, int h, eGPUDataFormat format, void *data)
{
int rect[4] = {x, y, w, h};
fb->read(GPU_DEPTH_BIT, format, rect, 1, 1, data);
}
void GPU_framebuffer_read_color(gpu::FrameBuffer *fb,
int x,
int y,
int w,
int h,
int channels,
int slot,
eGPUDataFormat format,
void *data)
{
int rect[4] = {x, y, w, h};
fb->read(GPU_COLOR_BIT, format, rect, channels, slot, data);
}
void GPU_frontbuffer_read_color(
int x, int y, int w, int h, int channels, eGPUDataFormat format, void *data)
{
int rect[4] = {x, y, w, h};
Context::get()->front_left->read(GPU_COLOR_BIT, format, rect, channels, 0, data);
}
/* TODO(fclem): port as texture operation. */
void GPU_framebuffer_blit(gpu::FrameBuffer *fb_read,
int read_slot,
gpu::FrameBuffer *fb_write,
int write_slot,
GPUFrameBufferBits blit_buffers)
{
BLI_assert(blit_buffers != 0);
FrameBuffer *prev_fb = Context::get()->active_fb;
#ifndef NDEBUG
gpu::Texture *read_tex, *write_tex;
if (blit_buffers & (GPU_DEPTH_BIT | GPU_STENCIL_BIT)) {
read_tex = fb_read->depth_tex();
write_tex = fb_write->depth_tex();
}
else {
read_tex = fb_read->color_tex(read_slot);
write_tex = fb_write->color_tex(write_slot);
}
if (blit_buffers & GPU_DEPTH_BIT) {
BLI_assert(GPU_texture_has_depth_format(read_tex) && GPU_texture_has_depth_format(write_tex));
BLI_assert(GPU_texture_format(read_tex) == GPU_texture_format(write_tex) ||
(ELEM(GPU_texture_format(read_tex),
TextureFormat::SFLOAT_32_DEPTH,
TextureFormat::SFLOAT_32_DEPTH_UINT_8) &&
ELEM(GPU_texture_format(write_tex),
TextureFormat::SFLOAT_32_DEPTH,
TextureFormat::SFLOAT_32_DEPTH_UINT_8)));
}
if (blit_buffers & GPU_STENCIL_BIT) {
BLI_assert(GPU_texture_has_stencil_format(read_tex) &&
GPU_texture_has_stencil_format(write_tex));
BLI_assert(GPU_texture_format(read_tex) == GPU_texture_format(write_tex));
}
#endif
fb_read->blit_to(blit_buffers, read_slot, fb_write, write_slot, 0, 0);
/* FIXME(@fclem): sRGB is not saved. */
prev_fb->bind(true);
}
#ifndef GPU_NO_USE_PY_REFERENCES
void **GPU_framebuffer_py_reference_get(gpu::FrameBuffer *fb)
{
return fb->py_ref;
}
void GPU_framebuffer_py_reference_set(gpu::FrameBuffer *fb, void **py_ref)
{
BLI_assert(py_ref == nullptr || fb->py_ref == nullptr);
fb->py_ref = py_ref;
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Frame-Buffer Stack
*
* Keeps track of frame-buffer binding operation to restore previously bound frame-buffers.
* \{ */
#define FRAMEBUFFER_STACK_DEPTH 16
static struct {
gpu::FrameBuffer *framebuffers[FRAMEBUFFER_STACK_DEPTH];
uint top;
} FrameBufferStack = {{nullptr}};
void GPU_framebuffer_push(gpu::FrameBuffer *fb)
{
BLI_assert(FrameBufferStack.top < FRAMEBUFFER_STACK_DEPTH);
FrameBufferStack.framebuffers[FrameBufferStack.top] = fb;
FrameBufferStack.top++;
}
gpu::FrameBuffer *GPU_framebuffer_pop()
{
BLI_assert(FrameBufferStack.top > 0);
FrameBufferStack.top--;
return FrameBufferStack.framebuffers[FrameBufferStack.top];
}
uint GPU_framebuffer_stack_level_get()
{
return FrameBufferStack.top;
}
#undef FRAMEBUFFER_STACK_DEPTH
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPUOffScreen
*
* Container that holds a frame-buffer and its textures.
* Might be bound to multiple contexts.
* \{ */
struct GPUOffScreen {
constexpr static int MAX_CTX_FB_LEN = 3;
struct {
Context *ctx;
gpu::FrameBuffer *fb;
} framebuffers[MAX_CTX_FB_LEN];
gpu::Texture *color;
gpu::Texture *depth;
};
/**
* Returns the correct frame-buffer for the current context.
*/
static gpu::FrameBuffer *gpu_offscreen_fb_get(GPUOffScreen *ofs)
{
Context *ctx = Context::get();
BLI_assert(ctx);
for (auto &framebuffer : ofs->framebuffers) {
if (framebuffer.fb == nullptr) {
framebuffer.ctx = ctx;
GPU_framebuffer_ensure_config(&framebuffer.fb,
{
GPU_ATTACHMENT_TEXTURE(ofs->depth),
GPU_ATTACHMENT_TEXTURE(ofs->color),
});
}
if (framebuffer.ctx == ctx) {
return framebuffer.fb;
}
}
/* List is full, this should never happen or
* it might just slow things down if it happens
* regularly. In this case we just empty the list
* and start over. This is most likely never going
* to happen under normal usage. */
BLI_assert(0);
printf(
"Warning: GPUOffscreen used in more than 3 GPUContext. "
"This may create performance drop.\n");
for (auto &framebuffer : ofs->framebuffers) {
GPU_framebuffer_free(framebuffer.fb);
framebuffer.fb = nullptr;
}
return gpu_offscreen_fb_get(ofs);
}
GPUOffScreen *GPU_offscreen_create(int width,
int height,
bool with_depth_buffer,
gpu::TextureFormat format,
eGPUTextureUsage usage,
bool clear,
char err_out[256])
{
GPUOffScreen *ofs = MEM_new_zeroed<GPUOffScreen>(__func__);
/* Sometimes areas can have 0 height or width and this will
* create a 1D texture which we don't want. */
height = max_ii(1, height);
width = max_ii(1, width);
/* Always add GPU_TEXTURE_USAGE_ATTACHMENT for convenience. */
usage |= GPU_TEXTURE_USAGE_ATTACHMENT;
ofs->color = GPU_texture_create_2d("ofs_color", width, height, 1, format, usage, nullptr);
if (with_depth_buffer) {
/* Format view flag is needed by Workbench Volumes to read the stencil view. */
eGPUTextureUsage depth_usage = usage | GPU_TEXTURE_USAGE_FORMAT_VIEW;
ofs->depth = GPU_texture_create_2d("ofs_depth",
width,
height,
1,
gpu::TextureFormat::SFLOAT_32_DEPTH_UINT_8,
depth_usage,
nullptr);
}
if ((with_depth_buffer && !ofs->depth) || !ofs->color) {
const char error[] = "gpu::Texture: Texture allocation failed.";
if (err_out) {
BLI_strncpy(err_out, error, 256);
}
else {
fprintf(stderr, "%s", error);
}
GPU_offscreen_free(ofs);
return nullptr;
}
gpu::FrameBuffer *fb = gpu_offscreen_fb_get(ofs);
/* check validity at the very end! */
if (!GPU_framebuffer_check_valid(fb, err_out)) {
GPU_offscreen_free(ofs);
return nullptr;
}
if (clear) {
GPU_framebuffer_bind(fb);
if (with_depth_buffer) {
GPU_framebuffer_clear_color_depth(fb, {0.0, 0.0, 0.0, 0.0}, 0.0f);
}
else {
GPU_framebuffer_clear_color(fb, {0.0, 0.0, 0.0, 0.0});
}
}
GPU_framebuffer_restore();
return ofs;
}
void GPU_offscreen_free(GPUOffScreen *offscreen)
{
for (auto &framebuffer : offscreen->framebuffers) {
if (framebuffer.fb) {
GPU_framebuffer_free(framebuffer.fb);
}
}
if (offscreen->color) {
GPU_texture_free(offscreen->color);
}
if (offscreen->depth) {
GPU_texture_free(offscreen->depth);
}
MEM_delete(offscreen);
}
void GPU_offscreen_bind(GPUOffScreen *offscreen, bool save)
{
if (save) {
gpu::FrameBuffer *fb = GPU_framebuffer_active_get();
GPU_framebuffer_push(fb);
}
gpu_offscreen_fb_get(offscreen)->bind(false);
}
void GPU_offscreen_unbind(GPUOffScreen * /*offscreen*/, bool restore)
{
gpu::FrameBuffer *fb = nullptr;
if (restore) {
fb = GPU_framebuffer_pop();
}
if (fb) {
GPU_framebuffer_bind(fb);
}
else {
GPU_framebuffer_restore();
}
}
void GPU_offscreen_draw_to_screen(GPUOffScreen *offscreen, int x, int y)
{
Context *ctx = Context::get();
FrameBuffer *ofs_fb = gpu_offscreen_fb_get(offscreen);
ofs_fb->blit_to(GPU_COLOR_BIT, 0, ctx->active_fb, 0, x, y);
}
void GPU_offscreen_read_color_region(
GPUOffScreen *offscreen, eGPUDataFormat format, int x, int y, int w, int h, void *r_data)
{
BLI_assert(ELEM(format, GPU_DATA_UBYTE, GPU_DATA_FLOAT));
BLI_assert(x >= 0 && y >= 0 && w > 0 && h > 0);
BLI_assert(x + w <= GPU_texture_width(offscreen->color));
BLI_assert(y + h <= GPU_texture_height(offscreen->color));
gpu::FrameBuffer *ofs_fb = gpu_offscreen_fb_get(offscreen);
GPU_framebuffer_read_color(ofs_fb, x, y, w, h, 4, 0, format, r_data);
}
void GPU_offscreen_read_color(GPUOffScreen *offscreen, eGPUDataFormat format, void *r_data)
{
BLI_assert(ELEM(format, GPU_DATA_UBYTE, GPU_DATA_FLOAT));
const int w = GPU_texture_width(offscreen->color);
const int h = GPU_texture_height(offscreen->color);
GPU_offscreen_read_color_region(offscreen, format, 0, 0, w, h, r_data);
}
int GPU_offscreen_width(const GPUOffScreen *offscreen)
{
return GPU_texture_width(offscreen->color);
}
int GPU_offscreen_height(const GPUOffScreen *offscreen)
{
return GPU_texture_height(offscreen->color);
}
gpu::Texture *GPU_offscreen_color_texture(const GPUOffScreen *offscreen)
{
return offscreen->color;
}
gpu::TextureFormat GPU_offscreen_format(const GPUOffScreen *offscreen)
{
return GPU_texture_format(offscreen->color);
}
void GPU_offscreen_viewport_data_get(GPUOffScreen *offscreen,
gpu::FrameBuffer **r_fb,
gpu::Texture **r_color,
gpu::Texture **r_depth)
{
*r_fb = gpu_offscreen_fb_get(offscreen);
*r_color = offscreen->color;
*r_depth = offscreen->depth;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,281 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Private frame buffer API.
*/
#pragma once
#include "BLI_math_vector.h"
#include "BLI_span.hh"
#include "GPU_framebuffer.hh"
namespace blender {
namespace gpu {
class Texture;
}
enum GPUAttachmentType : int {
GPU_FB_DEPTH_ATTACHMENT = 0,
GPU_FB_DEPTH_STENCIL_ATTACHMENT,
GPU_FB_COLOR_ATTACHMENT0,
GPU_FB_COLOR_ATTACHMENT1,
GPU_FB_COLOR_ATTACHMENT2,
GPU_FB_COLOR_ATTACHMENT3,
GPU_FB_COLOR_ATTACHMENT4,
GPU_FB_COLOR_ATTACHMENT5,
GPU_FB_COLOR_ATTACHMENT6,
GPU_FB_COLOR_ATTACHMENT7,
/* Number of maximum output slots. */
/* Keep in mind that GL max is GL_MAX_DRAW_BUFFERS and is at least 8, corresponding to
* the maximum number of COLOR attachments specified by glDrawBuffers. */
GPU_FB_MAX_ATTACHMENT,
};
#define GPU_FB_MAX_COLOR_ATTACHMENT (GPU_FB_MAX_ATTACHMENT - GPU_FB_COLOR_ATTACHMENT0)
constexpr GPUAttachmentType operator-(GPUAttachmentType a, int b)
{
return static_cast<GPUAttachmentType>(int(a) - b);
}
constexpr GPUAttachmentType operator+(GPUAttachmentType a, int b)
{
return static_cast<GPUAttachmentType>(int(a) + b);
}
inline GPUAttachmentType &operator++(GPUAttachmentType &a)
{
a = a + 1;
return a;
}
inline GPUAttachmentType &operator--(GPUAttachmentType &a)
{
a = a - 1;
return a;
}
namespace gpu {
#ifndef NDEBUG
# define DEBUG_NAME_LEN 64
#else
# define DEBUG_NAME_LEN 16
#endif
class FrameBuffer {
protected:
/** Set of texture attachments to render to. DEPTH and DEPTH_STENCIL are mutually exclusive. */
GPUAttachment attachments_[GPU_FB_MAX_ATTACHMENT];
/** Is true if internal representation need to be updated. */
bool dirty_attachments_ = true;
/** Size of attachment textures. */
int width_ = 0, height_ = 0;
/** Debug name. */
char name_[DEBUG_NAME_LEN];
/** Frame-buffer state. */
int viewport_[GPU_MAX_VIEWPORTS][4] = {{0}};
int scissor_[4] = {0};
bool multi_viewport_ = false;
bool scissor_test_ = false;
bool dirty_state_ = true;
/* Flag specifying the current bind operation should use explicit load-store state. */
bool use_explicit_load_store_ = false;
/** Bit-set indicating the color attachments slots in use. */
uint16_t color_attachments_bits_ = 0;
public:
#ifndef GPU_NO_USE_PY_REFERENCES
/**
* Reference of a pointer that needs to be cleaned when deallocating the frame-buffer.
* Points to #BPyGPUFrameBuffer.fb
*/
void **py_ref = nullptr;
#endif
FrameBuffer(const char *name);
virtual ~FrameBuffer();
virtual void bind(bool enabled_srgb) = 0;
virtual bool check(char err_out[256]) = 0;
virtual void clear(GPUFrameBufferBits buffers,
const double4 clear_color,
float clear_depth,
uint clear_stencil) = 0;
virtual void clear_multi(Span<double4> clear_cols) = 0;
virtual void clear_attachment(GPUAttachmentType type, const double4 clear_value) = 0;
virtual void attachment_set_loadstore_op(GPUAttachmentType type, GPULoadStore ls) = 0;
virtual void read(GPUFrameBufferBits planes,
eGPUDataFormat format,
const int area[4],
int channel_len,
int slot,
void *r_data) = 0;
virtual void blit_to(GPUFrameBufferBits planes,
int src_slot,
FrameBuffer *dst,
int dst_slot,
int dst_offset_x,
int dst_offset_y) = 0;
protected:
virtual void subpass_transition_impl(const GPUAttachmentState depth_attachment_state,
Span<GPUAttachmentState> color_attachment_states) = 0;
void set_color_attachment_bit(GPUAttachmentType type, bool value)
{
if (type >= GPU_FB_COLOR_ATTACHMENT0) {
int color_index = type - GPU_FB_COLOR_ATTACHMENT0;
SET_FLAG_FROM_TEST(color_attachments_bits_, value, 1u << color_index);
}
}
public:
void subpass_transition(const GPUAttachmentState depth_attachment_state,
Span<GPUAttachmentState> color_attachment_states);
void load_store_config_array(const GPULoadStore *load_store_actions, uint actions_len);
void attachment_set(GPUAttachmentType type, const GPUAttachment &new_attachment);
void attachment_remove(GPUAttachmentType type);
uint get_bits_per_pixel();
/* Sets the size after creation. */
void size_set(int width, int height)
{
width_ = width;
height_ = height;
dirty_state_ = true;
}
/** \brief Get the size of the framebuffer. */
int2 size_get() const
{
return int2(width_, height_);
}
/* Sets the size for frame-buffer with no attachments. */
void default_size_set(int width, int height)
{
width_ = width;
height_ = height;
dirty_attachments_ = true;
dirty_state_ = true;
}
void viewport_set(const int viewport[4])
{
if (!equals_v4v4_int(viewport_[0], viewport)) {
copy_v4_v4_int(viewport_[0], viewport);
dirty_state_ = true;
}
multi_viewport_ = false;
}
void viewport_multi_set(const int viewports[GPU_MAX_VIEWPORTS][4])
{
for (size_t i = 0; i < GPU_MAX_VIEWPORTS; i++) {
if (!equals_v4v4_int(viewport_[i], viewports[i])) {
copy_v4_v4_int(viewport_[i], viewports[i]);
dirty_state_ = true;
}
}
multi_viewport_ = true;
}
void scissor_set(const int scissor[4])
{
if (!equals_v4v4_int(scissor_, scissor)) {
copy_v4_v4_int(scissor_, scissor);
dirty_state_ = true;
}
}
void scissor_test_set(bool test)
{
scissor_test_ = test;
dirty_state_ = true;
}
void viewport_get(int r_viewport[4]) const
{
copy_v4_v4_int(r_viewport, viewport_[0]);
}
void scissor_get(int r_scissor[4]) const
{
copy_v4_v4_int(r_scissor, scissor_);
}
bool scissor_test_get() const
{
return scissor_test_;
}
void viewport_reset()
{
int viewport_rect[4] = {0, 0, width_, height_};
viewport_set(viewport_rect);
}
void scissor_reset()
{
int scissor_rect[4] = {0, 0, width_, height_};
scissor_set(scissor_rect);
}
const GPUAttachment &depth_attachment() const
{
if (attachments_[GPU_FB_DEPTH_ATTACHMENT].tex) {
return attachments_[GPU_FB_DEPTH_ATTACHMENT];
}
return attachments_[GPU_FB_DEPTH_STENCIL_ATTACHMENT];
}
gpu::Texture *depth_tex() const
{
return depth_attachment().tex;
};
gpu::Texture *color_tex(int slot) const
{
return attachments_[GPU_FB_COLOR_ATTACHMENT0 + slot].tex;
};
const char *name_get() const
{
return name_;
};
void set_use_explicit_loadstore(bool use_explicit_loadstore)
{
use_explicit_load_store_ = use_explicit_loadstore;
}
bool get_use_explicit_loadstore() const
{
return use_explicit_load_store_;
}
uint16_t get_color_attachments_bitset()
{
return color_attachments_bits_;
}
};
#undef DEBUG_NAME_LEN
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,772 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Mimics old style opengl immediate mode drawing.
*/
#ifndef GPU_STANDALONE
# include "UI_resources.hh"
#endif
#include "GPU_immediate.hh"
#include "GPU_matrix.hh"
#include "GPU_texture.hh"
#include "GPU_uniform_buffer.hh"
#include "gpu_context_private.hh"
#include "gpu_immediate_private.hh"
#include "gpu_shader_private.hh"
#include "gpu_vertex_format_private.hh"
namespace blender {
using namespace blender::gpu;
static thread_local Immediate *imm = nullptr;
void immActivate()
{
imm = Context::get()->imm;
}
void immDeactivate()
{
imm = nullptr;
}
GPUVertFormat *immVertexFormat()
{
GPU_vertformat_clear(&imm->vertex_format);
return &imm->vertex_format;
}
void immBindShader(gpu::Shader *shader)
{
BLI_assert(imm->shader == nullptr);
imm->shader = shader;
imm->builtin_shader_bound = std::nullopt;
if (!imm->vertex_format.packed) {
VertexFormat_pack(&imm->vertex_format);
imm->enabled_attr_bits = 0xFFFFu & ~(0xFFFFu << imm->vertex_format.attr_len);
}
GPU_shader_bind(shader);
GPU_matrix_bind(shader);
}
void immBindBuiltinProgram(GPUBuiltinShader shader_id)
{
gpu::Shader *shader = GPU_shader_get_builtin_shader(shader_id);
immBindShader(shader);
imm->builtin_shader_bound = shader_id;
}
void immUnbindProgram()
{
BLI_assert(imm->shader != nullptr);
GPU_shader_unbind();
imm->shader = nullptr;
}
bool immIsShaderBound()
{
return imm->shader != nullptr;
}
gpu::Shader *immGetShader()
{
return imm->shader;
}
#ifndef NDEBUG
static bool vertex_count_makes_sense_for_primitive(uint vertex_len, GPUPrimType prim_type)
{
/* does vertex_len make sense for this primitive type? */
if (vertex_len == 0) {
return false;
}
switch (prim_type) {
case GPU_PRIM_POINTS:
return true;
case GPU_PRIM_LINES:
return vertex_len % 2 == 0;
case GPU_PRIM_LINE_STRIP:
case GPU_PRIM_LINE_LOOP:
return vertex_len >= 2;
case GPU_PRIM_LINE_STRIP_ADJ:
return vertex_len >= 4;
case GPU_PRIM_TRIS:
return vertex_len % 3 == 0;
case GPU_PRIM_TRI_STRIP:
case GPU_PRIM_TRI_FAN:
return vertex_len >= 3;
default:
return false;
}
}
#endif
/* -------------------------------------------------------------------- */
/** \name Wide line workaround
*
* Some systems do not support wide lines.
* We workaround this by using specialized shaders.
* \{ */
static void wide_line_workaround_start(GPUPrimType prim_type)
{
if (!ELEM(prim_type, GPU_PRIM_LINES, GPU_PRIM_LINE_STRIP, GPU_PRIM_LINE_LOOP)) {
return;
}
float line_width = GPU_line_width_get();
if (line_width == 1.0f && !GPU_line_smooth_get()) {
/* No need to change the shader. */
return;
}
if (!imm->builtin_shader_bound) {
return;
}
GPUBuiltinShader polyline_sh;
switch (*imm->builtin_shader_bound) {
case GPU_SHADER_3D_CLIPPED_UNIFORM_COLOR:
polyline_sh = GPU_SHADER_3D_POLYLINE_CLIPPED_UNIFORM_COLOR;
break;
case GPU_SHADER_3D_UNIFORM_COLOR:
polyline_sh = GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR;
break;
case GPU_SHADER_3D_FLAT_COLOR:
polyline_sh = GPU_SHADER_3D_POLYLINE_FLAT_COLOR;
break;
case GPU_SHADER_3D_SMOOTH_COLOR:
polyline_sh = GPU_SHADER_3D_POLYLINE_SMOOTH_COLOR;
break;
default:
/* Cannot replace the current shader with a polyline shader. */
return;
}
imm->prev_builtin_shader = imm->builtin_shader_bound;
immUnbindProgram();
/* TODO(fclem): Don't use geometry shader and use quad instancing with double load. */
// GPU_vertformat_multiload_enable(imm->vertex_format, 2);
immBindBuiltinProgram(polyline_sh);
float viewport[4];
GPU_viewport_size_get_f(viewport);
immUniform2fv("viewportSize", &viewport[2]);
immUniform1f("lineWidth", line_width);
if (GPU_blend_get() == GPU_BLEND_NONE) {
/* Disable line smoothing when blending is disabled (see #81827). */
immUniform1i("lineSmooth", 0);
}
if (ELEM(polyline_sh,
GPU_SHADER_3D_POLYLINE_CLIPPED_UNIFORM_COLOR,
GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR))
{
immUniformColor4fv(imm->uniform_color);
}
}
static void wide_line_workaround_end()
{
if (imm->prev_builtin_shader) {
if (GPU_blend_get() == GPU_BLEND_NONE) {
/* Restore default. */
immUniform1i("lineSmooth", 1);
}
immUnbindProgram();
immBindBuiltinProgram(*imm->prev_builtin_shader);
imm->prev_builtin_shader = std::nullopt;
}
}
/** \} */
void immBegin(GPUPrimType prim_type, uint vertex_len)
{
BLI_assert(imm->prim_type == GPU_PRIM_NONE); /* Make sure we haven't already begun. */
BLI_assert(vertex_count_makes_sense_for_primitive(vertex_len, prim_type));
wide_line_workaround_start(prim_type);
imm->prim_type = prim_type;
imm->vertex_len = vertex_len;
imm->vertex_idx = 0;
imm->unassigned_attr_bits = imm->enabled_attr_bits;
imm->vertex_data = imm->begin();
}
void immBeginAtMost(GPUPrimType prim_type, uint vertex_len)
{
BLI_assert(vertex_len > 0);
imm->strict_vertex_len = false;
immBegin(prim_type, vertex_len);
}
gpu::Batch *immBeginBatch(GPUPrimType prim_type, uint vertex_len)
{
BLI_assert(imm->prim_type == GPU_PRIM_NONE); /* Make sure we haven't already begun. */
BLI_assert(vertex_count_makes_sense_for_primitive(vertex_len, prim_type));
imm->prim_type = prim_type;
imm->vertex_len = vertex_len;
imm->vertex_idx = 0;
imm->unassigned_attr_bits = imm->enabled_attr_bits;
VertBuf *verts = GPU_vertbuf_create_with_format(imm->vertex_format);
GPU_vertbuf_data_alloc(*verts, vertex_len);
imm->vertex_data = verts->data<uchar>().data();
imm->batch = GPU_batch_create_ex(prim_type, verts, nullptr, GPU_BATCH_OWNS_VBO);
imm->batch->flag |= GPU_BATCH_BUILDING;
return imm->batch;
}
gpu::Batch *immBeginBatchAtMost(GPUPrimType prim_type, uint vertex_len)
{
BLI_assert(vertex_len > 0);
imm->strict_vertex_len = false;
return immBeginBatch(prim_type, vertex_len);
}
void immEnd()
{
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* Make sure we're between a Begin/End pair. */
BLI_assert(imm->vertex_data || imm->batch);
if (imm->strict_vertex_len) {
BLI_assert(imm->vertex_idx == imm->vertex_len); /* With all vertices defined. */
}
else {
BLI_assert(imm->vertex_idx <= imm->vertex_len);
BLI_assert(imm->vertex_idx == 0 ||
vertex_count_makes_sense_for_primitive(imm->vertex_idx, imm->prim_type));
}
if (imm->batch) {
if (imm->vertex_idx < imm->vertex_len) {
GPU_vertbuf_data_resize(*imm->batch->verts[0], imm->vertex_idx);
/* TODO: resize only if vertex count is much smaller */
}
GPU_batch_set_shader(imm->batch, imm->shader);
imm->batch->flag &= ~GPU_BATCH_BUILDING;
imm->batch = nullptr; /* don't free, batch belongs to caller */
}
else {
Context::get()->assert_framebuffer_shader_compatibility(imm->shader);
imm->end();
}
/* Prepare for next immBegin. */
imm->prim_type = GPU_PRIM_NONE;
imm->strict_vertex_len = true;
imm->vertex_data = nullptr;
wide_line_workaround_end();
}
void Immediate::polyline_draw_workaround(uint64_t offset)
{
/* Check compatible input primitive. */
BLI_assert(ELEM(imm->prim_type, GPU_PRIM_LINES, GPU_PRIM_LINE_STRIP, GPU_PRIM_LINE_LOOP));
Batch *tri_batch = Context::get()->procedural_triangles_batch_get();
GPU_batch_set_shader(tri_batch, imm->shader);
BLI_assert(offset % 4 == 0);
/* Setup primitive and index buffer. */
int stride = (imm->prim_type == GPU_PRIM_LINES) ? 2 : 1;
int data[3] = {stride, int(imm->vertex_idx), int(offset / 4)};
GPU_shader_uniform_3iv(imm->shader, "gpu_vert_stride_count_offset", data);
GPU_shader_uniform_1b(imm->shader, "gpu_index_no_buffer", true);
{
/* Setup attributes metadata uniforms. */
const GPUVertFormat &format = imm->vertex_format;
/* Only support 4byte aligned formats. */
BLI_assert((format.stride % 4) == 0);
BLI_assert(format.attr_len > 0);
int pos_attr_id = -1;
int col_attr_id = -1;
for (uint a_idx = 0; a_idx < format.attr_len; a_idx++) {
const GPUVertAttr *a = &format.attrs[a_idx];
const char *name = GPU_vertformat_attr_name_get(&format, a, 0);
if (pos_attr_id == -1 && StringRefNull(name) == "pos") {
int descriptor[2] = {int(format.stride) / 4, int(a->offset) / 4};
const bool fetch_int = false;
BLI_assert(is_fetch_float(a->type.format) || fetch_int);
BLI_assert_msg((a->offset % 4) == 0, "Only support 4byte aligned attributes");
GPU_shader_uniform_2iv(imm->shader, "gpu_attr_0", descriptor);
GPU_shader_uniform_1i(imm->shader, "gpu_attr_0_len", a->type.comp_len());
GPU_shader_uniform_1b(imm->shader, "gpu_attr_0_fetch_int", fetch_int);
pos_attr_id = a_idx;
}
else if (col_attr_id == -1 && StringRefNull(name) == "color") {
int descriptor[2] = {int(format.stride) / 4, int(a->offset) / 4};
/* Maybe we can relax this if needed. */
BLI_assert_msg(ELEM(a->type.format,
VertAttrType::SFLOAT_32,
VertAttrType::SFLOAT_32_32,
VertAttrType::SFLOAT_32_32_32,
VertAttrType::SFLOAT_32_32_32_32,
VertAttrType::UNORM_8_8_8_8),
"Only support float attributes or uchar4");
const bool fetch_unorm8 = a->type.format == VertAttrType::UNORM_8_8_8_8;
BLI_assert_msg((a->offset % 4) == 0, "Only support 4byte aligned attributes");
GPU_shader_uniform_2iv(imm->shader, "gpu_attr_1", descriptor);
GPU_shader_uniform_1i(imm->shader, "gpu_attr_1_len", a->type.comp_len());
GPU_shader_uniform_1i(imm->shader, "gpu_attr_1_fetch_unorm8", fetch_unorm8);
col_attr_id = a_idx;
}
if (pos_attr_id != -1 && col_attr_id != -1) {
break;
}
}
BLI_assert(pos_attr_id != -1);
/* Could check for color attribute but we need to know which variant of the polyline shader is
* the one we are rendering with. */
// BLI_assert(pos_attr_id != -1);
}
IndexRange range = GPU_batch_draw_expanded_parameter_get(
imm->prim_type, GPU_PRIM_TRIS, imm->vertex_idx, 0, 2);
GPU_batch_draw_advanced(tri_batch, range.start(), range.size(), 0, 0);
}
static void setAttrValueBit(uint attr_id)
{
uint16_t mask = 1 << attr_id;
BLI_assert(imm->unassigned_attr_bits & mask); /* not already set */
imm->unassigned_attr_bits &= ~mask;
}
/* --- generic attribute functions --- */
void immAttr1f(uint attr_id, float x)
{
GPUVertAttr *attr = &imm->vertex_format.attrs[attr_id];
BLI_assert(attr_id < imm->vertex_format.attr_len);
BLI_assert(ELEM(attr->type.format, VertAttrType::SFLOAT_32));
BLI_assert(imm->vertex_idx < imm->vertex_len);
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
setAttrValueBit(attr_id);
float *data = reinterpret_cast<float *>(imm->vertex_data + attr->offset);
// printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm->buffer_data, data);
data[0] = x;
}
void immAttr2f(uint attr_id, float x, float y)
{
GPUVertAttr *attr = &imm->vertex_format.attrs[attr_id];
BLI_assert(attr_id < imm->vertex_format.attr_len);
BLI_assert(ELEM(attr->type.format, VertAttrType::SFLOAT_32_32));
BLI_assert(imm->vertex_idx < imm->vertex_len);
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
setAttrValueBit(attr_id);
float *data = reinterpret_cast<float *>(imm->vertex_data + attr->offset);
// printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm->buffer_data, data);
data[0] = x;
data[1] = y;
}
void immAttr3f(uint attr_id, float x, float y, float z)
{
GPUVertAttr *attr = &imm->vertex_format.attrs[attr_id];
BLI_assert(attr_id < imm->vertex_format.attr_len);
BLI_assert(ELEM(attr->type.format, VertAttrType::SFLOAT_32_32_32));
BLI_assert(imm->vertex_idx < imm->vertex_len);
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
setAttrValueBit(attr_id);
float *data = reinterpret_cast<float *>(imm->vertex_data + attr->offset);
// printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm->buffer_data, data);
data[0] = x;
data[1] = y;
data[2] = z;
}
void immAttr4f(uint attr_id, float x, float y, float z, float w)
{
GPUVertAttr *attr = &imm->vertex_format.attrs[attr_id];
BLI_assert(attr_id < imm->vertex_format.attr_len);
BLI_assert(ELEM(attr->type.format, VertAttrType::SFLOAT_32_32_32_32));
BLI_assert(imm->vertex_idx < imm->vertex_len);
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
setAttrValueBit(attr_id);
float *data = reinterpret_cast<float *>(imm->vertex_data + attr->offset);
// printf("%s %td %p\n", __FUNCTION__, (GLubyte*)data - imm->buffer_data, data);
data[0] = x;
data[1] = y;
data[2] = z;
data[3] = w;
}
void immAttr1u(uint attr_id, uint x)
{
GPUVertAttr *attr = &imm->vertex_format.attrs[attr_id];
BLI_assert(attr_id < imm->vertex_format.attr_len);
BLI_assert(ELEM(attr->type.format, VertAttrType::UINT_32));
BLI_assert(imm->vertex_idx < imm->vertex_len);
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
setAttrValueBit(attr_id);
uint *data = reinterpret_cast<uint *>(imm->vertex_data + attr->offset);
data[0] = x;
}
void immAttr2i(uint attr_id, int x, int y)
{
GPUVertAttr *attr = &imm->vertex_format.attrs[attr_id];
BLI_assert(attr_id < imm->vertex_format.attr_len);
BLI_assert(ELEM(attr->type.format, VertAttrType::SINT_32_32));
BLI_assert(imm->vertex_idx < imm->vertex_len);
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
setAttrValueBit(attr_id);
int *data = reinterpret_cast<int *>(imm->vertex_data + attr->offset);
data[0] = x;
data[1] = y;
}
void immAttr2fv(uint attr_id, const float data[2])
{
immAttr2f(attr_id, data[0], data[1]);
}
void immAttr3fv(uint attr_id, const float data[3])
{
immAttr3f(attr_id, data[0], data[1], data[2]);
}
void immAttr4fv(uint attr_id, const float data[4])
{
immAttr4f(attr_id, data[0], data[1], data[2], data[3]);
}
void immAttr4ub(uint attr_id, uchar r, uchar g, uchar b, uchar a)
{
GPUVertAttr *attr = &imm->vertex_format.attrs[attr_id];
BLI_assert(attr_id < imm->vertex_format.attr_len);
BLI_assert(ELEM(attr->type.format, VertAttrType::UINT_8_8_8_8, VertAttrType::UNORM_8_8_8_8));
BLI_assert(imm->vertex_idx < imm->vertex_len);
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
setAttrValueBit(attr_id);
uchar *data = imm->vertex_data + attr->offset;
// printf("%s %td %p\n", __FUNCTION__, data - imm->buffer_data, data);
data[0] = r;
data[1] = g;
data[2] = b;
data[3] = a;
}
void immAttr4ubv(uint attr_id, const uchar data[4])
{
immAttr4ub(attr_id, data[0], data[1], data[2], data[3]);
}
void immAttrSkip(uint attr_id)
{
BLI_assert(attr_id < imm->vertex_format.attr_len);
BLI_assert(imm->vertex_idx < imm->vertex_len);
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
setAttrValueBit(attr_id);
}
static void immEndVertex() /* and move on to the next vertex */
{
BLI_assert(imm->prim_type != GPU_PRIM_NONE); /* make sure we're between a Begin/End pair */
BLI_assert(imm->vertex_idx < imm->vertex_len);
/* Have all attributes been assigned values?
* If not, copy value from previous vertex. */
if (imm->unassigned_attr_bits) {
BLI_assert(imm->vertex_idx > 0); /* first vertex must have all attributes specified */
for (uint a_idx = 0; a_idx < imm->vertex_format.attr_len; a_idx++) {
if ((imm->unassigned_attr_bits >> a_idx) & 1) {
const GPUVertAttr *a = &imm->vertex_format.attrs[a_idx];
#if 0
printf("copying %s from vertex %u to %u\n", a->name, imm->vertex_idx - 1, imm->vertex_idx);
#endif
uchar *data = imm->vertex_data + a->offset;
memcpy(data, data - imm->vertex_format.stride, a->type.size());
/* TODO: consolidate copy of adjacent attributes */
}
}
}
imm->vertex_idx++;
imm->vertex_data += imm->vertex_format.stride;
imm->unassigned_attr_bits = imm->enabled_attr_bits;
}
void immVertex2f(uint attr_id, float x, float y)
{
immAttr2f(attr_id, x, y);
immEndVertex();
}
void immVertex3f(uint attr_id, float x, float y, float z)
{
immAttr3f(attr_id, x, y, z);
immEndVertex();
}
void immVertex4f(uint attr_id, float x, float y, float z, float w)
{
immAttr4f(attr_id, x, y, z, w);
immEndVertex();
}
void immVertex2i(uint attr_id, int x, int y)
{
immAttr2i(attr_id, x, y);
immEndVertex();
}
void immVertex2fv(uint attr_id, const float data[2])
{
immAttr2f(attr_id, data[0], data[1]);
immEndVertex();
}
void immVertex3fv(uint attr_id, const float data[3])
{
immAttr3f(attr_id, data[0], data[1], data[2]);
immEndVertex();
}
void immVertex2iv(uint attr_id, const int data[2])
{
immAttr2i(attr_id, data[0], data[1]);
immEndVertex();
}
/* --- generic uniform functions --- */
void immUniform1f(const char *name, float x)
{
GPU_shader_uniform_1f(imm->shader, name, x);
}
void immUniform2f(const char *name, float x, float y)
{
GPU_shader_uniform_2f(imm->shader, name, x, y);
}
void immUniform2fv(const char *name, const float data[2])
{
GPU_shader_uniform_2fv(imm->shader, name, data);
}
void immUniform3f(const char *name, float x, float y, float z)
{
GPU_shader_uniform_3f(imm->shader, name, x, y, z);
}
void immUniform3fv(const char *name, const float data[3])
{
GPU_shader_uniform_3fv(imm->shader, name, data);
}
void immUniform4f(const char *name, float x, float y, float z, float w)
{
GPU_shader_uniform_4f(imm->shader, name, x, y, z, w);
}
void immUniform4fv(const char *name, const float data[4])
{
GPU_shader_uniform_4fv(imm->shader, name, data);
}
void immUniformArray4fv(const char *name, const float *data, int count)
{
GPU_shader_uniform_4fv_array(
imm->shader, name, count, reinterpret_cast<const float (*)[4]>(data));
}
void immUniformMatrix4fv(const char *name, const float data[4][4])
{
GPU_shader_uniform_mat4(imm->shader, name, data);
}
void immUniform1i(const char *name, int x)
{
GPU_shader_uniform_1i(imm->shader, name, x);
}
void immBindTexture(const char *name, gpu::Texture *tex)
{
int binding = GPU_shader_get_sampler_binding(imm->shader, name);
GPU_texture_bind(tex, binding);
}
void immBindTextureSampler(const char *name, gpu::Texture *tex, GPUSamplerState state)
{
int binding = GPU_shader_get_sampler_binding(imm->shader, name);
GPU_texture_bind_ex(tex, state, binding);
}
void immBindUniformBuf(const char *name, gpu::UniformBuf *ubo)
{
int binding = GPU_shader_get_ubo_binding(imm->shader, name);
GPU_uniformbuf_bind(ubo, binding);
}
/* --- convenience functions for setting "uniform vec4 color" --- */
void immUniformColor4f(float r, float g, float b, float a)
{
int32_t uniform_loc = GPU_shader_get_builtin_uniform(imm->shader, GPU_UNIFORM_COLOR);
BLI_assert(uniform_loc != -1);
float data[4] = {r, g, b, a};
GPU_shader_uniform_float_ex(imm->shader, uniform_loc, 4, 1, data);
/* For wide Line workaround. */
copy_v4_v4(imm->uniform_color, data);
}
void immUniformColor4fv(const float rgba[4])
{
immUniformColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
}
void immUniformColor3f(float r, float g, float b)
{
immUniformColor4f(r, g, b, 1.0f);
}
void immUniformColor3fv(const float rgb[3])
{
immUniformColor4f(rgb[0], rgb[1], rgb[2], 1.0f);
}
void immUniformColor3fvAlpha(const float rgb[3], float a)
{
immUniformColor4f(rgb[0], rgb[1], rgb[2], a);
}
void immUniformColor3ub(uchar r, uchar g, uchar b)
{
const float scale = 1.0f / 255.0f;
immUniformColor4f(scale * r, scale * g, scale * b, 1.0f);
}
void immUniformColor4ub(uchar r, uchar g, uchar b, uchar a)
{
const float scale = 1.0f / 255.0f;
immUniformColor4f(scale * r, scale * g, scale * b, scale * a);
}
void immUniformColor3ubv(const uchar rgb[3])
{
immUniformColor3ub(rgb[0], rgb[1], rgb[2]);
}
void immUniformColor3ubvAlpha(const uchar rgb[3], uchar alpha)
{
immUniformColor4ub(rgb[0], rgb[1], rgb[2], alpha);
}
void immUniformColor4ubv(const uchar rgba[4])
{
immUniformColor4ub(rgba[0], rgba[1], rgba[2], rgba[3]);
}
#ifndef GPU_STANDALONE
void immUniformThemeColor(int color_id)
{
float color[4];
ui::theme::get_color_4fv(color_id, color);
immUniformColor4fv(color);
}
void immUniformThemeColorAlpha(int color_id, float a)
{
float color[4];
ui::theme::get_color_3fv(color_id, color);
color[3] = a;
immUniformColor4fv(color);
}
void immUniformThemeColor3(int color_id)
{
float color[3];
ui::theme::get_color_3fv(color_id, color);
immUniformColor3fv(color);
}
void immUniformThemeColorShade(int color_id, int offset)
{
float color[4];
ui::theme::get_color_shade_4fv(color_id, offset, color);
immUniformColor4fv(color);
}
void immUniformThemeColorShadeAlpha(int color_id, int color_offset, int alpha_offset)
{
float color[4];
ui::theme::get_color_shade_alpha_4fv(color_id, color_offset, alpha_offset, color);
immUniformColor4fv(color);
}
void immUniformThemeColorBlendShade(int color_id1, int color_id2, float fac, int offset)
{
float color[4];
ui::theme::get_color_blend_shade_4fv(color_id1, color_id2, fac, offset, color);
immUniformColor4fv(color);
}
void immUniformThemeColorBlend(int color_id1, int color_id2, float fac)
{
uint8_t color[3];
ui::theme::get_color_blend_3ubv(color_id1, color_id2, fac, color);
immUniformColor3ubv(color);
}
void immThemeColorShadeAlpha(int colorid, int coloffset, int alphaoffset)
{
uchar col[4];
ui::theme::get_color_shade_alpha_4ubv(colorid, coloffset, alphaoffset, col);
immUniformColor4ub(col[0], col[1], col[2], col[3]);
}
#endif /* !GPU_STANDALONE */
} // namespace blender

View File

@@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Mimics old style opengl immediate mode drawing.
*/
#pragma once
#include <optional>
#include "GPU_batch.hh"
#include "GPU_primitive.hh"
#include "GPU_shader.hh"
#include "GPU_vertex_format.hh"
namespace blender {
namespace gpu {
class Immediate {
public:
/** Pointer to the mapped buffer data for the current vertex. */
uchar *vertex_data = nullptr;
/** Current vertex index. */
uint vertex_idx = 0;
/** Length of the buffer in vertices. */
uint vertex_len = 0;
/** Which attributes of current vertex have not been given values? */
uint16_t unassigned_attr_bits = 0;
/** Attributes that needs to be set. One bit per attribute. */
uint16_t enabled_attr_bits = 0;
/** Current draw call specification. */
GPUPrimType prim_type = GPU_PRIM_NONE;
GPUVertFormat vertex_format = {};
gpu::Shader *shader = nullptr;
/** Enforce strict vertex count (disabled when using #immBeginAtMost). */
bool strict_vertex_len = true;
/** Batch in construction when using #immBeginBatch. */
Batch *batch = nullptr;
/** Wide Line workaround. */
/** Previously bound shader to restore after drawing. */
std::optional<GPUBuiltinShader> prev_builtin_shader;
/** Builtin shader index. Used to test if the line width workaround can be done. */
std::optional<GPUBuiltinShader> builtin_shader_bound;
/** Uniform color: Kept here to update the wide-line shader just before #immBegin. */
float uniform_color[4];
Immediate() = default;
virtual ~Immediate() = default;
virtual uchar *begin() = 0;
virtual void end() = 0;
/* To be called after polyline SSBO binding. */
void polyline_draw_workaround(uint64_t offset);
};
} // namespace gpu
void immActivate();
void immDeactivate();
} // namespace blender

View File

@@ -0,0 +1,752 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU immediate mode drawing utilities
*/
#include <cstring>
#include "DNA_userdef_types.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_rect.h"
#include "BLI_utildefines.h"
#include "GPU_immediate.hh"
#include "UI_resources.hh"
namespace blender {
static const float cube_coords[8][3] = {
{-1, -1, -1},
{-1, -1, +1},
{-1, +1, -1},
{-1, +1, +1},
{+1, -1, -1},
{+1, -1, +1},
{+1, +1, -1},
{+1, +1, +1},
};
static const int cube_quad_index[6][4] = {
{0, 1, 3, 2},
{0, 2, 6, 4},
{0, 4, 5, 1},
{1, 5, 7, 3},
{2, 3, 7, 6},
{4, 6, 7, 5},
};
static const int cube_line_index[12][2] = {
{0, 1},
{0, 2},
{0, 4},
{1, 3},
{1, 5},
{2, 3},
{2, 6},
{3, 7},
{4, 5},
{4, 6},
{5, 7},
{6, 7},
};
void immRectf(uint pos, float x1, float y1, float x2, float y2)
{
immBegin(GPU_PRIM_TRI_FAN, 4);
immVertex2f(pos, x1, y1);
immVertex2f(pos, x2, y1);
immVertex2f(pos, x2, y2);
immVertex2f(pos, x1, y2);
immEnd();
}
void immRecti(uint pos, int x1, int y1, int x2, int y2)
{
immBegin(GPU_PRIM_TRI_FAN, 4);
immVertex2i(pos, x1, y1);
immVertex2i(pos, x2, y1);
immVertex2i(pos, x2, y2);
immVertex2i(pos, x1, y2);
immEnd();
}
void immRectf_fast(uint pos, float x1, float y1, float x2, float y2)
{
immVertex2f(pos, x1, y1);
immVertex2f(pos, x2, y1);
immVertex2f(pos, x2, y2);
immVertex2f(pos, x1, y1);
immVertex2f(pos, x2, y2);
immVertex2f(pos, x1, y2);
}
void immRectf_fast_with_color(
uint pos, uint col, float x1, float y1, float x2, float y2, const float color[4])
{
immAttr4fv(col, color);
immVertex2f(pos, x1, y1);
immAttr4fv(col, color);
immVertex2f(pos, x2, y1);
immAttr4fv(col, color);
immVertex2f(pos, x2, y2);
immAttr4fv(col, color);
immVertex2f(pos, x1, y1);
immAttr4fv(col, color);
immVertex2f(pos, x2, y2);
immAttr4fv(col, color);
immVertex2f(pos, x1, y2);
}
void immRecti_fast_with_color(
uint pos, uint col, int x1, int y1, int x2, int y2, const float color[4])
{
immAttr4fv(col, color);
immVertex2i(pos, x1, y1);
immAttr4fv(col, color);
immVertex2i(pos, x2, y1);
immAttr4fv(col, color);
immVertex2i(pos, x2, y2);
immAttr4fv(col, color);
immVertex2i(pos, x1, y1);
immAttr4fv(col, color);
immVertex2i(pos, x2, y2);
immAttr4fv(col, color);
immVertex2i(pos, x1, y2);
}
void immRectf_with_texco(const uint pos, const uint tex_coord, const rctf &p, const rctf &uv)
{
immBegin(GPU_PRIM_TRI_FAN, 4);
immAttr2f(tex_coord, uv.xmin, uv.ymin);
immVertex2f(pos, p.xmin, p.ymin);
immAttr2f(tex_coord, uv.xmin, uv.ymax);
immVertex2f(pos, p.xmin, p.ymax);
immAttr2f(tex_coord, uv.xmax, uv.ymax);
immVertex2f(pos, p.xmax, p.ymax);
immAttr2f(tex_coord, uv.xmax, uv.ymin);
immVertex2f(pos, p.xmax, p.ymin);
immEnd();
}
#if 0 /* more complete version in case we want that */
void immRecti_complete(int x1, int y1, int x2, int y2, const float color[4])
{
GPUVertFormat *format = immVertexFormat();
uint pos = add_attr(format, "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformColor4fv(color);
immRectf(pos, x1, y1, x2, y2);
immUnbindProgram();
}
#endif
void imm_cpack(uint x)
{
immUniformColor3ub(((x) & 0xFF), (((x) >> 8) & 0xFF), (((x) >> 16) & 0xFF));
}
static void imm_draw_circle(GPUPrimType prim_type,
const uint shdr_pos,
float x,
float y,
float radius_x,
float radius_y,
int nsegments)
{
if (prim_type == GPU_PRIM_LINE_LOOP) {
/* NOTE(Metal/AMD): For small primitives, line list more efficient than line strip.. */
immBegin(GPU_PRIM_LINES, nsegments * 2);
immVertex2f(shdr_pos, x + (radius_x * cosf(0.0f)), y + (radius_y * sinf(0.0f)));
for (int i = 1; i < nsegments; i++) {
const float angle = float(2 * M_PI) * (float(i) / float(nsegments));
immVertex2f(shdr_pos, x + (radius_x * cosf(angle)), y + (radius_y * sinf(angle)));
immVertex2f(shdr_pos, x + (radius_x * cosf(angle)), y + (radius_y * sinf(angle)));
}
immVertex2f(shdr_pos, x + (radius_x * cosf(0.0f)), y + (radius_y * sinf(0.0f)));
immEnd();
}
else {
immBegin(prim_type, nsegments);
for (int i = 0; i < nsegments; i++) {
const float angle = float(2 * M_PI) * (float(i) / float(nsegments));
immVertex2f(shdr_pos, x + (radius_x * cosf(angle)), y + (radius_y * sinf(angle)));
}
immEnd();
}
}
void imm_draw_circle_wire_2d(uint shdr_pos, float x, float y, float radius, int nsegments)
{
imm_draw_circle(GPU_PRIM_LINE_LOOP, shdr_pos, x, y, radius, radius, nsegments);
}
void imm_draw_circle_fill_2d(uint shdr_pos, float x, float y, float radius, int nsegments)
{
imm_draw_circle(GPU_PRIM_TRI_FAN, shdr_pos, x, y, radius, radius, nsegments);
}
void imm_draw_circle_wire_aspect_2d(
uint shdr_pos, float x, float y, float radius_x, float radius_y, int nsegments)
{
imm_draw_circle(GPU_PRIM_LINE_LOOP, shdr_pos, x, y, radius_x, radius_y, nsegments);
}
void imm_draw_circle_fill_aspect_2d(
uint shdr_pos, float x, float y, float radius_x, float radius_y, int nsegments)
{
imm_draw_circle(GPU_PRIM_TRI_FAN, shdr_pos, x, y, radius_x, radius_y, nsegments);
}
static void imm_draw_circle_partial(GPUPrimType prim_type,
uint pos,
float x,
float y,
float radius,
int nsegments,
float start,
float sweep)
{
/* shift & reverse angle, increase 'nsegments' to match gluPartialDisk */
const float angle_start = -DEG2RADF(start) + float(M_PI_2);
const float angle_end = -(DEG2RADF(sweep) - angle_start);
nsegments += 1;
immBegin(prim_type, nsegments);
for (int i = 0; i < nsegments; i++) {
const float angle = interpf(angle_start, angle_end, (float(i) / float(nsegments - 1)));
const float angle_sin = sinf(angle);
const float angle_cos = cosf(angle);
immVertex2f(pos, x + radius * angle_cos, y + radius * angle_sin);
}
immEnd();
}
static void imm_draw_circle_partial_3d(GPUPrimType prim_type,
uint pos,
float x,
float y,
float z,
float rad,
int nsegments,
float start,
float sweep)
{
/* shift & reverse angle, increase 'nsegments' to match gluPartialDisk */
const float angle_start = -DEG2RADF(start) + float(M_PI / 2);
const float angle_end = -(DEG2RADF(sweep) - angle_start);
nsegments += 1;
immBegin(prim_type, nsegments);
for (int i = 0; i < nsegments; i++) {
const float angle = interpf(angle_start, angle_end, (float(i) / float(nsegments - 1)));
const float angle_sin = sinf(angle);
const float angle_cos = cosf(angle);
immVertex3f(pos, x + rad * angle_cos, y + rad * angle_sin, z);
}
immEnd();
}
void imm_draw_circle_partial_wire_2d(
uint pos, float x, float y, float radius, int nsegments, float start, float sweep)
{
imm_draw_circle_partial(GPU_PRIM_LINE_STRIP, pos, x, y, radius, nsegments, start, sweep);
}
void imm_draw_circle_partial_wire_3d(
uint pos, float x, float y, float z, float radius, int nsegments, float start, float sweep)
{
imm_draw_circle_partial_3d(GPU_PRIM_LINE_STRIP, pos, x, y, z, radius, nsegments, start, sweep);
}
static void imm_draw_disk_partial(GPUPrimType prim_type,
uint pos,
float x,
float y,
float rad_inner,
float rad_outer,
int nsegments,
float start,
float sweep)
{
/* to avoid artifacts */
const float max_angle = 3 * 360;
CLAMP(sweep, -max_angle, max_angle);
/* shift & reverse angle, increase 'nsegments' to match gluPartialDisk */
const float angle_start = -DEG2RADF(start) + float(M_PI_2);
const float angle_end = -(DEG2RADF(sweep) - angle_start);
nsegments += 1;
immBegin(prim_type, nsegments * 2);
for (int i = 0; i < nsegments; i++) {
const float angle = interpf(angle_start, angle_end, (float(i) / float(nsegments - 1)));
const float angle_sin = sinf(angle);
const float angle_cos = cosf(angle);
immVertex2f(pos, x + rad_inner * angle_cos, y + rad_inner * angle_sin);
immVertex2f(pos, x + rad_outer * angle_cos, y + rad_outer * angle_sin);
}
immEnd();
}
static void imm_draw_disk_partial_3d(GPUPrimType prim_type,
uint pos,
float x,
float y,
float z,
float rad_inner,
float rad_outer,
int nsegments,
float start,
float sweep)
{
/* to avoid artifacts */
const float max_angle = 3 * 360;
CLAMP(sweep, -max_angle, max_angle);
/* shift & reverse angle, increase 'nsegments' to match gluPartialDisk */
const float angle_start = -DEG2RADF(start) + float(M_PI_2);
const float angle_end = -(DEG2RADF(sweep) - angle_start);
nsegments += 1;
immBegin(prim_type, nsegments * 2);
for (int i = 0; i < nsegments; i++) {
const float angle = interpf(angle_start, angle_end, (float(i) / float(nsegments - 1)));
const float angle_sin = sinf(angle);
const float angle_cos = cosf(angle);
immVertex3f(pos, x + rad_inner * angle_cos, y + rad_inner * angle_sin, z);
immVertex3f(pos, x + rad_outer * angle_cos, y + rad_outer * angle_sin, z);
}
immEnd();
}
void imm_draw_disk_partial_fill_2d(uint pos,
float x,
float y,
float rad_inner,
float rad_outer,
int nsegments,
float start,
float sweep)
{
imm_draw_disk_partial(
GPU_PRIM_TRI_STRIP, pos, x, y, rad_inner, rad_outer, nsegments, start, sweep);
}
void imm_draw_disk_partial_fill_3d(uint pos,
float x,
float y,
float z,
float rad_inner,
float rad_outer,
int nsegments,
float start,
float sweep)
{
imm_draw_disk_partial_3d(
GPU_PRIM_TRI_STRIP, pos, x, y, z, rad_inner, rad_outer, nsegments, start, sweep);
}
static void imm_draw_circle_3D(GPUPrimType prim_type,
uint pos,
float x,
float y,
float radius_x,
float radius_y,
int nsegments)
{
if (prim_type == GPU_PRIM_LINE_LOOP) {
/* NOTE(Metal/AMD): For small primitives, line list more efficient than line strip. */
immBegin(GPU_PRIM_LINES, nsegments * 2);
const float angle = float(2 * M_PI) / float(nsegments);
float xprev = cosf(-angle) * radius_x;
float yprev = sinf(-angle) * radius_y;
const float alpha = 2.0f * cosf(angle);
float xr = radius_x;
float yr = 0;
for (int i = 0; i < nsegments; i++) {
immVertex3f(pos, x + xr, y + yr, 0.0f);
if (i) {
immVertex3f(pos, x + xr, y + yr, 0.0f);
}
/* `cos[(n + 1)a] = 2cos(a)cos(na) - cos[(n - 1)a]`. */
const float xnext = alpha * xr - xprev;
/* `sin[(n + 1)a] = 2cos(a)sin(na) - sin[(n - 1)a]`. */
const float ynext = alpha * yr - yprev;
xprev = xr;
yprev = yr;
xr = xnext;
yr = ynext;
}
immVertex3f(pos, x + radius_x, y, 0.0f);
immEnd();
}
else {
immBegin(prim_type, nsegments);
for (int i = 0; i < nsegments; i++) {
float angle = float(2 * M_PI) * (float(i) / float(nsegments));
immVertex3f(pos, x + radius_x * cosf(angle), y + radius_y * sinf(angle), 0.0f);
}
immEnd();
}
}
void imm_draw_circle_wire_3d(uint pos, float x, float y, float radius, int nsegments)
{
imm_draw_circle_3D(GPU_PRIM_LINE_LOOP, pos, x, y, radius, radius, nsegments);
}
void imm_draw_circle_wire_aspect_3d(
uint pos, float x, float y, float radius_x, float radius_y, int nsegments)
{
imm_draw_circle_3D(GPU_PRIM_LINE_LOOP, pos, x, y, radius_x, radius_y, nsegments);
}
void imm_draw_circle_dashed_3d(uint pos, float x, float y, float radius, int nsegments)
{
imm_draw_circle_3D(GPU_PRIM_LINES, pos, x, y, radius, radius, nsegments / 2);
}
void imm_draw_circle_fill_3d(uint pos, float x, float y, float radius, int nsegments)
{
imm_draw_circle_3D(GPU_PRIM_TRI_FAN, pos, x, y, radius, radius, nsegments);
}
void imm_draw_circle_fill_aspect_3d(
uint pos, float x, float y, float radius_x, float radius_y, int nsegments)
{
imm_draw_circle_3D(GPU_PRIM_TRI_FAN, pos, x, y, radius_x, radius_y, nsegments);
}
void imm_draw_box_wire_2d(uint pos, float x1, float y1, float x2, float y2)
{
/* NOTE(Metal/AMD): For small primitives, line list more efficient than line-strip. */
immBegin(GPU_PRIM_LINES, 8);
immVertex2f(pos, x1, y1);
immVertex2f(pos, x1, y2);
immVertex2f(pos, x1, y2);
immVertex2f(pos, x2, y2);
immVertex2f(pos, x2, y2);
immVertex2f(pos, x2, y1);
immVertex2f(pos, x2, y1);
immVertex2f(pos, x1, y1);
immEnd();
}
void imm_draw_box_wire_3d(uint pos, float x1, float y1, float x2, float y2)
{
/* use this version when GPUVertFormat has a vec3 position */
/* NOTE(Metal/AMD): For small primitives, line list more efficient than line-strip. */
immBegin(GPU_PRIM_LINES, 8);
immVertex3f(pos, x1, y1, 0.0f);
immVertex3f(pos, x1, y2, 0.0f);
immVertex3f(pos, x1, y2, 0.0f);
immVertex3f(pos, x2, y2, 0.0f);
immVertex3f(pos, x2, y2, 0.0f);
immVertex3f(pos, x2, y1, 0.0f);
immVertex3f(pos, x2, y1, 0.0f);
immVertex3f(pos, x1, y1, 0.0f);
immEnd();
}
void imm_draw_box_checker_2d_ex(float x1,
float y1,
float x2,
float y2,
const float color_primary[4],
const float color_secondary[4],
int checker_size)
{
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_2D_CHECKER);
immUniform4fv("color1", color_primary);
immUniform4fv("color2", color_secondary);
immUniform1i("size", checker_size);
immRectf(pos, x1, y1, x2, y2);
immUnbindProgram();
}
void imm_draw_box_checker_2d(float x1, float y1, float x2, float y2, bool clear_alpha)
{
float checker_primary[4];
float checker_secondary[4];
ui::theme::get_color_4fv(TH_TRANSPARENT_CHECKER_PRIMARY, checker_primary);
ui::theme::get_color_4fv(TH_TRANSPARENT_CHECKER_SECONDARY, checker_secondary);
checker_primary[3] = clear_alpha ? 0.0 : checker_primary[3];
checker_secondary[3] = clear_alpha ? 0.0 : checker_secondary[3];
int checker_size = ui::theme::get_value(TH_TRANSPARENT_CHECKER_SIZE) * U.pixelsize;
imm_draw_box_checker_2d_ex(x1, y1, x2, y2, checker_primary, checker_secondary, checker_size);
}
void imm_draw_cube_fill_3d(uint pos, const float center[3], const float aspect[3])
{
float coords[ARRAY_SIZE(cube_coords)][3];
for (int i = 0; i < ARRAY_SIZE(cube_coords); i++) {
madd_v3_v3v3v3(coords[i], center, cube_coords[i], aspect);
}
immBegin(GPU_PRIM_TRIS, ARRAY_SIZE(cube_quad_index) * 3 * 2);
for (int i = 0; i < ARRAY_SIZE(cube_quad_index); i++) {
immVertex3fv(pos, coords[cube_quad_index[i][0]]);
immVertex3fv(pos, coords[cube_quad_index[i][1]]);
immVertex3fv(pos, coords[cube_quad_index[i][2]]);
immVertex3fv(pos, coords[cube_quad_index[i][0]]);
immVertex3fv(pos, coords[cube_quad_index[i][2]]);
immVertex3fv(pos, coords[cube_quad_index[i][3]]);
}
immEnd();
}
void imm_draw_cube_wire_3d(uint pos, const float center[3], const float aspect[3])
{
float coords[ARRAY_SIZE(cube_coords)][3];
for (int i = 0; i < ARRAY_SIZE(cube_coords); i++) {
madd_v3_v3v3v3(coords[i], center, cube_coords[i], aspect);
}
immBegin(GPU_PRIM_LINES, ARRAY_SIZE(cube_line_index) * 2);
for (int i = 0; i < ARRAY_SIZE(cube_line_index); i++) {
immVertex3fv(pos, coords[cube_line_index[i][0]]);
immVertex3fv(pos, coords[cube_line_index[i][1]]);
}
immEnd();
}
void imm_draw_cube_corners_3d(uint pos,
const float center[3],
const float aspect[3],
const float factor)
{
float coords[ARRAY_SIZE(cube_coords)][3];
for (int i = 0; i < ARRAY_SIZE(cube_coords); i++) {
madd_v3_v3v3v3(coords[i], center, cube_coords[i], aspect);
}
immBegin(GPU_PRIM_LINES, ARRAY_SIZE(cube_line_index) * 4);
for (int i = 0; i < ARRAY_SIZE(cube_line_index); i++) {
float vec[3], co[3];
sub_v3_v3v3(vec, coords[cube_line_index[i][1]], coords[cube_line_index[i][0]]);
mul_v3_fl(vec, factor);
copy_v3_v3(co, coords[cube_line_index[i][0]]);
immVertex3fv(pos, co);
add_v3_v3(co, vec);
immVertex3fv(pos, co);
copy_v3_v3(co, coords[cube_line_index[i][1]]);
immVertex3fv(pos, co);
sub_v3_v3(co, vec);
immVertex3fv(pos, co);
}
immEnd();
}
void imm_draw_cylinder_fill_normal_3d(
uint pos, uint nor, float base, float top, float height, int slices, int stacks)
{
immBegin(GPU_PRIM_TRIS, 6 * slices * stacks);
for (int i = 0; i < slices; i++) {
const float angle1 = float(2 * M_PI) * (float(i) / float(slices));
const float angle2 = float(2 * M_PI) * (float(i + 1) / float(slices));
const float cos1 = cosf(angle1);
const float sin1 = sinf(angle1);
const float cos2 = cosf(angle2);
const float sin2 = sinf(angle2);
for (int j = 0; j < stacks; j++) {
float fac1 = float(j) / float(stacks);
float fac2 = float(j + 1) / float(stacks);
float r1 = base * (1.0f - fac1) + top * fac1;
float r2 = base * (1.0f - fac2) + top * fac2;
float h1 = height * (float(j) / float(stacks));
float h2 = height * (float(j + 1) / float(stacks));
const float v1[3] = {r1 * cos2, r1 * sin2, h1};
const float v2[3] = {r2 * cos2, r2 * sin2, h2};
const float v3[3] = {r2 * cos1, r2 * sin1, h2};
const float v4[3] = {r1 * cos1, r1 * sin1, h1};
float n1[3], n2[3];
/* calc normals */
sub_v3_v3v3(n1, v2, v1);
normalize_v3(n1);
n1[0] = cos1;
n1[1] = sin1;
n1[2] = 1 - n1[2];
sub_v3_v3v3(n2, v3, v4);
normalize_v3(n2);
n2[0] = cos2;
n2[1] = sin2;
n2[2] = 1 - n2[2];
/* first tri */
immAttr3fv(nor, n2);
immVertex3fv(pos, v1);
immVertex3fv(pos, v2);
immAttr3fv(nor, n1);
immVertex3fv(pos, v3);
/* second tri */
immVertex3fv(pos, v3);
immVertex3fv(pos, v4);
immAttr3fv(nor, n2);
immVertex3fv(pos, v1);
}
}
immEnd();
}
void imm_draw_cylinder_wire_3d(
uint pos, float base, float top, float height, int slices, int stacks)
{
immBegin(GPU_PRIM_LINES, 6 * slices * stacks);
for (int i = 0; i < slices; i++) {
const float angle1 = float(2 * M_PI) * (float(i) / float(slices));
const float angle2 = float(2 * M_PI) * (float(i + 1) / float(slices));
const float cos1 = cosf(angle1);
const float sin1 = sinf(angle1);
const float cos2 = cosf(angle2);
const float sin2 = sinf(angle2);
for (int j = 0; j < stacks; j++) {
float fac1 = float(j) / float(stacks);
float fac2 = float(j + 1) / float(stacks);
float r1 = base * (1.0f - fac1) + top * fac1;
float r2 = base * (1.0f - fac2) + top * fac2;
float h1 = height * (float(j) / float(stacks));
float h2 = height * (float(j + 1) / float(stacks));
const float v1[3] = {r1 * cos2, r1 * sin2, h1};
const float v2[3] = {r2 * cos2, r2 * sin2, h2};
const float v3[3] = {r2 * cos1, r2 * sin1, h2};
const float v4[3] = {r1 * cos1, r1 * sin1, h1};
immVertex3fv(pos, v1);
immVertex3fv(pos, v2);
immVertex3fv(pos, v2);
immVertex3fv(pos, v3);
immVertex3fv(pos, v1);
immVertex3fv(pos, v4);
}
}
immEnd();
}
void imm_draw_cylinder_fill_3d(
uint pos, float base, float top, float height, int slices, int stacks)
{
immBegin(GPU_PRIM_TRIS, 6 * slices * stacks);
for (int i = 0; i < slices; i++) {
const float angle1 = float(2 * M_PI) * (float(i) / float(slices));
const float angle2 = float(2 * M_PI) * (float(i + 1) / float(slices));
const float cos1 = cosf(angle1);
const float sin1 = sinf(angle1);
const float cos2 = cosf(angle2);
const float sin2 = sinf(angle2);
for (int j = 0; j < stacks; j++) {
float fac1 = float(j) / float(stacks);
float fac2 = float(j + 1) / float(stacks);
float r1 = base * (1.0f - fac1) + top * fac1;
float r2 = base * (1.0f - fac2) + top * fac2;
float h1 = height * (float(j) / float(stacks));
float h2 = height * (float(j + 1) / float(stacks));
const float v1[3] = {r1 * cos2, r1 * sin2, h1};
const float v2[3] = {r2 * cos2, r2 * sin2, h2};
const float v3[3] = {r2 * cos1, r2 * sin1, h2};
const float v4[3] = {r1 * cos1, r1 * sin1, h1};
/* first tri */
immVertex3fv(pos, v1);
immVertex3fv(pos, v2);
immVertex3fv(pos, v3);
/* second tri */
immVertex3fv(pos, v3);
immVertex3fv(pos, v4);
immVertex3fv(pos, v1);
}
}
immEnd();
}
/* Circle Drawing - Tables for Optimized Drawing Speed */
constexpr static int CIRCLE_RESOL = 32;
static void circball_array_fill(const float verts[CIRCLE_RESOL][3],
const float cent[3],
const float radius,
const float tmat[4][4])
{
/* 32 values of sin function (still same result!) */
const float sinval[CIRCLE_RESOL] = {
0.00000000, 0.20129852, 0.39435585, 0.57126821, 0.72479278, 0.84864425, 0.93775213,
0.98846832, 0.99871650, 0.96807711, 0.89780453, 0.79077573, 0.65137248, 0.48530196,
0.29936312, 0.10116832, -0.10116832, -0.29936312, -0.48530196, -0.65137248, -0.79077573,
-0.89780453, -0.96807711, -0.99871650, -0.98846832, -0.93775213, -0.84864425, -0.72479278,
-0.57126821, -0.39435585, -0.20129852, 0.00000000,
};
/* 32 values of cos function (still same result!) */
const float cosval[CIRCLE_RESOL] = {
1.00000000, 0.97952994, 0.91895781, 0.82076344, 0.68896691, 0.52896401, 0.34730525,
0.15142777, -0.05064916, -0.25065253, -0.44039415, -0.61210598, -0.75875812, -0.87434661,
-0.95413925, -0.99486932, -0.99486932, -0.95413925, -0.87434661, -0.75875812, -0.61210598,
-0.44039415, -0.25065253, -0.05064916, 0.15142777, 0.34730525, 0.52896401, 0.68896691,
0.82076344, 0.91895781, 0.97952994, 1.00000000,
};
float vx[3], vy[3];
float *viter = (float *)verts;
mul_v3_v3fl(vx, tmat[0], radius);
mul_v3_v3fl(vy, tmat[1], radius);
for (uint a = 0; a < CIRCLE_RESOL; a++, viter += 3) {
viter[0] = cent[0] + sinval[a] * vx[0] + cosval[a] * vy[0];
viter[1] = cent[1] + sinval[a] * vx[1] + cosval[a] * vy[1];
viter[2] = cent[2] + sinval[a] * vx[2] + cosval[a] * vy[2];
}
}
void imm_drawcircball(const float cent[3], float radius, const float tmat[4][4], uint pos)
{
float verts[CIRCLE_RESOL][3];
circball_array_fill(verts, cent, radius, tmat);
immBegin(GPU_PRIM_LINE_LOOP, CIRCLE_RESOL);
for (int i = 0; i < CIRCLE_RESOL; i++) {
immVertex3fv(pos, verts[i]);
}
immEnd();
}
} // namespace blender

View File

@@ -0,0 +1,547 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU element list (AKA index buffer)
*/
#include "MEM_guardedalloc.h"
#include "BLI_array_utils.hh"
#include "BLI_math_base.h"
#include "BLI_utildefines.h"
#include "gpu_backend.hh"
#include "GPU_index_buffer.hh"
#include "GPU_capabilities.hh"
#include "GPU_compute.hh"
#include "GPU_platform.hh"
#include "GPU_state.hh"
#include <algorithm> /* For `min/max`. */
#include <cstring>
namespace blender {
/* -------------------------------------------------------------------- */
/** \name IndexBufBuilder
* \{ */
using namespace blender::gpu;
void GPU_indexbuf_init_ex(GPUIndexBufBuilder *builder,
GPUPrimType prim_type,
uint index_len,
uint vertex_len)
{
builder->max_allowed_index = vertex_len - 1;
builder->max_index_len = index_len;
builder->index_len = 0; // start empty
builder->index_min = UINT32_MAX;
builder->index_max = 0;
builder->prim_type = prim_type;
#ifdef __APPLE__
/* Only encode restart indices for restart-compatible primitive types.
* Resolves out-of-bounds read error on macOS. Using 0-index will ensure
* degenerative primitives when skipping primitives is required and will
* incur no additional performance cost for rendering. */
if (GPU_type_matches_ex(GPU_DEVICE_ANY, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_METAL)) {
/* We will still use restart-indices for point primitives and then
* patch these during IndexBuf::init, as we cannot benefit from degenerative
* primitives to eliminate these. */
builder->restart_index_value = (is_restart_compatible(prim_type) ||
prim_type == GPU_PRIM_POINTS) ?
RESTART_INDEX :
0;
}
else {
builder->restart_index_value = RESTART_INDEX;
}
#else
builder->restart_index_value = RESTART_INDEX;
#endif
builder->uses_restart_indices = false;
builder->data = MEM_new_array_uninitialized<uint>(builder->max_index_len, "IndexBuf data");
}
void GPU_indexbuf_init(GPUIndexBufBuilder *builder,
GPUPrimType prim_type,
uint prim_len,
uint vertex_len)
{
int verts_per_prim = GPU_indexbuf_primitive_len(prim_type);
BLI_assert(verts_per_prim != -1);
GPU_indexbuf_init_ex(builder, prim_type, prim_len * uint(verts_per_prim), vertex_len);
}
IndexBuf *GPU_indexbuf_build_on_device(uint index_len)
{
IndexBuf *elem_ = GPU_indexbuf_calloc();
GPU_indexbuf_init_build_on_device(elem_, index_len);
return elem_;
}
void GPU_indexbuf_init_build_on_device(IndexBuf *elem, uint index_len)
{
IndexBuf *elem_ = elem;
elem_->init_build_on_device(index_len);
}
MutableSpan<uint32_t> GPU_indexbuf_get_data(GPUIndexBufBuilder *builder)
{
return {builder->data, builder->max_index_len};
}
void GPU_indexbuf_join(GPUIndexBufBuilder *builder_to, const GPUIndexBufBuilder *builder_from)
{
BLI_assert(builder_to->data == builder_from->data);
builder_to->index_len = max_uu(builder_to->index_len, builder_from->index_len);
builder_to->index_min = min_uu(builder_to->index_min, builder_from->index_min);
builder_to->index_max = max_uu(builder_to->index_max, builder_from->index_max);
}
void GPU_indexbuf_add_generic_vert(GPUIndexBufBuilder *builder, uint v)
{
BLI_assert(builder->data != nullptr);
BLI_assert(builder->index_len < builder->max_index_len);
BLI_assert(v <= builder->max_allowed_index);
builder->data[builder->index_len++] = v;
builder->index_min = std::min(builder->index_min, v);
builder->index_max = std::max(builder->index_max, v);
}
void GPU_indexbuf_add_primitive_restart(GPUIndexBufBuilder *builder)
{
BLI_assert(builder->data != nullptr);
BLI_assert(builder->index_len < builder->max_index_len);
builder->data[builder->index_len++] = builder->restart_index_value;
builder->uses_restart_indices = true;
}
void GPU_indexbuf_add_point_vert(GPUIndexBufBuilder *builder, uint v)
{
BLI_assert(builder->prim_type == GPU_PRIM_POINTS);
GPU_indexbuf_add_generic_vert(builder, v);
}
void GPU_indexbuf_add_line_verts(GPUIndexBufBuilder *builder, uint v1, uint v2)
{
BLI_assert(builder->prim_type == GPU_PRIM_LINES);
BLI_assert(v1 != v2);
GPU_indexbuf_add_generic_vert(builder, v1);
GPU_indexbuf_add_generic_vert(builder, v2);
}
void GPU_indexbuf_add_tri_verts(GPUIndexBufBuilder *builder, uint v1, uint v2, uint v3)
{
BLI_assert(builder->prim_type == GPU_PRIM_TRIS);
BLI_assert(v1 != v2 && v2 != v3 && v3 != v1);
GPU_indexbuf_add_generic_vert(builder, v1);
GPU_indexbuf_add_generic_vert(builder, v2);
GPU_indexbuf_add_generic_vert(builder, v3);
}
void GPU_indexbuf_add_line_adj_verts(
GPUIndexBufBuilder *builder, uint v1, uint v2, uint v3, uint v4)
{
BLI_assert(builder->prim_type == GPU_PRIM_LINES_ADJ);
BLI_assert(v2 != v3); /* only the line need diff indices */
GPU_indexbuf_add_generic_vert(builder, v1);
GPU_indexbuf_add_generic_vert(builder, v2);
GPU_indexbuf_add_generic_vert(builder, v3);
GPU_indexbuf_add_generic_vert(builder, v4);
}
void GPU_indexbuf_set_point_vert(GPUIndexBufBuilder *builder, uint elem, uint v1)
{
BLI_assert(builder->prim_type == GPU_PRIM_POINTS);
BLI_assert(elem < builder->max_index_len);
builder->data[elem++] = v1;
builder->index_min = std::min(builder->index_min, v1);
builder->index_max = std::max(builder->index_max, v1);
builder->index_len = std::max(builder->index_len, elem);
}
void GPU_indexbuf_set_line_verts(GPUIndexBufBuilder *builder, uint elem, uint v1, uint v2)
{
BLI_assert(builder->prim_type == GPU_PRIM_LINES);
BLI_assert(v1 != v2);
BLI_assert(v1 <= builder->max_allowed_index);
BLI_assert(v2 <= builder->max_allowed_index);
BLI_assert((elem + 1) * 2 <= builder->max_index_len);
uint idx = elem * 2;
builder->data[idx++] = v1;
builder->data[idx++] = v2;
builder->index_min = std::min({builder->index_min, v1, v2});
builder->index_max = std::max({builder->index_max, v1, v2});
builder->index_len = std::max(builder->index_len, idx);
}
void GPU_indexbuf_set_tri_verts(GPUIndexBufBuilder *builder, uint elem, uint v1, uint v2, uint v3)
{
BLI_assert(builder->prim_type == GPU_PRIM_TRIS);
BLI_assert(v1 != v2 && v2 != v3 && v3 != v1);
BLI_assert(v1 <= builder->max_allowed_index);
BLI_assert(v2 <= builder->max_allowed_index);
BLI_assert(v3 <= builder->max_allowed_index);
BLI_assert((elem + 1) * 3 <= builder->max_index_len);
uint idx = elem * 3;
builder->data[idx++] = v1;
builder->data[idx++] = v2;
builder->data[idx++] = v3;
builder->index_min = std::min({builder->index_min, v1, v2, v3});
builder->index_max = std::max({builder->index_max, v1, v2, v3});
builder->index_len = std::max(builder->index_len, idx);
}
void GPU_indexbuf_set_point_restart(GPUIndexBufBuilder *builder, uint elem)
{
BLI_assert(builder->prim_type == GPU_PRIM_POINTS);
BLI_assert(elem < builder->max_index_len);
builder->data[elem++] = builder->restart_index_value;
builder->index_len = std::max(builder->index_len, elem);
builder->uses_restart_indices = true;
}
void GPU_indexbuf_set_line_restart(GPUIndexBufBuilder *builder, uint elem)
{
BLI_assert(builder->prim_type == GPU_PRIM_LINES);
BLI_assert((elem + 1) * 2 <= builder->max_index_len);
uint idx = elem * 2;
builder->data[idx++] = builder->restart_index_value;
builder->data[idx++] = builder->restart_index_value;
builder->index_len = std::max(builder->index_len, idx);
builder->uses_restart_indices = true;
}
void GPU_indexbuf_set_tri_restart(GPUIndexBufBuilder *builder, uint elem)
{
BLI_assert(builder->prim_type == GPU_PRIM_TRIS);
BLI_assert((elem + 1) * 3 <= builder->max_index_len);
uint idx = elem * 3;
builder->data[idx++] = builder->restart_index_value;
builder->data[idx++] = builder->restart_index_value;
builder->data[idx++] = builder->restart_index_value;
builder->index_len = std::max(builder->index_len, idx);
builder->uses_restart_indices = true;
}
IndexBuf *GPU_indexbuf_build_curves_on_device(GPUPrimType prim_type,
uint curves_num,
uint verts_per_curve)
{
uint64_t dispatch_x_dim = verts_per_curve;
if (ELEM(prim_type, GPU_PRIM_LINE_STRIP, GPU_PRIM_TRI_STRIP)) {
dispatch_x_dim += 1;
}
uint64_t grid_x, grid_y, grid_z;
uint64_t max_grid_x = GPU_max_work_group_count(0), max_grid_y = GPU_max_work_group_count(1),
max_grid_z = GPU_max_work_group_count(2);
grid_x = min_uu(max_grid_x, (dispatch_x_dim + 15) / 16);
grid_y = (curves_num + 15) / 16;
if (grid_y <= max_grid_y) {
grid_z = 1;
}
else {
grid_y = grid_z = uint64_t(ceil(sqrt(double(grid_y))));
grid_y = min_uu(grid_y, max_grid_y);
grid_z = min_uu(grid_z, max_grid_z);
}
bool tris = (prim_type == GPU_PRIM_TRIS);
bool lines = (prim_type == GPU_PRIM_LINES);
gpu::Shader *shader = GPU_shader_get_builtin_shader(
tris ? GPU_SHADER_INDEXBUF_TRIS :
(lines ? GPU_SHADER_INDEXBUF_LINES : GPU_SHADER_INDEXBUF_POINTS));
GPU_shader_bind(shader);
IndexBuf *ibo = GPU_indexbuf_build_on_device(curves_num * dispatch_x_dim);
int resolution;
if (tris) {
resolution = 6;
}
else if (lines) {
resolution = 2;
}
else {
resolution = 1;
}
GPU_shader_uniform_1i(shader, "elements_per_curve", dispatch_x_dim / resolution);
GPU_shader_uniform_1i(shader, "ncurves", curves_num);
GPU_indexbuf_bind_as_ssbo(ibo, GPU_shader_get_ssbo_binding(shader, "out_indices"));
GPU_compute_dispatch(shader, grid_x, grid_y, grid_z);
GPU_memory_barrier(GPU_BARRIER_ELEMENT_ARRAY);
GPU_shader_unbind();
return ibo;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
namespace gpu {
IndexBuf::~IndexBuf()
{
if (!is_subrange_) {
MEM_SAFE_DELETE_VOID(data_);
}
}
void IndexBuf::init(uint indices_len,
uint32_t *indices,
uint min_index,
uint max_index,
GPUPrimType prim_type,
bool uses_restart_indices)
{
is_init_ = true;
data_ = indices;
index_start_ = 0;
index_len_ = indices_len;
is_empty_ = min_index > max_index;
/* Patch index buffer to remove restart indices from
* non-restart-compatible primitive types. Restart indices
* are situationally added to selectively hide vertices.
* Metal does not support restart-indices for non-restart-compatible
* types, as such we should remove these indices.
*
* We only need to perform this for point primitives, as
* line primitives/triangle primitives can use index 0 for all
* vertices to create a degenerative primitive, where all
* vertices share the same index and skip rendering via HW
* culling. */
if (prim_type == GPU_PRIM_POINTS && uses_restart_indices) {
this->strip_restart_indices();
}
#if GPU_TRACK_INDEX_RANGE
/* Everything remains 32 bit while building to keep things simple.
* Find min/max after, then convert to smallest index type possible. */
uint range = min_index < max_index ? max_index - min_index : 0;
/* count the primitive restart index. */
range += 1;
if (range <= 0xFFFF) {
index_type_ = GPU_INDEX_U16;
bool do_clamp_indices = false;
# ifdef __APPLE__
/* NOTE: For the Metal Backend, we use degenerative primitives to hide vertices
* which are not restart compatible. When this is done, we need to ensure
* that compressed index ranges clamp all index values within the valid
* range, rather than maximally clamping against the USHORT restart index
* value of 0xFFFFu, as this will cause an out-of-bounds read during
* vertex assembly. */
do_clamp_indices = GPU_type_matches_ex(
GPU_DEVICE_ANY, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_METAL);
# endif
this->squeeze_indices_short(min_index, max_index, prim_type, do_clamp_indices);
}
#endif
}
void IndexBuf::init_build_on_device(uint index_len)
{
is_init_ = true;
index_start_ = 0;
index_len_ = index_len;
index_type_ = GPU_INDEX_U32;
data_ = nullptr;
}
void IndexBuf::init_subrange(IndexBuf *elem_src, uint start, uint length)
{
/* We don't support nested sub-ranges. */
BLI_assert(elem_src && elem_src->is_subrange_ == false);
BLI_assert((length == 0) || (start + length <= elem_src->index_len_));
is_init_ = true;
is_subrange_ = true;
src_ = elem_src;
index_start_ = start;
index_len_ = length;
index_base_ = elem_src->index_base_;
index_type_ = elem_src->index_type_;
}
void IndexBuf::squeeze_indices_short(uint min_idx,
uint max_idx,
GPUPrimType prim_type,
bool clamp_indices_in_range)
{
/* data will never be *larger* than builder->data...
* converting in place to avoid extra allocation */
uint16_t *ushort_idx = static_cast<uint16_t *>(data_);
const uint32_t *uint_idx = static_cast<uint32_t *>(data_);
if (max_idx >= 0xFFFF) {
index_base_ = min_idx;
/* NOTE: When using restart_index=0 for degenerative primitives indices,
* the compressed index will go below zero and wrap around when min_idx > 0.
* In order to ensure the resulting index is still within range, we instead
* clamp index to the maximum within the index range.
*
* `clamp_max_idx` represents the maximum possible index to clamp against. If primitive is
* restart-compatible, we can just clamp against the primitive-restart value, otherwise, we
* must assign to a valid index within the range.
*
* NOTE: For OpenGL we skip this by disabling clamping, as we still need to use
* restart index values for point primitives to disable rendering. */
uint16_t clamp_max_idx = (is_restart_compatible(prim_type) || !clamp_indices_in_range) ?
0xFFFFu :
(max_idx - min_idx);
for (uint i = 0; i < index_len_; i++) {
ushort_idx[i] = std::min<uint16_t>(clamp_max_idx, uint_idx[i] - min_idx);
}
}
else {
index_base_ = 0;
for (uint i = 0; i < index_len_; i++) {
ushort_idx[i] = uint16_t(uint_idx[i]);
}
}
}
} // namespace gpu
/** \} */
/* -------------------------------------------------------------------- */
/** \name C-API
* \{ */
IndexBuf *GPU_indexbuf_calloc()
{
return GPUBackend::get()->indexbuf_alloc();
}
IndexBuf *GPU_indexbuf_build(GPUIndexBufBuilder *builder)
{
IndexBuf *elem = GPU_indexbuf_calloc();
GPU_indexbuf_build_in_place(builder, elem);
return elem;
}
IndexBuf *GPU_indexbuf_create_subrange(IndexBuf *elem_src, uint start, uint length)
{
IndexBuf *elem = GPU_indexbuf_calloc();
GPU_indexbuf_create_subrange_in_place(elem, elem_src, start, length);
return elem;
}
void GPU_indexbuf_build_in_place(GPUIndexBufBuilder *builder, IndexBuf *elem)
{
BLI_assert(builder->data != nullptr);
/* Transfer data ownership to IndexBuf.
* It will be uploaded upon first use. */
elem->init(builder->index_len,
builder->data,
builder->index_min,
builder->index_max,
builder->prim_type,
builder->uses_restart_indices);
builder->data = nullptr;
}
void GPU_indexbuf_build_in_place_ex(GPUIndexBufBuilder *builder,
const uint index_min,
const uint index_max,
const bool uses_restart_indices,
IndexBuf *elem)
{
BLI_assert(builder->data != nullptr);
/* Transfer data ownership to IndexBuf.
* It will be uploaded upon first use. */
elem->init(builder->max_index_len,
builder->data,
index_min,
index_max,
builder->prim_type,
uses_restart_indices);
builder->data = nullptr;
}
IndexBuf *GPU_indexbuf_build_ex(GPUIndexBufBuilder *builder,
const uint index_min,
const uint index_max,
const bool uses_restart_indices)
{
IndexBuf *elem = GPU_indexbuf_calloc();
GPU_indexbuf_build_in_place_ex(builder, index_min, index_max, uses_restart_indices, elem);
return elem;
}
IndexBuf *GPU_indexbuf_build_from_memory(const GPUPrimType prim_type,
const uint32_t *data,
const int32_t data_len,
const int32_t index_min,
const int32_t index_max,
const bool uses_restart_indices)
{
const uint32_t indices_num = data_len * indices_per_primitive(prim_type);
/* TODO: The need for this copy is meant to be temporary. The data should be uploaded directly to
* the GPU here rather than copied to an array owned by the IBO first. */
uint32_t *copy = MEM_new_array_uninitialized<uint32_t>(indices_num, __func__);
threading::memory_bandwidth_bound_task(sizeof(uint32_t) * indices_num * 2, [&]() {
array_utils::copy(Span(data, indices_num), MutableSpan(copy, indices_num));
});
IndexBuf *ibo = GPU_indexbuf_calloc();
ibo->init(indices_num, copy, index_min, index_max, prim_type, uses_restart_indices);
return ibo;
}
void GPU_indexbuf_create_subrange_in_place(IndexBuf *elem,
IndexBuf *elem_src,
uint start,
uint length)
{
elem->init_subrange(elem_src, start, length);
}
void GPU_indexbuf_read(IndexBuf *elem, uint32_t *data)
{
elem->read(data);
}
void GPU_indexbuf_discard(IndexBuf *elem)
{
delete elem;
}
bool GPU_indexbuf_is_init(IndexBuf *elem)
{
return elem->is_init();
}
int GPU_indexbuf_primitive_len(GPUPrimType prim_type)
{
return indices_per_primitive(prim_type);
}
void GPU_indexbuf_use(IndexBuf *elem)
{
elem->upload_data();
}
void GPU_indexbuf_bind_as_ssbo(IndexBuf *elem, int binding)
{
elem->bind_as_ssbo(binding);
}
void GPU_indexbuf_update_sub(IndexBuf *elem, uint start, uint len, const void *data)
{
elem->update_sub(start, len, data);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2013 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BKE_material.hh"
#include "GPU_batch.hh"
#include "GPU_init_exit.hh" /* interface */
#include "GPU_pass.hh"
#include "intern/gpu_private.hh"
#include "intern/gpu_shader_create_info_private.hh"
#include "intern/gpu_shader_dependency_private.hh"
namespace blender {
/**
* although the order of initialization and shutdown should not matter
* (except for the extensions), I chose alphabetical and reverse alphabetical order
*/
static bool initialized = false;
void GPU_init()
{
/* can't avoid calling this multiple times, see wm_window_ghostwindow_add */
if (initialized) {
return;
}
initialized = true;
gpu_backend_init_resources();
gpu_shader_dependency_init();
gpu_shader_create_info_init();
GPU_shader_builtin_warm_up();
GPU_pass_cache_init();
gpu_batch_init();
}
void GPU_exit()
{
gpu_batch_exit();
GPU_pass_cache_free();
BKE_material_defaults_free_gpu();
GPU_shader_free_builtin_shaders();
gpu_backend_delete_resources();
gpu_shader_dependency_exit();
gpu_shader_create_info_exit();
initialized = false;
}
bool GPU_is_init()
{
return initialized;
}
} // namespace blender

View File

@@ -0,0 +1,600 @@
/* SPDX-FileCopyrightText: 2006 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Manages materials, lights and textures.
*/
#include <cstring>
#include "BKE_lib_id.hh"
#include "MEM_guardedalloc.h"
#include "DNA_material_types.h"
#include "DNA_scene_types.h"
#include "DNA_world_types.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BLI_time.h"
#include "BLI_utildefines.h"
#include "BKE_main.hh"
#include "BKE_material.hh"
#include "BKE_node.hh"
#include "BKE_node_runtime.hh"
#include "NOD_shader.h"
#include "NOD_shader_nodes_inline.hh"
#include "GPU_material.hh"
#include "GPU_pass.hh"
#include "GPU_shader.hh"
#include "GPU_texture.hh"
#include "GPU_uniform_buffer.hh"
#include "DRW_engine.hh"
#include "gpu_node_graph.hh"
#include "atomic_ops.h"
namespace blender {
static void gpu_material_ramp_texture_build(GPUMaterial *mat);
static void gpu_material_sky_texture_build(GPUMaterial *mat);
/* Structs */
#define MAX_COLOR_BAND 128
#define MAX_GPU_SKIES 8
struct GPUColorBandBuilder {
float pixels[MAX_COLOR_BAND][CM_TABLE + 1][4];
int current_layer;
};
struct GPUSkyBuilder {
float pixels[MAX_GPU_SKIES][GPU_SKY_WIDTH * GPU_SKY_HEIGHT][4];
int current_layer;
};
struct GPUMaterial {
/* Contains #gpu::Shader and source code for deferred compilation.
* Can be shared between materials sharing same node-tree topology. */
GPUPass *pass = nullptr;
/* Optimized GPUPass, situationally compiled after initial pass for optimal realtime performance.
* This shader variant bakes dynamic uniform data as constant. This variant will not use
* the ubo, and instead bake constants directly into the shader source. */
GPUPass *optimized_pass = nullptr;
/* UBOs for this material parameters. */
gpu::UniformBuf *ubo = nullptr;
/* Some flags about the nodetree & the needed resources. */
eGPUMaterialFlag flag = GPU_MATFLAG_UPDATED;
/* The engine type this material is compiled for. */
eGPUMaterialEngine engine;
/* Identify shader variations (shadow, probe, world background...) */
uint64_t uuid = 0;
/* Number of generated function. */
int generated_function_len = 0;
/* Source material, might be null. */
Material *source_material = nullptr;
/* 1D Texture array containing all color bands. */
gpu::Texture *coba_tex = nullptr;
/* Builder for coba_tex. */
GPUColorBandBuilder *coba_builder = nullptr;
/* 2D Texture array containing all sky textures. */
gpu::Texture *sky_tex = nullptr;
/* Builder for sky_tex. */
GPUSkyBuilder *sky_builder = nullptr;
/* Low level node graph(s). Also contains resources needed by the material. */
GPUNodeGraph graph = {};
bool has_surface_output = false;
bool has_volume_output = false;
bool has_displacement_output = false;
std::string name;
GPUMaterial(eGPUMaterialEngine engine) : engine(engine) {};
~GPUMaterial()
{
gpu_node_graph_free(&graph);
if (optimized_pass != nullptr) {
GPU_pass_release(optimized_pass);
}
if (pass != nullptr) {
GPU_pass_release(pass);
}
if (ubo != nullptr) {
GPU_uniformbuf_free(ubo);
}
if (coba_builder != nullptr) {
MEM_delete(coba_builder);
}
if (coba_tex != nullptr) {
GPU_texture_free(coba_tex);
}
if (sky_tex != nullptr) {
GPU_texture_free(sky_tex);
}
}
};
/* Public API */
GPUMaterialFromNodeTreeResult GPU_material_from_nodetree(
Material *ma,
bNodeTree *ntree,
ListBaseT<LinkData> *gpumaterials,
const char *name,
eGPUMaterialEngine engine,
uint64_t shader_uuid,
bool deferred_compilation,
GPUCodegenCallbackFn callback,
void *thunk,
GPUMaterialPassReplacementCallbackFn pass_replacement_cb)
{
/* Search if this material is not already compiled. */
for (LinkData &link : *gpumaterials) {
GPUMaterial *mat = static_cast<GPUMaterial *>(link.data);
if (mat->uuid == shader_uuid && mat->engine == engine) {
if (!deferred_compilation) {
GPU_pass_ensure_its_ready(mat->pass);
}
return {mat};
}
}
GPUMaterialFromNodeTreeResult result;
GPUMaterial *mat = MEM_new<GPUMaterial>(__func__, engine);
mat->source_material = ma;
mat->uuid = shader_uuid;
mat->name = name;
result.material = mat;
/* Localize tree to create links for reroute and mute. */
bNodeTree *localtree = bke::node_tree_add_tree(
nullptr, (StringRef(ntree->id.name) + " Inlined").c_str(), ntree->idname);
localtree->flag |= NTREE_IS_GPU_SHADER_INTERNAL;
nodes::InlineShaderNodeTreeParams inline_params;
inline_params.allow_preserving_repeat_zones = true;
inline_params.target_engine_ = engine == GPU_MAT_EEVEE ? SHD_OUTPUT_EEVEE : SHD_OUTPUT_ALL;
nodes::inline_shader_node_tree(*ntree, *localtree, inline_params);
for (nodes::InlineShaderNodeTreeParams::ErrorMessage &error : inline_params.r_error_messages) {
result.errors.append({error.node, std::move(error.message)});
}
ntreeGPUMaterialNodes(localtree, mat);
gpu_material_ramp_texture_build(mat);
gpu_material_sky_texture_build(mat);
/* Use default material pass when possible. */
if (GPUPass *default_pass = pass_replacement_cb ? pass_replacement_cb(thunk, mat) : nullptr) {
mat->pass = default_pass;
GPU_pass_acquire(mat->pass);
/** WORKAROUND:
* The node tree code is never executed in default replaced passes,
* but the GPU validation will still complain if the node tree UBO is not bound.
* So we create a dummy UBO with (at least) the size of the default material one (192 bytes).
* We allocate 256 bytes to leave some room for future changes. */
mat->ubo = GPU_uniformbuf_create_ex(256, nullptr, "Dummy UBO");
}
else {
/* Create source code and search pass cache for an already compiled version. */
mat->pass = GPU_generate_pass(
mat, &mat->graph, mat->name.c_str(), engine, deferred_compilation, callback, thunk, false);
}
/* Determine whether we should generate an optimized variant of the graph.
* Heuristic is based on complexity of default material pass and shader node graph. */
if (GPU_pass_should_optimize(mat->pass)) {
mat->optimized_pass = GPU_generate_pass(
mat, &mat->graph, mat->name.c_str(), engine, true, callback, thunk, true);
}
gpu_node_graph_free_nodes(&mat->graph);
/* Only free after GPU_pass_shader_get where gpu::UniformBuf read data from the local
* tree. */
BKE_id_free(nullptr, &localtree->id);
/* Note that even if building the shader fails in some way, we want to keep
* it to avoid trying to compile again and again, and simply do not use
* the actual shader on drawing. */
LinkData *link = MEM_new_zeroed<LinkData>("GPUMaterialLink");
link->data = mat;
BLI_addtail(gpumaterials, link);
return result;
}
GPUMaterial *GPU_material_from_callbacks(eGPUMaterialEngine engine,
ConstructGPUMaterialFn construct_function_cb,
GPUCodegenCallbackFn generate_code_function_cb,
void *thunk)
{
/* Allocate a new material and its material graph. */
GPUMaterial *material = MEM_new<GPUMaterial>(__func__, engine);
/* Construct the material graph by adding and linking the necessary GPU material nodes. */
construct_function_cb(thunk, material);
/* Create and initialize the texture storing color bands used by Ramp and Curve nodes. */
gpu_material_ramp_texture_build(material);
/* Lookup an existing pass in the cache or generate a new one. */
material->pass = GPU_generate_pass(material,
&material->graph,
__func__,
engine,
false,
generate_code_function_cb,
thunk,
false);
/* Determine whether we should generate an optimized variant of the graph.
* Heuristic is based on complexity of default material pass and shader node graph. */
if (GPU_pass_should_optimize(material->pass)) {
material->optimized_pass = GPU_generate_pass(material,
&material->graph,
__func__,
engine,
true,
generate_code_function_cb,
thunk,
true);
}
gpu_node_graph_free_nodes(&material->graph);
return material;
}
void GPU_material_free_single(GPUMaterial *material)
{
MEM_delete(material);
}
void GPU_material_free(ListBaseT<LinkData> *gpumaterial)
{
for (LinkData &link : *gpumaterial) {
GPUMaterial *material = static_cast<GPUMaterial *>(link.data);
GPU_material_free_single(material);
}
gpumaterial->free_no_destruct();
}
void GPU_materials_free(Main *bmain)
{
for (Material &ma : bmain->materials) {
GPU_material_free(&ma.gpumaterial);
}
for (World &wo : bmain->worlds) {
GPU_material_free(&wo.gpumaterial);
}
BKE_material_defaults_free_gpu();
}
const char *GPU_material_get_name(GPUMaterial *material)
{
return material->name.c_str();
}
uint64_t GPU_material_uuid_get(GPUMaterial *mat)
{
return mat->uuid;
}
Material *GPU_material_get_material(GPUMaterial *material)
{
return material->source_material;
}
GPUPass *GPU_material_get_pass(GPUMaterial *material)
{
/* If an optimized pass variant is available, and optimization is
* flagged as complete, we use this one instead. */
return GPU_material_optimization_status(material) == GPU_MAT_OPTIMIZATION_SUCCESS ?
material->optimized_pass :
material->pass;
}
gpu::Shader *GPU_material_get_shader(GPUMaterial *material)
{
return GPU_pass_shader_get(GPU_material_get_pass(material));
}
GPUMaterialStatus GPU_material_status(GPUMaterial *mat)
{
switch (GPU_pass_status(mat->pass)) {
case GPU_PASS_SUCCESS:
return GPU_MAT_SUCCESS;
case GPU_PASS_QUEUED:
return GPU_MAT_QUEUED;
default:
return GPU_MAT_FAILED;
}
}
eGPUMaterialOptimizationStatus GPU_material_optimization_status(GPUMaterial *mat)
{
if (!GPU_pass_should_optimize(mat->pass)) {
return GPU_MAT_OPTIMIZATION_SKIP;
}
switch (GPU_pass_status(mat->optimized_pass)) {
case GPU_PASS_SUCCESS:
return GPU_MAT_OPTIMIZATION_SUCCESS;
case GPU_PASS_QUEUED:
return GPU_MAT_OPTIMIZATION_QUEUED;
default:
BLI_assert_unreachable();
return GPU_MAT_OPTIMIZATION_SKIP;
}
}
uint64_t GPU_material_compilation_timestamp(GPUMaterial *mat)
{
return GPU_pass_compilation_timestamp(mat->pass);
}
bool GPU_material_has_surface_output(GPUMaterial *mat)
{
return mat->has_surface_output;
}
bool GPU_material_has_volume_output(GPUMaterial *mat)
{
return mat->has_volume_output;
}
bool GPU_material_has_displacement_output(GPUMaterial *mat)
{
return mat->has_displacement_output;
}
bool GPU_material_flag_get(const GPUMaterial *mat, eGPUMaterialFlag flag)
{
return (mat->flag & flag) != 0;
}
eGPUMaterialFlag GPU_material_flag(const GPUMaterial *mat)
{
return mat->flag;
}
void GPU_material_flag_set(GPUMaterial *mat, eGPUMaterialFlag flag)
{
if ((flag & GPU_MATFLAG_GLOSSY) && (mat->flag & GPU_MATFLAG_GLOSSY)) {
/* Tag material using multiple glossy BSDF as using clear coat. */
mat->flag |= GPU_MATFLAG_COAT;
}
mat->flag |= flag;
}
void GPU_material_uniform_buffer_create(GPUMaterial *material, ListBaseT<LinkData> *inputs)
{
material->ubo = GPU_uniformbuf_create_from_list(inputs, material->name.c_str());
}
gpu::UniformBuf *GPU_material_uniform_buffer_get(GPUMaterial *material)
{
return material->ubo;
}
ListBaseT<GPUMaterialAttribute> GPU_material_attributes(const GPUMaterial *material)
{
return material->graph.attributes;
}
ListBaseT<GPUMaterialTexture> GPU_material_textures(GPUMaterial *material)
{
return material->graph.textures;
}
const GPUUniformAttrList *GPU_material_uniform_attributes(const GPUMaterial *material)
{
const GPUUniformAttrList *attrs = &material->graph.uniform_attrs;
return attrs->count > 0 ? attrs : nullptr;
}
const ListBaseT<GPULayerAttr> *GPU_material_layer_attributes(const GPUMaterial *material)
{
const ListBaseT<GPULayerAttr> *attrs = &material->graph.layer_attrs;
return !attrs->is_empty() ? attrs : nullptr;
}
GPUNodeGraph *gpu_material_node_graph(GPUMaterial *material)
{
return &material->graph;
}
/* Resources */
gpu::Texture **gpu_material_sky_texture_layer_set(
GPUMaterial *mat, int width, int height, const float *pixels, float *row)
{
/* In order to put all sky textures into one 2D array texture,
* we need them to be the same size. */
BLI_assert(width == GPU_SKY_WIDTH);
BLI_assert(height == GPU_SKY_HEIGHT);
UNUSED_VARS_NDEBUG(width, height);
if (mat->sky_builder == nullptr) {
mat->sky_builder = MEM_new_uninitialized<GPUSkyBuilder>("GPUSkyBuilder");
mat->sky_builder->current_layer = 0;
}
int layer = mat->sky_builder->current_layer;
*row = float(layer);
if (*row == MAX_GPU_SKIES) {
printf("Too many sky textures in shader!\n");
}
else {
float *dst = reinterpret_cast<float *>(mat->sky_builder->pixels[layer]);
memcpy(dst, pixels, sizeof(float) * GPU_SKY_WIDTH * GPU_SKY_HEIGHT * 4);
mat->sky_builder->current_layer += 1;
}
return &mat->sky_tex;
}
gpu::Texture **gpu_material_ramp_texture_row_set(GPUMaterial *mat,
int size,
const float *pixels,
float *r_row)
{
/* In order to put all the color-bands into one 1D array texture,
* we need them to be the same size. */
BLI_assert(size == CM_TABLE + 1);
UNUSED_VARS_NDEBUG(size);
if (mat->coba_builder == nullptr) {
mat->coba_builder = MEM_new_uninitialized<GPUColorBandBuilder>("GPUColorBandBuilder");
mat->coba_builder->current_layer = 0;
}
int layer = mat->coba_builder->current_layer;
*r_row = float(layer);
if (*r_row == MAX_COLOR_BAND) {
printf("Too many color band in shader! Remove some Curve, Black Body or Color Ramp Node.\n");
}
else {
float *dst = reinterpret_cast<float *>(mat->coba_builder->pixels[layer]);
memcpy(dst, pixels, sizeof(float) * (CM_TABLE + 1) * 4);
mat->coba_builder->current_layer += 1;
}
return &mat->coba_tex;
}
static void gpu_material_ramp_texture_build(GPUMaterial *mat)
{
if (mat->coba_builder == nullptr) {
return;
}
GPUColorBandBuilder *builder = mat->coba_builder;
mat->coba_tex = GPU_texture_create_1d_array("mat_ramp",
CM_TABLE + 1,
builder->current_layer,
1,
gpu::TextureFormat::SFLOAT_16_16_16_16,
GPU_TEXTURE_USAGE_SHADER_READ,
reinterpret_cast<float *>(builder->pixels));
MEM_delete(builder);
mat->coba_builder = nullptr;
}
static void gpu_material_sky_texture_build(GPUMaterial *mat)
{
if (mat->sky_builder == nullptr) {
return;
}
mat->sky_tex = GPU_texture_create_2d_array("mat_sky",
GPU_SKY_WIDTH,
GPU_SKY_HEIGHT,
mat->sky_builder->current_layer,
1,
gpu::TextureFormat::SFLOAT_32_32_32_32,
GPU_TEXTURE_USAGE_SHADER_READ,
reinterpret_cast<float *>(mat->sky_builder->pixels));
MEM_delete(mat->sky_builder);
mat->sky_builder = nullptr;
}
/* Code generation */
void GPU_material_output_surface(GPUMaterial *material, GPUNodeLink *link)
{
if (!material->graph.outlink_surface) {
material->graph.outlink_surface = link;
material->has_surface_output = true;
}
}
void GPU_material_output_volume(GPUMaterial *material, GPUNodeLink *link)
{
if (!material->graph.outlink_volume) {
material->graph.outlink_volume = link;
material->has_volume_output = true;
}
}
void GPU_material_output_displacement(GPUMaterial *material, GPUNodeLink *link)
{
if (!material->graph.outlink_displacement) {
material->graph.outlink_displacement = link;
material->has_displacement_output = true;
}
}
void GPU_material_output_thickness(GPUMaterial *material, GPUNodeLink *link)
{
if (!material->graph.outlink_thickness) {
material->graph.outlink_thickness = link;
}
}
void GPU_material_add_output_link_aov(GPUMaterial *material, GPUNodeLink *link, int hash)
{
GPUNodeGraphOutputLink *aov_link = MEM_new_zeroed<GPUNodeGraphOutputLink>(__func__);
aov_link->outlink = link;
aov_link->hash = hash;
BLI_addtail(&material->graph.outlink_aovs, aov_link);
}
void GPU_material_add_output_link_composite(GPUMaterial *material, GPUNodeLink *link)
{
GPUNodeGraphOutputLink *compositor_link = MEM_new_zeroed<GPUNodeGraphOutputLink>(__func__);
compositor_link->outlink = link;
BLI_addtail(&material->graph.outlink_compositor, compositor_link);
}
char *GPU_material_split_sub_function(GPUMaterial *material,
GPUType return_type,
GPUNodeLink **link)
{
/* Force cast to return type. */
switch (return_type) {
case GPU_FLOAT:
GPU_link(material, "set_value", *link, link);
break;
case GPU_VEC3:
GPU_link(material, "set_rgb", *link, link);
break;
case GPU_VEC4:
GPU_link(material, "set_rgba", *link, link);
break;
default:
BLI_assert(0);
break;
}
GPUNodeGraphFunctionLink *func_link = MEM_new_zeroed<GPUNodeGraphFunctionLink>(__func__);
func_link->outlink = *link;
SNPRINTF(func_link->name, "ntree_fn%d", material->generated_function_len++);
BLI_addtail(&material->graph.material_functions, func_link);
return func_link->name;
}
} // namespace blender

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Parsing of and code generation using GLSL shaders in gpu/shaders/material. */
#pragma once
#include "BLI_set.hh"
#include "GPU_material.hh"
namespace blender {
#define MAX_FUNCTION_NAME 64
#define MAX_PARAMETER 36
enum GPUFunctionQual {
FUNCTION_QUAL_IN,
FUNCTION_QUAL_OUT,
FUNCTION_QUAL_INOUT,
};
struct GPUFunction {
char name[MAX_FUNCTION_NAME];
GPUType paramtype[MAX_PARAMETER];
GPUFunctionQual paramqual[MAX_PARAMETER];
int totparam;
/* TODO(@fclem): Clean that void pointer. */
void *source; /* GPUSource */
};
GPUFunction *gpu_material_library_get_function(const char *name);
void gpu_material_library_use_function(Set<StringRefNull> &used_libraries, const char *name);
} // namespace blender

View File

@@ -0,0 +1,753 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "gpu_context_private.hh"
#include "gpu_matrix_private.hh"
#define SUPPRESS_GENERIC_MATRIX_API
#define USE_GPU_PY_MATRIX_API /* only so values are declared */
#include "GPU_matrix.hh"
#undef USE_GPU_PY_MATRIX_API
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "MEM_guardedalloc.h"
namespace blender {
using namespace blender::gpu;
constexpr static int MATRIX_STACK_DEPTH = 32;
using Mat4 = float[4][4];
using Mat3 = float[3][3];
struct MatrixStack {
Mat4 stack[MATRIX_STACK_DEPTH];
uint top;
};
struct GPUMatrixState {
MatrixStack model_view_stack;
MatrixStack projection_stack;
bool dirty;
/* TODO: cache of derived matrices (Normal, MVP, inverse MVP, etc)
* generate as needed for shaders, invalidate when original matrices change
*
* TODO: separate Model from View transform? Batches/objects have model,
* camera/eye has view & projection
*/
};
#define ModelViewStack Context::get()->matrix_state->model_view_stack
#define ModelView ModelViewStack.stack[ModelViewStack.top]
#define ProjectionStack Context::get()->matrix_state->projection_stack
#define Projection ProjectionStack.stack[ProjectionStack.top]
GPUMatrixState *GPU_matrix_state_create()
{
#define MATRIX_4X4_IDENTITY \
{ \
{1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}, \
{0.0f, 0.0f, 0.0f, 1.0f} \
}
GPUMatrixState *state = MEM_new_uninitialized<GPUMatrixState>(__func__);
const MatrixStack identity_stack = {{MATRIX_4X4_IDENTITY}, 0};
state->model_view_stack = state->projection_stack = identity_stack;
state->dirty = true;
#undef MATRIX_4X4_IDENTITY
return state;
}
void GPU_matrix_state_discard(GPUMatrixState *state)
{
MEM_delete(state);
}
static void gpu_matrix_state_active_set_dirty(bool value)
{
GPUMatrixState *state = Context::get()->matrix_state;
state->dirty = value;
}
void GPU_matrix_reset()
{
GPUMatrixState *state = Context::get()->matrix_state;
state->model_view_stack.top = 0;
state->projection_stack.top = 0;
unit_m4(ModelView);
unit_m4(Projection);
gpu_matrix_state_active_set_dirty(true);
}
#ifdef WITH_GPU_SAFETY
/* Check if matrix is numerically good */
static void checkmat(cosnt float *m)
{
const int n = 16;
for (int i = 0; i < n; i++) {
# if _MSC_VER
BLI_assert(_finite(m[i]));
# else
BLI_assert(!isinf(m[i]));
# endif
}
}
# define CHECKMAT(m) checkmat((const float *)m)
#else
# define CHECKMAT(m)
#endif
void GPU_matrix_push()
{
BLI_assert(ModelViewStack.top + 1 < MATRIX_STACK_DEPTH);
ModelViewStack.top++;
copy_m4_m4(ModelView, ModelViewStack.stack[ModelViewStack.top - 1]);
}
void GPU_matrix_pop()
{
BLI_assert(ModelViewStack.top > 0);
ModelViewStack.top--;
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_push_projection()
{
BLI_assert(ProjectionStack.top + 1 < MATRIX_STACK_DEPTH);
ProjectionStack.top++;
copy_m4_m4(Projection, ProjectionStack.stack[ProjectionStack.top - 1]);
}
void GPU_matrix_pop_projection()
{
BLI_assert(ProjectionStack.top > 0);
ProjectionStack.top--;
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_set(const float m[4][4])
{
copy_m4_m4(ModelView, m);
CHECKMAT(ModelView3D);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_identity_projection_set()
{
unit_m4(Projection);
CHECKMAT(Projection3D);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_projection_set(const float m[4][4])
{
copy_m4_m4(Projection, m);
CHECKMAT(Projection3D);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_identity_set()
{
unit_m4(ModelView);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_translate_2f(float x, float y)
{
Mat4 m;
unit_m4(m);
m[3][0] = x;
m[3][1] = y;
GPU_matrix_mul(m);
}
void GPU_matrix_translate_2fv(const float vec[2])
{
GPU_matrix_translate_2f(vec[0], vec[1]);
}
void GPU_matrix_translate_3f(float x, float y, float z)
{
#if 1
translate_m4(ModelView, x, y, z);
CHECKMAT(ModelView);
#else /* above works well in early testing, below is generic version */
Mat4 m;
unit_m4(m);
m[3][0] = x;
m[3][1] = y;
m[3][2] = z;
GPU_matrix_mul(m);
#endif
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_translate_3fv(const float vec[3])
{
GPU_matrix_translate_3f(vec[0], vec[1], vec[2]);
}
void GPU_matrix_scale_1f(float factor)
{
Mat4 m;
scale_m4_fl(m, factor);
GPU_matrix_mul(m);
}
void GPU_matrix_scale_2f(float x, float y)
{
Mat4 m = {{0.0f}};
m[0][0] = x;
m[1][1] = y;
m[2][2] = 1.0f;
m[3][3] = 1.0f;
GPU_matrix_mul(m);
}
void GPU_matrix_scale_2fv(const float vec[2])
{
GPU_matrix_scale_2f(vec[0], vec[1]);
}
void GPU_matrix_scale_3f(float x, float y, float z)
{
Mat4 m = {{0.0f}};
m[0][0] = x;
m[1][1] = y;
m[2][2] = z;
m[3][3] = 1.0f;
GPU_matrix_mul(m);
}
void GPU_matrix_scale_3fv(const float vec[3])
{
GPU_matrix_scale_3f(vec[0], vec[1], vec[2]);
}
void GPU_matrix_mul(const float m[4][4])
{
mul_m4_m4_post(ModelView, m);
CHECKMAT(ModelView);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_rotate_2d(float deg)
{
/* essentially RotateAxis('Z')
* TODO: simpler math for 2D case
*/
rotate_m4(ModelView, 'Z', DEG2RADF(deg));
}
void GPU_matrix_rotate_3f(float deg, float x, float y, float z)
{
const float axis[3] = {x, y, z};
GPU_matrix_rotate_3fv(deg, axis);
}
void GPU_matrix_rotate_3fv(float deg, const float axis[3])
{
Mat4 m;
axis_angle_to_mat4(m, axis, DEG2RADF(deg));
GPU_matrix_mul(m);
}
void GPU_matrix_rotate_axis(float deg, char axis)
{
/* rotate_m4 works in place */
rotate_m4(ModelView, axis, DEG2RADF(deg));
CHECKMAT(ModelView);
gpu_matrix_state_active_set_dirty(true);
}
static void mat4_ortho_set(
float m[4][4], float left, float right, float bottom, float top, float near, float far)
{
m[0][0] = 2.0f / (right - left);
m[1][0] = 0.0f;
m[2][0] = 0.0f;
m[3][0] = -(right + left) / (right - left);
m[0][1] = 0.0f;
m[1][1] = 2.0f / (top - bottom);
m[2][1] = 0.0f;
m[3][1] = -(top + bottom) / (top - bottom);
m[0][2] = 0.0f;
m[1][2] = 0.0f;
m[2][2] = -2.0f / (far - near);
m[3][2] = -(far + near) / (far - near);
m[0][3] = 0.0f;
m[1][3] = 0.0f;
m[2][3] = 0.0f;
m[3][3] = 1.0f;
gpu_matrix_state_active_set_dirty(true);
}
static void mat4_frustum_set(
float m[4][4], float left, float right, float bottom, float top, float near, float far)
{
m[0][0] = 2.0f * near / (right - left);
m[1][0] = 0.0f;
m[2][0] = (right + left) / (right - left);
m[3][0] = 0.0f;
m[0][1] = 0.0f;
m[1][1] = 2.0f * near / (top - bottom);
m[2][1] = (top + bottom) / (top - bottom);
m[3][1] = 0.0f;
m[0][2] = 0.0f;
m[1][2] = 0.0f;
m[2][2] = -(far + near) / (far - near);
m[3][2] = -2.0f * far * near / (far - near);
m[0][3] = 0.0f;
m[1][3] = 0.0f;
m[2][3] = -1.0f;
m[3][3] = 0.0f;
gpu_matrix_state_active_set_dirty(true);
}
static void mat4_look_from_origin(float m[4][4], float lookdir[3], float camup[3])
{
/* This function is loosely based on Mesa implementation.
*
* SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
* Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice including the dates of first publication and
* either this permission notice or a reference to
* http://oss.sgi.com/projects/FreeB/
* shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of Silicon Graphics, Inc.
* shall not be used in advertising or otherwise to promote the sale, use or
* other dealings in this Software without prior written authorization from
* Silicon Graphics, Inc.
*/
float side[3];
normalize_v3(lookdir);
cross_v3_v3v3(side, lookdir, camup);
normalize_v3(side);
cross_v3_v3v3(camup, side, lookdir);
m[0][0] = side[0];
m[1][0] = side[1];
m[2][0] = side[2];
m[3][0] = 0.0f;
m[0][1] = camup[0];
m[1][1] = camup[1];
m[2][1] = camup[2];
m[3][1] = 0.0f;
m[0][2] = -lookdir[0];
m[1][2] = -lookdir[1];
m[2][2] = -lookdir[2];
m[3][2] = 0.0f;
m[0][3] = 0.0f;
m[1][3] = 0.0f;
m[2][3] = 0.0f;
m[3][3] = 1.0f;
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_ortho_set(float left, float right, float bottom, float top, float near, float far)
{
mat4_ortho_set(Projection, left, right, bottom, top, near, far);
CHECKMAT(Projection);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_ortho_set_z(float near, float far)
{
CHECKMAT(Projection);
Projection[2][2] = -2.0f / (far - near);
Projection[3][2] = -(far + near) / (far - near);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_ortho_2d_set(float left, float right, float bottom, float top)
{
Mat4 m;
mat4_ortho_set(m, left, right, bottom, top, -1.0f, 1.0f);
CHECKMAT(Projection2D);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_frustum_set(
float left, float right, float bottom, float top, float near, float far)
{
mat4_frustum_set(Projection, left, right, bottom, top, near, far);
CHECKMAT(Projection);
gpu_matrix_state_active_set_dirty(true);
}
void GPU_matrix_perspective_set(float fovy, float aspect, float near, float far)
{
float half_height = tanf(fovy * float(M_PI / 360.0)) * near;
float half_width = half_height * aspect;
GPU_matrix_frustum_set(-half_width, +half_width, -half_height, +half_height, near, far);
}
void GPU_matrix_look_at(float eyeX,
float eyeY,
float eyeZ,
float centerX,
float centerY,
float centerZ,
float upX,
float upY,
float upZ)
{
Mat4 cm;
float lookdir[3];
float camup[3] = {upX, upY, upZ};
lookdir[0] = centerX - eyeX;
lookdir[1] = centerY - eyeY;
lookdir[2] = centerZ - eyeZ;
mat4_look_from_origin(cm, lookdir, camup);
GPU_matrix_mul(cm);
GPU_matrix_translate_3f(-eyeX, -eyeY, -eyeZ);
}
void GPU_matrix_project_3fv(const float world[3],
const float model[4][4],
const float proj[4][4],
const int view[4],
float r_win[3])
{
float v[4];
mul_v4_m4v3(v, model, world);
mul_m4_v4(proj, v);
if (v[3] != 0.0f) {
mul_v3_fl(v, 1.0f / v[3]);
}
r_win[0] = view[0] + (view[2] * (v[0] + 1)) * 0.5f;
r_win[1] = view[1] + (view[3] * (v[1] + 1)) * 0.5f;
r_win[2] = (v[2] + 1) * 0.5f;
}
void GPU_matrix_project_2fv(const float world[3],
const float model[4][4],
const float proj[4][4],
const int view[4],
float r_win[2])
{
float v[4];
mul_v4_m4v3(v, model, world);
mul_m4_v4(proj, v);
if (v[3] != 0.0f) {
mul_v2_fl(v, 1.0f / v[3]);
}
r_win[0] = view[0] + (view[2] * (v[0] + 1)) * 0.5f;
r_win[1] = view[1] + (view[3] * (v[1] + 1)) * 0.5f;
}
bool GPU_matrix_unproject_3fv(const float win[3],
const float model_inverted[4][4],
const float proj[4][4],
const int view[4],
float r_world[3])
{
zero_v3(r_world);
const float in[3] = {
2 * ((win[0] - view[0]) / view[2]) - 1.0f,
2 * ((win[1] - view[1]) / view[3]) - 1.0f,
2 * win[2] - 1.0f,
};
/**
* The same result could be obtained as follows:
*
* \code{.c}
* float projinv[4][4];
* invert_m4_m4(projinv, projview);
* copy_v3_v3(r_world, in);
* mul_project_m4_v3(projinv, r_world);
* \endcode
*
* But that solution loses much precision.
* Therefore, get the same result without inverting the project view matrix.
*/
float out[3];
const bool is_persp = proj[3][3] == 0.0f;
if (is_persp) {
out[2] = proj[3][2] / (proj[2][2] + in[2]);
if (isinf(out[2])) {
out[2] = FLT_MAX;
}
out[0] = out[2] * ((proj[2][0] + in[0]) / proj[0][0]);
out[1] = out[2] * ((proj[2][1] + in[1]) / proj[1][1]);
out[2] *= -1;
}
else {
out[0] = (-proj[3][0] + in[0]) / proj[0][0];
out[1] = (-proj[3][1] + in[1]) / proj[1][1];
out[2] = (-proj[3][2] + in[2]) / proj[2][2];
}
if (!is_finite_v3(out)) {
return false;
}
mul_v3_m4v3(r_world, model_inverted, out);
return true;
}
const float (*GPU_matrix_model_view_get(float m[4][4]))[4]
{
if (m) {
copy_m4_m4(m, ModelView);
return m;
}
return ModelView;
}
const float (*GPU_matrix_projection_get(float m[4][4]))[4]
{
if (m) {
copy_m4_m4(m, Projection);
return m;
}
return Projection;
}
const float (*GPU_matrix_model_view_projection_get(float m[4][4]))[4]
{
if (m == nullptr) {
static Mat4 temp;
m = temp;
}
mul_m4_m4m4(m, Projection, ModelView);
return m;
}
const float (*GPU_matrix_normal_get(float m[3][3]))[3]
{
if (m == nullptr) {
static Mat3 temp3;
m = temp3;
}
copy_m3_m4(m, GPU_matrix_model_view_get(nullptr));
invert_m3(m);
transpose_m3(m);
return m;
}
const float (*GPU_matrix_normal_inverse_get(float m[3][3]))[3]
{
if (m == nullptr) {
static Mat3 temp3;
m = temp3;
}
GPU_matrix_normal_get(m);
invert_m3(m);
return m;
}
void GPU_matrix_bind(gpu::Shader *shader)
{
/* set uniform values to matrix stack values
* call this before a draw call if desired matrices are dirty
* call glUseProgram before this, as glUniform expects program to be bound
*/
int32_t MV = GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_MODELVIEW);
int32_t P = GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_PROJECTION);
int32_t MVP = GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_MVP);
int32_t N = GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_NORMAL);
int32_t MV_inv = GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_MODELVIEW_INV);
int32_t P_inv = GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_PROJECTION_INV);
if (MV != -1) {
GPU_shader_uniform_float_ex(
shader, MV, 16, 1, reinterpret_cast<const float *>(GPU_matrix_model_view_get(nullptr)));
}
if (P != -1) {
GPU_shader_uniform_float_ex(
shader, P, 16, 1, reinterpret_cast<const float *>(GPU_matrix_projection_get(nullptr)));
}
if (MVP != -1) {
GPU_shader_uniform_float_ex(
shader,
MVP,
16,
1,
reinterpret_cast<const float *>(GPU_matrix_model_view_projection_get(nullptr)));
}
if (N != -1) {
GPU_shader_uniform_float_ex(
shader, N, 9, 1, reinterpret_cast<const float *>(GPU_matrix_normal_get(nullptr)));
}
if (MV_inv != -1) {
Mat4 m;
GPU_matrix_model_view_get(m);
invert_m4(m);
GPU_shader_uniform_float_ex(shader, MV_inv, 16, 1, reinterpret_cast<const float *>(m));
}
if (P_inv != -1) {
Mat4 m;
GPU_matrix_projection_get(m);
invert_m4(m);
GPU_shader_uniform_float_ex(shader, P_inv, 16, 1, reinterpret_cast<const float *>(m));
}
gpu_matrix_state_active_set_dirty(false);
}
bool GPU_matrix_dirty_get()
{
GPUMatrixState *state = Context::get()->matrix_state;
return state->dirty;
}
/* -------------------------------------------------------------------- */
/** \name Python API Helpers
* \{ */
BLI_STATIC_ASSERT(GPU_PY_MATRIX_STACK_LEN + 1 == MATRIX_STACK_DEPTH, "define mismatch");
/* Return int since caller is may subtract. */
int GPU_matrix_stack_level_get_model_view()
{
GPUMatrixState *state = Context::get()->matrix_state;
return int(state->model_view_stack.top);
}
int GPU_matrix_stack_level_get_projection()
{
GPUMatrixState *state = Context::get()->matrix_state;
return int(state->projection_stack.top);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Polygon Offset Hack
*
* Workaround the fact that polygon-offset is implementation dependent.
* We modify the projection matrix \a winmat in order to change the final depth a tiny amount.
* \{ */
float GPU_polygon_offset_calc(const float (*winmat)[4], float viewdist, float dist)
{
/* Seems like we have a factor of 2 more offset than 2.79 for some reason. Correct for this. */
dist *= 0.5f;
if (winmat[3][3] > 0.5f) {
#if 1
return 0.00001f * dist * viewdist; // ortho tweaking
#else
static float depth_fac = 0.0f;
if (depth_fac == 0.0f) {
/* Hard-code for 24 bit precision. */
int depthbits = 24;
depth_fac = 1.0f / float((1 << depthbits) - 1);
}
ofs = (-1.0 / winmat[2][2]) * dist * depth_fac;
UNUSED_VARS(viewdist);
#endif
}
/* This adjustment effectively results in reducing the Z value by 0.25%.
*
* winmat[4][3] actually evaluates to `-2 * far * near / (far - near)`,
* is very close to -0.2 with default clip range,
* and is used as the coefficient multiplied by `w / z`,
* thus controlling the z dependent part of the depth value.
*/
return winmat[3][2] * -0.0025f * dist;
}
void GPU_polygon_offset(float viewdist, float dist)
{
static float winmat[4][4], offset = 0.0f;
if (dist != 0.0f) {
/* hack below is to mimic polygon offset */
GPU_matrix_projection_get(winmat);
/* dist is from camera to center point */
float ofs = GPU_polygon_offset_calc(winmat, viewdist, dist);
winmat[3][2] -= ofs;
offset += ofs;
}
else {
winmat[3][2] += offset;
offset = 0.0;
}
GPU_matrix_projection_set(winmat);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
namespace blender {
struct GPUMatrixState;
GPUMatrixState *GPU_matrix_state_create();
void GPU_matrix_state_discard(GPUMatrixState *state);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,242 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Intermediate node graph for generating GLSL shaders.
*/
#pragma once
#include "DNA_listBase.h"
#include "BLI_enum_flags.hh"
#include "GPU_material.hh"
namespace blender {
struct GPUNode;
struct GPUOutput;
struct GPUInput;
enum GPUDataSource {
GPU_SOURCE_OUTPUT,
GPU_SOURCE_CONSTANT,
GPU_SOURCE_UNIFORM,
GPU_SOURCE_ATTR,
GPU_SOURCE_UNIFORM_ATTR,
GPU_SOURCE_LAYER_ATTR,
GPU_SOURCE_STRUCT,
GPU_SOURCE_TEX,
GPU_SOURCE_TEX_TILED_MAPPING,
GPU_SOURCE_FUNCTION_CALL,
GPU_SOURCE_CRYPTOMATTE,
};
enum GPUNodeLinkType {
GPU_NODE_LINK_NONE = 0,
GPU_NODE_LINK_ATTR,
GPU_NODE_LINK_UNIFORM_ATTR,
GPU_NODE_LINK_LAYER_ATTR,
GPU_NODE_LINK_COLORBAND,
GPU_NODE_LINK_CONSTANT,
GPU_NODE_LINK_IMAGE,
GPU_NODE_LINK_IMAGE_TILED,
GPU_NODE_LINK_IMAGE_TILED_MAPPING,
GPU_NODE_LINK_IMAGE_SKY,
GPU_NODE_LINK_OUTPUT,
GPU_NODE_LINK_UNIFORM,
GPU_NODE_LINK_DIFFERENTIATE_FLOAT_FN,
};
enum GPUNodeTag {
GPU_NODE_TAG_NONE = 0,
GPU_NODE_TAG_SURFACE = (1 << 0),
GPU_NODE_TAG_VOLUME = (1 << 1),
GPU_NODE_TAG_DISPLACEMENT = (1 << 2),
GPU_NODE_TAG_THICKNESS = (1 << 3),
GPU_NODE_TAG_AOV = (1 << 4),
GPU_NODE_TAG_FUNCTION = (1 << 5),
GPU_NODE_TAG_COMPOSITOR = (1 << 6),
};
ENUM_OPERATORS(GPUNodeTag)
struct GPUNode {
GPUNode *next, *prev;
const char *name;
/* Internal flag to mark nodes during pruning */
GPUNodeTag tag;
ListBaseT<GPUInput> inputs;
ListBaseT<GPUOutput> outputs;
/* Zones. */
int zone_index;
bool is_zone_end;
};
struct GPUNodeLink {
GPUNodeStack *socket;
GPUNodeLinkType link_type;
int users; /* Refcount */
union {
/* GPU_NODE_LINK_CONSTANT | GPU_NODE_LINK_UNIFORM */
const float *data;
/* GPU_NODE_LINK_COLORBAND */
gpu::Texture **colorband;
/* GPU_NODE_LINK_OUTPUT */
GPUOutput *output;
/* GPU_NODE_LINK_ATTR */
GPUMaterialAttribute *attr;
/* GPU_NODE_LINK_UNIFORM_ATTR */
GPUUniformAttr *uniform_attr;
/* GPU_NODE_LINK_LAYER_ATTR */
GPULayerAttr *layer_attr;
/* GPU_NODE_LINK_IMAGE_BLENDER */
GPUMaterialTexture *texture;
/* GPU_NODE_LINK_DIFFERENTIATE_FLOAT_FN */
struct {
const char *function_name;
float filter_width;
} differentiate_float;
};
};
struct GPUOutput {
GPUOutput *next, *prev;
GPUNode *node;
GPUType type; /* data type = length of vector/matrix */
GPUNodeLink *link; /* output link */
int id; /* unique id as created by code generator */
/* True for Zone Items. */
bool is_zone_io;
/* This variable is shared with other socket/s and doesn't need to be declared. */
bool is_duplicate;
};
struct GPUInput {
GPUInput *next, *prev;
GPUNode *node;
GPUType type; /* data-type. */
GPUNodeLink *link;
int id; /* unique id as created by code generator */
GPUDataSource source; /* data source */
/* Content based on GPUDataSource */
union {
/* GPU_SOURCE_CONSTANT | GPU_SOURCE_UNIFORM */
float vec[16]; /* vector data */
/* GPU_SOURCE_TEX | GPU_SOURCE_TEX_TILED_MAPPING */
GPUMaterialTexture *texture;
/* GPU_SOURCE_ATTR */
GPUMaterialAttribute *attr;
/* GPU_SOURCE_UNIFORM_ATTR */
GPUUniformAttr *uniform_attr;
/* GPU_SOURCE_LAYER_ATTR */
GPULayerAttr *layer_attr;
/* GPU_SOURCE_FUNCTION_CALL */
char function_call[64];
};
/* True for Zone Items. */
bool is_zone_io;
/* This variable is shared with other socket/s and doesn't need to be declared. */
bool is_duplicate;
};
struct GPUNodeGraphOutputLink {
GPUNodeGraphOutputLink *next, *prev;
int hash;
GPUNodeLink *outlink;
};
struct GPUNodeGraphFunctionLink {
GPUNodeGraphFunctionLink *next, *prev;
char name[16];
GPUNodeLink *outlink;
};
struct GPUNodeGraph {
/* Nodes */
ListBaseT<GPUNode> nodes;
/* Main Outputs. */
GPUNodeLink *outlink_surface;
GPUNodeLink *outlink_volume;
GPUNodeLink *outlink_displacement;
GPUNodeLink *outlink_thickness;
/* List of GPUNodeGraphOutputLink */
ListBaseT<GPUNodeGraphOutputLink> outlink_aovs;
/* List of GPUNodeGraphFunctionLink */
ListBaseT<GPUNodeGraphFunctionLink> material_functions;
/* List of GPUNodeGraphOutputLink */
ListBaseT<GPUNodeGraphOutputLink> outlink_compositor;
/* Requested attributes and textures. */
ListBaseT<GPUMaterialAttribute> attributes;
ListBaseT<GPUMaterialTexture> textures;
/* The list of uniform attributes. */
GPUUniformAttrList uniform_attrs;
/* The list of layer attributes. */
ListBaseT<GPULayerAttr> layer_attrs;
};
/* Node Graph */
void gpu_nodes_tag(GPUNodeGraph *graph, GPUNodeLink *link_start, GPUNodeTag tag);
void gpu_node_graph_prune_unused(GPUNodeGraph *graph);
void gpu_node_graph_finalize_uniform_attrs(GPUNodeGraph *graph);
/**
* Optimize node graph for optimized material shader path.
* Once the base material has been generated, we can modify the shader
* node graph to create one which will produce an optimally performing shader.
* This currently involves baking uniform data into constant data to enable
* aggressive constant folding by the compiler in order to reduce complexity and
* shader core memory pressure.
*
* NOTE: Graph optimizations will produce a shader which needs to be re-compiled
* more frequently, however, the default material pass will always exist to fall
* back on. */
void gpu_node_graph_optimize(GPUNodeGraph *graph);
/**
* Free intermediate node graph.
*/
void gpu_node_graph_free_nodes(GPUNodeGraph *graph);
/**
* Free both node graph and requested attributes and textures.
*/
void gpu_node_graph_free(GPUNodeGraph *graph);
/* Material calls */
GPUNodeGraph *gpu_material_node_graph(GPUMaterial *material);
/**
* Returns the address of the future pointer to coba_tex.
*/
gpu::Texture **gpu_material_ramp_texture_row_set(GPUMaterial *mat,
int size,
const float *pixels,
float *r_row);
/**
* Returns the address of the future pointer to sky_tex
*/
gpu::Texture **gpu_material_sky_texture_layer_set(
GPUMaterial *mat, int width, int height, const float *pixels, float *row);
} // namespace blender

View File

@@ -0,0 +1,405 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Convert material node-trees to GLSL.
*/
#include "MEM_guardedalloc.h"
#include "BLI_map.hh"
#include "BLI_span.hh"
#include "BLI_time.h"
#include "BLI_vector.hh"
#include "GPU_capabilities.hh"
#include "GPU_context.hh"
#include "GPU_pass.hh"
#include "GPU_vertex_format.hh"
#include "gpu_codegen.hh"
#include <mutex>
#include <string>
namespace blender {
using namespace blender::gpu::shader;
static bool gpu_pass_validate(GPUCodegenCreateInfo *create_info);
/* -------------------------------------------------------------------- */
/** \name GPUPass
* \{ */
struct GPUPass {
static inline std::atomic<uint64_t> compilation_counts = 0;
GPUCodegenCreateInfo *create_info = nullptr;
AsyncCompilationHandle compilation_handle = 0;
std::atomic<gpu::Shader *> shader = nullptr;
std::atomic<GPUPassStatus> status = GPU_PASS_QUEUED;
/* Orphaned GPUPasses gets freed by the garbage collector. */
std::atomic<int> refcount = 1;
double creation_timestamp = 0.0f;
/* The last time the refcount was greater than 0. */
double gc_timestamp = 0.0f;
uint64_t compilation_timestamp = 0;
/** Hint that an optimized variant of this pass should be created.
* Based on a complexity heuristic from pass code generation. */
bool should_optimize = false;
bool is_optimization_pass = false;
/* Number of seconds after creation required before compiling an optimization pass. */
static constexpr float optimization_delay = 10.0f;
GPUPass(GPUCodegenCreateInfo *info,
bool deferred_compilation,
bool is_optimization_pass,
bool should_optimize)
: create_info(info),
creation_timestamp(BLI_time_now_seconds()),
should_optimize(should_optimize),
is_optimization_pass(is_optimization_pass)
{
BLI_assert(!is_optimization_pass || !should_optimize);
if (is_optimization_pass && deferred_compilation) {
// Defer until all non optimization passes are compiled.
return;
}
GPUShaderCreateInfo *base_info = reinterpret_cast<GPUShaderCreateInfo *>(create_info);
if (deferred_compilation) {
compilation_handle = GPU_shader_async_compilation(base_info, compilation_priority());
}
else {
shader = GPU_shader_create_from_info(base_info);
finalize_compilation();
}
}
~GPUPass()
{
if (compilation_handle) {
GPU_shader_async_compilation_cancel(compilation_handle);
}
else {
BLI_assert(create_info == nullptr || (is_optimization_pass && status == GPU_PASS_QUEUED));
}
MEM_delete(create_info);
GPU_SHADER_FREE_SAFE(shader);
}
CompilationPriority compilation_priority()
{
return is_optimization_pass ? CompilationPriority::Low : CompilationPriority::Medium;
}
void finalize_compilation()
{
BLI_assert_msg(create_info, "GPUPass::finalize_compilation() called more than once.");
if (compilation_handle) {
shader = GPU_shader_async_compilation_finalize(compilation_handle);
}
compilation_timestamp = ++compilation_counts;
if (!shader && !gpu_pass_validate(create_info)) {
fprintf(stderr, "gpu::Shader: error: too many samplers in shader.\n");
}
status = shader ? GPU_PASS_SUCCESS : GPU_PASS_FAILED;
MEM_delete(create_info);
create_info = nullptr;
}
void update(double timestamp)
{
update_compilation(timestamp);
update_gc_timestamp(timestamp);
}
void update_compilation(double timestamp)
{
if (compilation_handle) {
if (GPU_shader_async_compilation_is_ready(compilation_handle)) {
finalize_compilation();
}
}
else if (status == GPU_PASS_QUEUED && refcount > 0 &&
((creation_timestamp + optimization_delay) <= timestamp))
{
BLI_assert(is_optimization_pass);
GPUShaderCreateInfo *base_info = reinterpret_cast<GPUShaderCreateInfo *>(create_info);
compilation_handle = GPU_shader_async_compilation(base_info, compilation_priority());
}
}
void update_gc_timestamp(double timestamp)
{
if (refcount != 0 || gc_timestamp == 0.0f) {
gc_timestamp = timestamp;
}
}
bool should_gc(int gc_collect_rate, double timestamp)
{
BLI_assert(gc_timestamp != 0.0f);
return !compilation_handle && status != GPU_PASS_FAILED &&
(timestamp - gc_timestamp) >= gc_collect_rate;
}
};
GPUPassStatus GPU_pass_status(GPUPass *pass)
{
return pass->status;
}
bool GPU_pass_should_optimize(GPUPass *pass)
{
/* Returns optimization heuristic prepared during
* initial codegen.
* NOTE: Only enabled on Metal, since it doesn't seem to yield any performance improvements for
* other backends. */
return (GPU_backend_get_type() == GPU_BACKEND_METAL) && pass->should_optimize;
}
gpu::Shader *GPU_pass_shader_get(GPUPass *pass)
{
return pass->shader;
}
void GPU_pass_acquire(GPUPass *pass)
{
int previous_refcount = pass->refcount++;
UNUSED_VARS_NDEBUG(previous_refcount);
BLI_assert(previous_refcount > 0);
}
void GPU_pass_release(GPUPass *pass)
{
int previous_refcount = pass->refcount--;
UNUSED_VARS_NDEBUG(previous_refcount);
BLI_assert(previous_refcount > 0);
}
uint64_t GPU_pass_global_compilation_count()
{
return GPUPass::compilation_counts;
}
uint64_t GPU_pass_compilation_timestamp(GPUPass *pass)
{
return pass->compilation_timestamp;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPUPass Cache
*
* Internal shader cache: This prevent the shader recompilation / stall when
* using undo/redo AND also allows for GPUPass reuse if the Shader code is the
* same for 2 different Materials. Unused GPUPasses are free by Garbage collection.
* \{ */
class GPUPassCache {
/** Number of seconds with 0 users required before garbage collecting a pass. */
static constexpr float gc_collect_rate_ = 60.0f;
static constexpr float optimization_gc_collect_rate_ = 1.0f;
Map<uint32_t, std::unique_ptr<GPUPass>> passes_[GPU_MAT_ENGINE_MAX][2 /*is_optimization_pass*/];
std::mutex mutex_;
public:
~GPUPassCache()
{
/* Pause to prevent new compilations to start while we are cancelling them. */
GPU_shader_compiler_pause();
for (int i : IndexRange(GPU_MAT_ENGINE_MAX)) {
passes_[i][0].clear();
passes_[i][1].clear();
}
GPU_shader_compiler_resume();
}
void add(eGPUMaterialEngine engine,
GPUCodegen &codegen,
bool deferred_compilation,
bool is_optimization_pass)
{
std::lock_guard lock(mutex_);
passes_[engine][is_optimization_pass].add(
codegen.hash_get(),
std::make_unique<GPUPass>(codegen.create_info,
deferred_compilation,
is_optimization_pass,
codegen.should_optimize_heuristic()));
};
GPUPass *get(eGPUMaterialEngine engine,
size_t hash,
bool allow_deferred,
bool is_optimization_pass)
{
std::lock_guard lock(mutex_);
std::unique_ptr<GPUPass> *pass = passes_[engine][is_optimization_pass].lookup_ptr(hash);
if (!allow_deferred && pass && pass->get()->status == GPU_PASS_QUEUED) {
pass->get()->finalize_compilation();
}
return pass ? pass->get() : nullptr;
}
void update()
{
std::lock_guard lock(mutex_);
double timestamp = BLI_time_now_seconds();
/* Base Passes. */
for (auto &engine_passes : passes_) {
for (std::unique_ptr<GPUPass> &pass : engine_passes[false].values()) {
pass->update(timestamp);
}
engine_passes[false].remove_if(
[&](auto item) { return item.value->should_gc(gc_collect_rate_, timestamp); });
}
/* Optimization Passes */
for (auto &engine_passes : passes_) {
for (std::unique_ptr<GPUPass> &pass : engine_passes[true].values()) {
pass->update(timestamp);
}
engine_passes[true].remove_if([&](auto item) {
return item.value->should_gc(optimization_gc_collect_rate_, timestamp);
});
}
}
std::mutex &get_mutex()
{
return mutex_;
}
};
static GPUPassCache *g_cache = nullptr;
void GPU_pass_ensure_its_ready(GPUPass *pass)
{
if (pass->status == GPU_PASS_QUEUED) {
std::lock_guard lock(g_cache->get_mutex());
if (pass->status == GPU_PASS_QUEUED) {
pass->finalize_compilation();
}
}
}
void GPU_pass_cache_init()
{
g_cache = MEM_new<GPUPassCache>(__func__);
}
void GPU_pass_cache_update()
{
g_cache->update();
}
void GPU_pass_cache_wait_for_all()
{
GPU_shader_compiler_wait_for_all();
g_cache->update();
}
void GPU_pass_cache_free()
{
MEM_SAFE_DELETE(g_cache);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Compilation
* \{ */
static bool gpu_pass_validate(GPUCodegenCreateInfo *create_info)
{
int samplers_len = 0;
for (const ShaderCreateInfo::Resource &res : create_info->resources_get_all_()) {
if (res.bind_type == ShaderCreateInfo::Resource::BindType::SAMPLER) {
samplers_len++;
}
}
/* Validate against GPU limit. */
return samplers_len <= GPU_max_textures();
}
GPUPass *GPU_generate_pass(GPUMaterial *material,
GPUNodeGraph *graph,
const char *debug_name,
eGPUMaterialEngine engine,
bool deferred_compilation,
GPUCodegenCallbackFn finalize_source_cb,
void *thunk,
bool optimize_graph)
{
gpu_node_graph_prune_unused(graph);
/* If Optimize flag is passed in, we are generating an optimized
* variant of the GPUMaterial's GPUPass. */
if (optimize_graph) {
gpu_node_graph_optimize(graph);
}
/* Extract attributes before compiling so the generated VBOs are ready to accept the future
* shader. */
gpu_node_graph_finalize_uniform_attrs(graph);
GPUCodegen codegen(material, graph, debug_name);
codegen.generate_graphs();
codegen.generate_cryptomatte();
GPUPass *pass = nullptr;
if (!optimize_graph) {
/* The optimized version of the shader should not re-generate a UBO.
* The UBO will not be used for this variant. */
codegen.generate_uniform_buffer();
}
/* Cache lookup: Reuse shaders already compiled. */
pass = g_cache->get(engine, codegen.hash_get(), deferred_compilation, optimize_graph);
if (pass) {
pass->refcount++;
return pass;
}
/* The shader is not compiled, continue generating the shader strings. */
codegen.generate_attribs();
codegen.generate_resources();
/* Make engine add its own code and implement the generated functions. */
finalize_source_cb(thunk, material, &codegen.output);
codegen.create_info->finalize();
g_cache->add(engine, codegen, deferred_compilation, optimize_graph);
codegen.create_info = nullptr;
return g_cache->get(engine, codegen.hash_get(), deferred_compilation, optimize_graph);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,206 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Wrap OpenGL features such as textures, shaders and GLSL
* with checks for drivers and GPU support.
*/
#include <cstdint>
#include "MEM_guardedalloc.h"
#include "BLI_dynstr.h"
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "BLI_vector.hh"
#include "GPU_platform.hh"
#include "gpu_platform_private.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name GPUPlatformGlobal
* \{ */
namespace gpu {
GPUPlatformGlobal GPG;
static char *create_key(GPUSupportLevel support_level,
const char *vendor,
const char *renderer,
const char *version)
{
DynStr *ds = BLI_dynstr_new();
BLI_dynstr_appendf(ds, "{%s/%s/%s}=", vendor, renderer, version);
if (support_level == GPU_SUPPORT_LEVEL_SUPPORTED) {
BLI_dynstr_append(ds, "SUPPORTED");
}
else if (support_level == GPU_SUPPORT_LEVEL_LIMITED) {
BLI_dynstr_append(ds, "LIMITED");
}
else {
BLI_dynstr_append(ds, "UNSUPPORTED");
}
char *support_key = BLI_dynstr_get_cstring(ds);
BLI_dynstr_free(ds);
BLI_string_replace_char(support_key, '\n', ' ');
BLI_string_replace_char(support_key, '\r', ' ');
return support_key;
}
static char *create_gpu_name(const char *vendor, const char *renderer, const char *version)
{
DynStr *ds = BLI_dynstr_new();
BLI_dynstr_appendf(ds, "%s %s %s", vendor, renderer, version);
char *gpu_name = BLI_dynstr_get_cstring(ds);
BLI_dynstr_free(ds);
BLI_string_replace_char(gpu_name, '\n', ' ');
BLI_string_replace_char(gpu_name, '\r', ' ');
return gpu_name;
}
void GPUPlatformGlobal::init(GPUDeviceType gpu_device,
GPUOSType os_type,
GPUDriverType driver_type,
GPUSupportLevel gpu_support_level,
GPUBackendType backend,
const char *vendor_str,
const char *renderer_str,
const char *version_str,
GPUArchitectureType arch_type)
{
this->clear();
this->initialized = true;
this->device = gpu_device;
this->os = os_type;
this->driver = driver_type;
this->support_level = gpu_support_level;
const char *vendor = vendor_str ? vendor_str : "UNKNOWN";
const char *renderer = renderer_str ? renderer_str : "UNKNOWN";
const char *version = version_str ? version_str : "UNKNOWN";
this->vendor = BLI_strdup(vendor);
this->renderer = BLI_strdup(renderer);
this->version = BLI_strdup(version);
this->support_key = create_key(gpu_support_level, vendor, renderer, version);
this->gpu_name = create_gpu_name(vendor, renderer, version);
this->backend = backend;
this->architecture_type = arch_type;
}
void GPUPlatformGlobal::clear()
{
MEM_SAFE_DELETE(vendor);
MEM_SAFE_DELETE(renderer);
MEM_SAFE_DELETE(version);
MEM_SAFE_DELETE(support_key);
MEM_SAFE_DELETE(gpu_name);
devices.clear_and_shrink();
device_uuid.reinitialize(0);
device_luid.reinitialize(0);
device_luid_node_mask = 0;
initialized = false;
}
} // namespace gpu
/** \} */
/* -------------------------------------------------------------------- */
/** \name C-API
* \{ */
using namespace blender::gpu;
GPUSupportLevel GPU_platform_support_level()
{
BLI_assert(GPG.initialized);
return GPG.support_level;
}
const char *GPU_platform_vendor()
{
BLI_assert(GPG.initialized);
return GPG.vendor;
}
const char *GPU_platform_renderer()
{
BLI_assert(GPG.initialized);
return GPG.renderer;
}
const char *GPU_platform_version()
{
BLI_assert(GPG.initialized);
return GPG.version;
}
const char *GPU_platform_support_level_key()
{
BLI_assert(GPG.initialized);
return GPG.support_key;
}
const char *GPU_platform_gpu_name()
{
BLI_assert(GPG.initialized);
return GPG.gpu_name;
}
GPUArchitectureType GPU_platform_architecture()
{
BLI_assert(GPG.initialized);
return GPG.architecture_type;
}
bool GPU_type_matches(GPUDeviceType device, GPUOSType os, GPUDriverType driver)
{
return GPU_type_matches_ex(device, os, driver, GPU_BACKEND_ANY);
}
bool GPU_type_matches_ex(GPUDeviceType device,
GPUOSType os,
GPUDriverType driver,
GPUBackendType backend)
{
BLI_assert(GPG.initialized);
return (GPG.device & device) && (GPG.os & os) && (GPG.driver & driver) &&
(GPG.backend & backend);
}
Span<GPUDevice> GPU_platform_devices_list()
{
return GPG.devices.as_span();
}
Span<uint8_t> GPU_platform_uuid()
{
return GPG.device_uuid.as_span();
}
Span<uint8_t> GPU_platform_luid()
{
return GPG.device_luid.as_span();
}
uint32_t GPU_platform_luid_node_mask()
{
return GPG.device_luid_node_mask;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include <cstdint>
#include "BLI_array.hh"
#include "BLI_vector.hh"
#include "GPU_platform.hh"
namespace blender::gpu {
class GPUPlatformGlobal {
public:
bool initialized = false;
GPUDeviceType device;
GPUOSType os;
GPUDriverType driver;
GPUSupportLevel support_level;
char *vendor = nullptr;
char *renderer = nullptr;
char *version = nullptr;
char *support_key = nullptr;
char *gpu_name = nullptr;
GPUBackendType backend = GPU_BACKEND_NONE;
GPUArchitectureType architecture_type = GPU_ARCHITECTURE_IMR;
Vector<GPUDevice> devices;
/* The UUID of the device. Can be an empty array, since it is not supported on all platforms. */
Array<uint8_t, 16> device_uuid;
/* The LUID of the device. Can be an empty array, since it is not supported on all platforms. */
Array<uint8_t, 8> device_luid;
/* A bit field with the nth bit active identifying the nth device with the same LUID. Only
* matters if device_luid is defined. */
uint32_t device_luid_node_mask;
void init(GPUDeviceType gpu_device,
GPUOSType os_type,
GPUDriverType driver_type,
GPUSupportLevel gpu_support_level,
GPUBackendType backend,
const char *vendor_str,
const char *renderer_str,
const char *version_str,
GPUArchitectureType arch_type);
void clear();
};
extern GPUPlatformGlobal GPG;
} // namespace blender::gpu

View File

@@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
namespace blender {
/* gpu_backend.cc */
void gpu_backend_init_resources();
void gpu_backend_delete_resources();
} // namespace blender

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_map.hh"
#include "BLI_mutex.hh"
#include "BLI_string_ref.hh"
#include <fmt/format.h>
#include <fstream>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
namespace blender::gpu {
class ProfileReport {
private:
std::fstream _report;
Mutex _mutex;
Map<uint64_t, int> _thread_ids;
ProfileReport()
{
_report.open("profile.json", std::ios::out);
_report << R"([{"name":"process_name","ph":"M","pid":1,"args":{"name":"GPU"}})"
",\n";
_report << R"({"name":"process_name","ph":"M","pid":2,"args":{"name":"CPU"}})";
}
~ProfileReport()
{
_report << "\n]\n";
_report.close();
}
public:
static ProfileReport &get()
{
static ProfileReport singleton;
return singleton;
}
void add_group(StringRefNull name,
uint64_t gpu_start,
uint64_t gpu_end,
uint64_t cpu_start,
uint64_t cpu_end)
{
std::scoped_lock lock(_mutex);
size_t thread_hash = std::hash<std::thread::id>()(std::this_thread::get_id());
int thread_id = _thread_ids.lookup_or_add(thread_hash, _thread_ids.size());
_report << fmt::format(
",\n"
R"({{"name":"{}","ph":"X","ts":{},"dur":{},"pid":1,"tid":{}}})",
name.c_str(),
gpu_start / uint64_t(1000),
(gpu_end - gpu_start) / uint64_t(1000),
thread_id);
_report << fmt::format(
",\n"
R"({{"name":"{}","ph":"X","ts":{},"dur":{},"pid":2,"tid":{}}})",
name.c_str(),
cpu_start / uint64_t(1000),
(cpu_end - cpu_start) / uint64_t(1000),
thread_id);
}
void add_group_cpu(StringRefNull name, uint64_t cpu_start, uint64_t cpu_end)
{
std::scoped_lock lock(_mutex);
size_t thread_hash = std::hash<std::thread::id>()(std::this_thread::get_id());
int thread_id = _thread_ids.lookup_or_add(thread_hash, _thread_ids.size());
_report << fmt::format(
",\n"
R"({{"name":"{}","ph":"X","ts":{},"dur":{},"pid":2,"tid":{}}})",
name.c_str(),
cpu_start / uint64_t(1000),
(cpu_end - cpu_start) / uint64_t(1000),
thread_id);
}
};
} // namespace blender::gpu

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "gpu_query.hh"
namespace blender {
using namespace blender::gpu;
/* TODO(fclem): Make the associated C-API to use inside DRW profiler. */
} // namespace blender

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_span.hh"
namespace blender::gpu {
#define QUERY_MIN_LEN 16
enum GPUQueryType {
GPU_QUERY_OCCLUSION = 0,
};
class QueryPool {
public:
virtual ~QueryPool() = default;
/**
* Will start and end the query at this index inside the pool. The pool will resize
* automatically but does not support sparse allocation. So prefer using consecutive indices.
*/
virtual void init(GPUQueryType type) = 0;
/**
* Will start and end the query at this index inside the pool.
* The pool will resize automatically.
*/
virtual void begin_query() = 0;
virtual void end_query() = 0;
/**
* Must be fed with a buffer large enough to contain all the queries issued.
* IMPORTANT: Result for each query can be either binary or represent the number of samples
* drawn.
*/
virtual void get_occlusion_result(MutableSpan<uint32_t> r_values) = 0;
};
} // namespace blender::gpu

View File

@@ -0,0 +1,345 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Interface for accessing GPU-related methods for selection. The semantics are
* similar to `glRenderMode(GL_SELECT)` from older OpenGL versions.
*/
#include <cstdlib>
#include <cstring>
#include "GPU_select.hh"
#include "BLI_rect.h"
#include "BLI_utildefines.h"
#include "gpu_select_private.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Internal Types
* \{ */
/* Internal algorithm used */
enum GPUSelectAlgo {
/**
* `glBegin/EndQuery(GL_SAMPLES_PASSED... )`, `gpu_select_query.c`
* Only sets 4th component (ID) correctly.
*/
ALGO_SAMPLE_QUERY = 1,
/**
* Read depth buffer for every drawing pass and extract depths, `gpu_select_pick.cc`
* Only sets 4th component (ID) correctly.
*/
ALGO_DEPTH_PICK = 2,
/** Use Select-Next draw engine. */
ALGO_SELECT_NEXT = 3,
};
struct GPUSelectState {
/* To ignore selection id calls when not initialized */
bool select_is_active;
/* mode of operation */
GPUSelectMode mode;
/* internal algorithm for selection */
GPUSelectAlgo algorithm;
/* allow GPU_select_begin/end without drawing */
bool use_cache;
/**
* Signifies that #GPU_select_cache_begin has been called,
* future calls to #GPU_select_begin should initialize the cache.
*
* \note #GPU_select_cache_begin could perform initialization but doesn't as it's inconvenient
* for callers making the cache begin/end calls outside lower level selection logic
* where the `mode` to pass to #GPU_select_begin yet isn't known.
*/
bool use_cache_needs_init;
};
static GPUSelectState g_select_state = {false};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
static void gpu_select_begin_ex(GPUSelectBuffer *buffer,
const rcti *input,
const int radius,
GPUSelectMode mode,
int oldhits,
bool use_select_next)
{
if (mode == GPU_SELECT_NEAREST_SECOND_PASS) {
/* In the case hits was '-1',
* don't start the second pass since it's not going to give useful results.
* As well as buffer overflow in 'gpu_select_query_load_id'. */
BLI_assert(oldhits != -1);
}
g_select_state.select_is_active = true;
g_select_state.mode = mode;
if (use_select_next) {
g_select_state.algorithm = ALGO_SELECT_NEXT;
}
else if (ELEM(g_select_state.mode, GPU_SELECT_PICK_ALL, GPU_SELECT_PICK_NEAREST)) {
g_select_state.algorithm = ALGO_DEPTH_PICK;
}
else {
g_select_state.algorithm = ALGO_SAMPLE_QUERY;
}
/* This function is called when cache has already been initialized,
* so only manipulate cache values when cache is pending. */
if (g_select_state.use_cache_needs_init) {
g_select_state.use_cache_needs_init = false;
switch (g_select_state.algorithm) {
case ALGO_SELECT_NEXT:
case ALGO_SAMPLE_QUERY: {
g_select_state.use_cache = false;
break;
}
default: {
g_select_state.use_cache = true;
gpu_select_pick_cache_begin();
break;
}
}
}
switch (g_select_state.algorithm) {
case ALGO_SELECT_NEXT: {
gpu_select_next_begin(buffer, input, radius, mode);
break;
}
case ALGO_SAMPLE_QUERY: {
gpu_select_query_begin(buffer, input, mode, oldhits);
break;
}
default: /* ALGO_DEPTH_PICK */
{
gpu_select_pick_begin(buffer, input, mode);
break;
}
}
}
void GPU_select_begin_next(
GPUSelectBuffer *buffer, const rcti *input, const int radius, GPUSelectMode mode, int oldhits)
{
gpu_select_begin_ex(buffer, input, radius, mode, oldhits, true);
}
void GPU_select_begin(GPUSelectBuffer *buffer, const rcti *input, GPUSelectMode mode, int oldhits)
{
gpu_select_begin_ex(buffer, input, 0, mode, oldhits, false);
}
bool GPU_select_load_id(uint id)
{
/* if no selection mode active, ignore */
if (!g_select_state.select_is_active) {
return true;
}
switch (g_select_state.algorithm) {
case ALGO_SELECT_NEXT:
/* This shouldn't use this pipeline. */
BLI_assert_unreachable();
return false;
case ALGO_SAMPLE_QUERY: {
return gpu_select_query_load_id(id);
}
default: /* ALGO_DEPTH_PICK */
{
return gpu_select_pick_load_id(id, false);
}
}
}
uint GPU_select_end()
{
uint hits = 0;
switch (g_select_state.algorithm) {
case ALGO_SELECT_NEXT: {
hits = gpu_select_next_end();
break;
}
case ALGO_SAMPLE_QUERY: {
hits = gpu_select_query_end();
break;
}
default: /* ALGO_DEPTH_PICK */
{
hits = gpu_select_pick_end();
break;
}
}
g_select_state.select_is_active = false;
return hits;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Caching
*
* Support multiple begin/end's as long as they are within the initial region.
* Currently only used by #ALGO_DEPTH_PICK.
* \{ */
void GPU_select_cache_begin()
{
BLI_assert(g_select_state.select_is_active == false);
/* Ensure #GPU_select_cache_end is always called. */
BLI_assert(g_select_state.use_cache_needs_init == false);
/* Signal that cache should be used, instead of calling the algorithms cache-begin function.
* This is more convenient as the exact method of selection may not be known by the caller. */
g_select_state.use_cache_needs_init = true;
}
void GPU_select_cache_load_id()
{
BLI_assert(g_select_state.use_cache == true);
if (g_select_state.algorithm == ALGO_DEPTH_PICK) {
gpu_select_pick_cache_load_id();
}
}
void GPU_select_cache_end()
{
if (g_select_state.algorithm == ALGO_DEPTH_PICK) {
BLI_assert(g_select_state.use_cache == true);
gpu_select_pick_cache_end();
}
g_select_state.use_cache = false;
/* Paranoid assignment, should already be false. */
g_select_state.use_cache_needs_init = false;
}
bool GPU_select_is_cached()
{
return g_select_state.use_cache && gpu_select_pick_is_cached();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Utilities
* \{ */
int gpu_select_buffer_depth_id_cmp(const void *sel_a_p, const void *sel_b_p)
{
GPUSelectResult *a = static_cast<GPUSelectResult *>(const_cast<void *>(sel_a_p));
GPUSelectResult *b = static_cast<GPUSelectResult *>(const_cast<void *>(sel_b_p));
if (a->depth < b->depth) {
return -1;
}
if (a->depth > b->depth) {
return 1;
}
/* Depths match, sort by id. */
/* NOTE: this is endianness-sensitive.
* GPUSelectResult values are always expected to be little-endian. */
uint sel_a = a->id;
uint sel_b = b->id;
if (sel_a < sel_b) {
return -1;
}
if (sel_a > sel_b) {
return 1;
}
return 0;
}
const GPUSelectResult *GPU_select_buffer_near(const Span<GPUSelectResult> hit_results)
{
const GPUSelectResult *hit_result_near = nullptr;
uint depth_min = uint(-1);
for (const GPUSelectResult &hit_result : hit_results) {
if (hit_result.depth < depth_min) {
BLI_assert(hit_result.id != -1);
depth_min = hit_result.depth;
hit_result_near = &hit_result;
}
}
return hit_result_near;
}
uint GPU_select_buffer_remove_by_id(MutableSpan<GPUSelectResult> hit_results, uint select_id)
{
uint index_src = 0;
uint index_dst = 0;
uint hits_final = 0;
for (const GPUSelectResult &hit_result : hit_results) {
if (hit_result.id != select_id) {
if (index_dst != index_src) {
hit_results[index_dst] = hit_result;
}
index_dst++;
hits_final++;
}
index_src++;
}
return hits_final;
}
void GPU_select_buffer_stride_realign(const rcti *src, const rcti *dst, uint *r_buf)
{
const int x = dst->xmin - src->xmin;
const int y = dst->ymin - src->ymin;
BLI_assert(src->xmin <= dst->xmin && src->ymin <= dst->ymin && src->xmax >= dst->xmax &&
src->ymax >= dst->ymax);
BLI_assert(x >= 0 && y >= 0);
const int src_x = BLI_rcti_size_x(src);
const int src_y = BLI_rcti_size_y(src);
const int dst_x = BLI_rcti_size_x(dst);
const int dst_y = BLI_rcti_size_y(dst);
BLI_assert(dst_x > 0 && dst_y > 0);
int last_px_id = src_x * (y + dst_y - 1) + (x + dst_x - 1);
memset(&r_buf[last_px_id + 1], 0, (src_x * src_y - (last_px_id + 1)) * sizeof(*r_buf));
if (last_px_id < 0) {
/* Nothing to write. */
BLI_assert(last_px_id == -1);
return;
}
int last_px_written = dst_x * dst_y - 1;
const int skip = src_x - dst_x;
while (true) {
for (int i = dst_x; i--;) {
r_buf[last_px_id--] = r_buf[last_px_written--];
}
if (last_px_written < 0) {
break;
}
last_px_id -= skip;
memset(&r_buf[last_px_id + 1], 0, skip * sizeof(*r_buf));
}
memset(r_buf, 0, (last_px_id + 1) * sizeof(*r_buf));
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Glue to make the new Select-Next engine work with the old GPU select API.
*/
#include <cfloat>
#include "BLI_rect.h"
#include "BLI_span.hh"
#include "gpu_select_private.hh"
namespace blender {
struct GPUSelectNextState {
/** Result buffer set on initialization. */
GPUSelectBuffer *buffer = nullptr;
/** Area of the viewport to render / select from. */
rcti rect = {0, 0, 0, 0};
/** The radius relative to the center of `rect` in which to select. Ignored if 0. */
int radius = 0;
/** Number of hits. Set to -1 if it overflows buffer_len. */
uint hits = -1;
/** Mode of operation. */
GPUSelectMode mode = GPUSelectMode::GPU_SELECT_INVALID;
};
static GPUSelectNextState g_state = {};
void gpu_select_next_begin(GPUSelectBuffer *buffer,
const rcti *input,
const int radius,
GPUSelectMode mode)
{
g_state.buffer = buffer;
g_state.rect = *input;
g_state.mode = mode;
g_state.radius = radius;
}
int2 gpu_select_next_get_pick_area_center()
{
return int2(BLI_rcti_size_x(&g_state.rect) / 2, BLI_rcti_size_y(&g_state.rect) / 2);
}
GPUSelectMode gpu_select_next_get_mode()
{
return g_state.mode;
}
int gpu_select_next_get_radius()
{
return g_state.radius;
}
void gpu_select_next_set_result(GPUSelectResult *hit_buf, uint hit_len)
{
const int old_size = g_state.buffer->storage.size();
g_state.buffer->storage.resize(old_size + hit_len);
MutableSpan<GPUSelectResult> hit_results = g_state.buffer->storage.as_mutable_span().slice(
old_size, hit_len);
const Span<GPUSelectResult> hits(hit_buf, hit_len);
/* TODO(fclem): There might be some conversion to do to align to the other APIs output. */
switch (g_state.mode) {
case GPUSelectMode::GPU_SELECT_ALL:
hit_results.copy_from(hits);
break;
case GPUSelectMode::GPU_SELECT_PICK_ALL:
hit_results.copy_from(hits);
break;
case GPUSelectMode::GPU_SELECT_PICK_NEAREST:
hit_results.copy_from(hits);
break;
case GPUSelectMode::GPU_SELECT_NEAREST_FIRST_PASS:
case GPUSelectMode::GPU_SELECT_NEAREST_SECOND_PASS:
case GPUSelectMode::GPU_SELECT_INVALID:
BLI_assert_unreachable();
break;
}
g_state.hits = hit_len;
}
uint gpu_select_next_end()
{
return g_state.hits;
}
} // namespace blender

View File

@@ -0,0 +1,755 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Custom select code for picking small regions (not efficient for large regions).
* `gpu_select_pick_*` API.
*/
#include <cfloat>
#include <cstdlib>
#include <cstring>
#include "GPU_debug.hh"
#include "GPU_framebuffer.hh"
#include "GPU_select.hh"
#include "GPU_state.hh"
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_rect.h"
#include "BLI_utildefines.h"
#include "gpu_select_private.hh"
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
namespace blender {
// #define DEBUG_PRINT
/* Alloc number for depths */
#define ALLOC_DEPTHS 200
/* Z-depth of cleared depth buffer */
#define DEPTH_MAX 0xffffffff
/* -------------------------------------------------------------------- */
/** \name #SubRectStride
* \{ */
/** For looping over a sub-region of a #rcti, could be moved into `rct.c`. */
struct SubRectStride {
/** Start here. */
uint start;
/** Read these. */
uint span;
/** `len` times (read span 'len' times). */
uint span_len;
/** Skip those. */
uint skip;
};
/** We may want to change back to float if `uint` isn't well supported. */
using depth_t = uint;
/**
* Calculate values needed for looping over a sub-region (smaller buffer within a larger buffer).
*
* 'src' must be bigger than 'dst'.
*/
static void rect_subregion_stride_calc(const rcti *src, const rcti *dst, SubRectStride *r_sub)
{
const int src_x = BLI_rcti_size_x(src);
// const int src_y = BLI_rcti_size_y(src);
const int dst_x = BLI_rcti_size_x(dst);
const int dst_y = BLI_rcti_size_y(dst);
const int x = dst->xmin - src->xmin;
const int y = dst->ymin - src->ymin;
BLI_assert(src->xmin <= dst->xmin && src->ymin <= dst->ymin && src->xmax >= dst->xmax &&
src->ymax >= dst->ymax);
BLI_assert(x >= 0 && y >= 0);
r_sub->start = uint((src_x * y) + x);
r_sub->span = uint(dst_x);
r_sub->span_len = uint(dst_y);
r_sub->skip = uint(src_x - dst_x);
}
/**
* Ignore depth clearing as a change,
* only check if its been changed _and_ filled in (ignore clearing since XRAY does this).
*/
BLI_INLINE bool depth_is_filled(const depth_t *prev, const depth_t *curr)
{
return (*prev != *curr) && (*curr != DEPTH_MAX);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #DepthBufCache
*
* Result of reading #GPU_framebuffer_read_depth,
* use for both cache and non-cached storage.
* \{ */
/** Store result of #GPU_framebuffer_read_depth. */
struct DepthBufCache {
DepthBufCache *next, *prev;
uint id;
depth_t buf[0];
};
static DepthBufCache *depth_buf_malloc(uint rect_len)
{
DepthBufCache *rect = static_cast<DepthBufCache *>(
MEM_new_uninitialized(sizeof(DepthBufCache) + sizeof(depth_t) * rect_len, __func__));
rect->id = SELECT_ID_NONE;
return rect;
}
static bool depth_buf_rect_depth_any(const DepthBufCache *rect_depth, uint rect_len)
{
const depth_t *curr = rect_depth->buf;
for (uint i = 0; i < rect_len; i++, curr++) {
if (*curr != DEPTH_MAX) {
return true;
}
}
return false;
}
static bool depth_buf_subrect_depth_any(const DepthBufCache *rect_depth,
const SubRectStride *sub_rect)
{
const depth_t *curr = rect_depth->buf + sub_rect->start;
for (uint i = 0; i < sub_rect->span_len; i++) {
const depth_t *curr_end = curr + sub_rect->span;
for (; curr < curr_end; curr++, curr++) {
if (*curr != DEPTH_MAX) {
return true;
}
}
curr += sub_rect->skip;
}
return false;
}
static bool depth_buf_rect_depth_any_filled(const DepthBufCache *rect_prev,
const DepthBufCache *rect_curr,
uint rect_len)
{
#if 0
return memcmp(rect_depth_a->buf, rect_depth_b->buf, rect_len * sizeof(depth_t)) != 0;
#else
const depth_t *prev = rect_prev->buf;
const depth_t *curr = rect_curr->buf;
for (uint i = 0; i < rect_len; i++, curr++, prev++) {
if (depth_is_filled(prev, curr)) {
return true;
}
}
return false;
#endif
}
/**
* Both buffers are the same size, just check if the sub-rect contains any differences.
*/
static bool depth_buf_subrect_depth_any_filled(const DepthBufCache *rect_src,
const DepthBufCache *rect_dst,
const SubRectStride *sub_rect)
{
/* Same as #depth_buf_rect_depth_any_filled but different rectangle sizes. */
const depth_t *prev = rect_src->buf + sub_rect->start;
const depth_t *curr = rect_dst->buf + sub_rect->start;
for (uint i = 0; i < sub_rect->span_len; i++) {
const depth_t *curr_end = curr + sub_rect->span;
for (; curr < curr_end; prev++, curr++) {
if (depth_is_filled(prev, curr)) {
return true;
}
}
prev += sub_rect->skip;
curr += sub_rect->skip;
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #DepthID
*
* Internal structure for storing hits.
* \{ */
struct DepthID {
uint id;
depth_t depth;
};
static int depth_id_cmp(const void *v1, const void *v2)
{
const DepthID *d1 = static_cast<const DepthID *>(v1), *d2 = static_cast<const DepthID *>(v2);
if (d1->id < d2->id) {
return -1;
}
if (d1->id > d2->id) {
return 1;
}
return 0;
}
static int depth_cmp(const void *v1, const void *v2)
{
const DepthID *d1 = static_cast<const DepthID *>(v1), *d2 = static_cast<const DepthID *>(v2);
if (d1->depth < d2->depth) {
return -1;
}
if (d1->depth > d2->depth) {
return 1;
}
return 0;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Main Selection Begin/End/Load API
* \{ */
/** Depth sorting. */
struct GPUPickState {
/** Cache on initialization. */
GPUSelectBuffer *buffer;
/** Mode of this operation. */
GPUSelectMode mode;
/** GPU drawing, never use when `is_cached == true`. */
struct {
/** The current depth, accumulated while drawing. */
DepthBufCache *rect_depth;
/** Scratch buffer, avoid allocations every time (when not caching). */
DepthBufCache *rect_depth_test;
/** Pass to `GPU_framebuffer_read_depth(x, y, w, h)`. */
int clip_readpixels[4];
/** Set after first draw. */
bool is_init;
uint prev_id;
} gpu;
/**
* `src`: data stored in 'cache' and 'gpu',
* `dst`: use when cached region is smaller (where `src` -> `dst` isn't 1:1).
*/
struct {
rcti clip_rect;
uint rect_len;
} src, dst;
/** Store cache between `GPU_select_cache_begin/end` */
bool use_cache;
bool is_cached;
struct {
/**
* Cleanup used for iterating over both source and destination buffers:
* `src.clip_rect` -> `dst.clip_rect`.
*/
SubRectStride sub_rect;
/** List of #DepthBufCache, sized of 'src.clip_rect'. */
ListBaseT<DepthBufCache> bufs;
} cache;
/** Picking methods. */
union {
/** #GPU_SELECT_PICK_ALL */
struct {
DepthID *hits;
uint hits_len;
uint hits_len_alloc;
} all;
/** #GPU_SELECT_PICK_NEAREST */
struct {
uint *rect_id;
} nearest;
};
/** Previous state to restore after drawing. */
int viewport[4];
int scissor[4];
GPUWriteMask write_mask;
GPUDepthTest depth_test;
};
static GPUPickState g_pick_state{};
void gpu_select_pick_begin(GPUSelectBuffer *buffer, const rcti *input, GPUSelectMode mode)
{
GPUPickState *ps = &g_pick_state;
#ifdef DEBUG_PRINT
printf("%s: mode=%d, use_cache=%d, is_cache=%d\n",
__func__,
int(mode),
ps->use_cache,
ps->is_cached);
#endif
GPU_debug_group_begin("Selection Pick");
ps->buffer = buffer;
ps->mode = mode;
const uint rect_len = uint(BLI_rcti_size_x(input) * BLI_rcti_size_y(input));
ps->dst.clip_rect = *input;
ps->dst.rect_len = rect_len;
/* Avoids unnecessary GPU operations when cache is available and they are unnecessary. */
if (ps->is_cached == false) {
ps->write_mask = GPU_write_mask_get();
ps->depth_test = GPU_depth_test_get();
GPU_scissor_get(ps->scissor);
/* Disable writing to the frame-buffer. */
GPU_color_mask(false, false, false, false);
GPU_depth_mask(true);
/* Always use #GPU_DEPTH_LESS_EQUAL even though #GPU_SELECT_PICK_ALL always clears the buffer.
* This is because individual objects themselves might have sections that overlap and we need
* these to have the correct distance information. */
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
float viewport[4];
GPU_viewport_size_get_f(viewport);
ps->src.clip_rect = *input;
ps->src.rect_len = rect_len;
ps->gpu.clip_readpixels[0] = int(viewport[0]);
ps->gpu.clip_readpixels[1] = int(viewport[1]);
ps->gpu.clip_readpixels[2] = BLI_rcti_size_x(&ps->src.clip_rect);
ps->gpu.clip_readpixels[3] = BLI_rcti_size_y(&ps->src.clip_rect);
GPU_viewport(UNPACK4(ps->gpu.clip_readpixels));
/* It's possible we don't want to clear depth buffer,
* so existing elements are masked by current z-buffer. */
GPU_clear_depth(1.0f);
/* scratch buffer (read new values here) */
ps->gpu.rect_depth_test = depth_buf_malloc(rect_len);
ps->gpu.rect_depth = depth_buf_malloc(rect_len);
/* Set initial 'far' value. */
for (uint i = 0; i < rect_len; i++) {
ps->gpu.rect_depth->buf[i] = DEPTH_MAX;
}
ps->gpu.is_init = false;
ps->gpu.prev_id = 0;
}
else {
/* Using cache `ps->is_cached == true`. */
/* `src.clip_rect` -> `dst.clip_rect`. */
rect_subregion_stride_calc(&ps->src.clip_rect, &ps->dst.clip_rect, &ps->cache.sub_rect);
BLI_assert(ps->gpu.rect_depth == nullptr);
BLI_assert(ps->gpu.rect_depth_test == nullptr);
}
if (mode == GPU_SELECT_PICK_ALL) {
ps->all.hits = MEM_new_array_uninitialized<DepthID>(ALLOC_DEPTHS, __func__);
ps->all.hits_len = 0;
ps->all.hits_len_alloc = ALLOC_DEPTHS;
}
else {
/* Set to 0xff for #SELECT_ID_NONE. */
ps->nearest.rect_id = MEM_new_array_uninitialized<uint>(ps->dst.rect_len, __func__);
memset(ps->nearest.rect_id, 0xff, sizeof(uint) * ps->dst.rect_len);
}
}
/**
* Given 2x depths, we know are different - update the depth information
* use for both cached/uncached depth buffers.
*/
static void gpu_select_load_id_pass_all(const DepthBufCache *rect_curr)
{
GPUPickState *ps = &g_pick_state;
const uint id = rect_curr->id;
/* find the best depth for this pass and store in 'all.hits' */
depth_t depth_best = DEPTH_MAX;
#define EVAL_TEST() \
if (depth_best > *curr) { \
depth_best = *curr; \
} \
((void)0)
if (ps->is_cached == false) {
const depth_t *curr = rect_curr->buf;
BLI_assert(ps->src.rect_len == ps->dst.rect_len);
const uint rect_len = ps->src.rect_len;
for (uint i = 0; i < rect_len; i++, curr++) {
EVAL_TEST();
}
}
else {
/* Same as above but different rectangle sizes. */
const depth_t *curr = rect_curr->buf + ps->cache.sub_rect.start;
for (uint i = 0; i < ps->cache.sub_rect.span_len; i++) {
const depth_t *curr_end = curr + ps->cache.sub_rect.span;
for (; curr < curr_end; curr++) {
EVAL_TEST();
}
curr += ps->cache.sub_rect.skip;
}
}
#undef EVAL_TEST
/* Ensure enough space. */
if (UNLIKELY(ps->all.hits_len == ps->all.hits_len_alloc)) {
ps->all.hits_len_alloc += ALLOC_DEPTHS;
ps->all.hits = static_cast<DepthID *>(
MEM_realloc_uninitialized(ps->all.hits, ps->all.hits_len_alloc * sizeof(*ps->all.hits)));
}
DepthID *d = &ps->all.hits[ps->all.hits_len++];
d->id = id;
d->depth = depth_best;
}
static void gpu_select_load_id_pass_nearest(const DepthBufCache *rect_prev,
const DepthBufCache *rect_curr)
{
GPUPickState *ps = &g_pick_state;
const uint id = rect_curr->id;
/* Keep track each pixels ID in `nearest.rect_id`. */
if (id != SELECT_ID_NONE) {
uint *id_ptr = ps->nearest.rect_id;
/* Check against DEPTH_MAX because XRAY will clear the buffer,
* so previously set values will become unset.
* In this case just leave those id's left as-is. */
#define EVAL_TEST() \
if (depth_is_filled(prev, curr)) { \
*id_ptr = id; \
} \
((void)0)
if (ps->is_cached == false) {
const depth_t *prev = rect_prev->buf;
const depth_t *curr = rect_curr->buf;
BLI_assert(ps->src.rect_len == ps->dst.rect_len);
const uint rect_len = ps->src.rect_len;
for (uint i = 0; i < rect_len; i++, curr++, prev++, id_ptr++) {
EVAL_TEST();
}
}
else {
/* same as above but different rect sizes */
const depth_t *prev = rect_prev->buf + ps->cache.sub_rect.start;
const depth_t *curr = rect_curr->buf + ps->cache.sub_rect.start;
for (uint i = 0; i < ps->cache.sub_rect.span_len; i++) {
const depth_t *curr_end = curr + ps->cache.sub_rect.span;
for (; curr < curr_end; prev++, curr++, id_ptr++) {
EVAL_TEST();
}
prev += ps->cache.sub_rect.skip;
curr += ps->cache.sub_rect.skip;
}
}
#undef EVAL_TEST
}
}
bool gpu_select_pick_load_id(uint id, bool end)
{
GPUPickState *ps = &g_pick_state;
if (ps->gpu.is_init) {
if (id == ps->gpu.prev_id && !end) {
/* No need to read if we are still drawing for the same id since
* all these depths will be merged / de-duplicated in the end. */
return true;
}
const uint rect_len = ps->src.rect_len;
gpu::FrameBuffer *fb = GPU_framebuffer_active_get();
GPU_framebuffer_read_depth(
fb, UNPACK4(ps->gpu.clip_readpixels), GPU_DATA_UINT, ps->gpu.rect_depth_test->buf);
/* Perform initial check since most cases the array remains unchanged. */
bool do_pass = false;
if (g_pick_state.mode == GPU_SELECT_PICK_ALL) {
if (depth_buf_rect_depth_any(ps->gpu.rect_depth_test, rect_len)) {
ps->gpu.rect_depth_test->id = ps->gpu.prev_id;
gpu_select_load_id_pass_all(ps->gpu.rect_depth_test);
do_pass = true;
}
}
else {
if (depth_buf_rect_depth_any_filled(ps->gpu.rect_depth, ps->gpu.rect_depth_test, rect_len)) {
ps->gpu.rect_depth_test->id = ps->gpu.prev_id;
gpu_select_load_id_pass_nearest(ps->gpu.rect_depth, ps->gpu.rect_depth_test);
do_pass = true;
}
}
if (do_pass) {
/* Store depth in cache */
if (ps->use_cache) {
BLI_addtail(&ps->cache.bufs, ps->gpu.rect_depth);
ps->gpu.rect_depth = depth_buf_malloc(ps->src.rect_len);
}
std::swap(ps->gpu.rect_depth, ps->gpu.rect_depth_test);
if (g_pick_state.mode == GPU_SELECT_PICK_ALL) {
/* (fclem) This is to be on the safe side. I don't know if this is required. */
bool prev_depth_mask = GPU_depth_mask_get();
/* we want new depths every time */
GPU_depth_mask(true);
GPU_clear_depth(1.0f);
GPU_depth_mask(prev_depth_mask);
}
}
}
ps->gpu.is_init = true;
ps->gpu.prev_id = id;
return true;
}
uint gpu_select_pick_end()
{
GPUPickState *ps = &g_pick_state;
#ifdef DEBUG_PRINT
printf("%s\n", __func__);
#endif
if (ps->is_cached == false) {
if (ps->gpu.is_init) {
/* force finishing last pass */
gpu_select_pick_load_id(ps->gpu.prev_id, true);
}
GPU_write_mask(ps->write_mask);
GPU_depth_test(ps->depth_test);
GPU_viewport(UNPACK4(ps->viewport));
}
GPU_debug_group_end();
/* Assign but never free directly since it may be in cache. */
DepthBufCache *rect_depth_final;
/* Store depth in cache */
if (ps->use_cache && !ps->is_cached) {
BLI_addtail(&ps->cache.bufs, ps->gpu.rect_depth);
ps->gpu.rect_depth = nullptr;
rect_depth_final = static_cast<DepthBufCache *>(ps->cache.bufs.last);
}
else if (ps->is_cached) {
rect_depth_final = static_cast<DepthBufCache *>(ps->cache.bufs.last);
}
else {
/* Common case, no cache. */
rect_depth_final = ps->gpu.rect_depth;
}
DepthID *depth_data;
uint depth_data_len = 0;
if (g_pick_state.mode == GPU_SELECT_PICK_ALL) {
depth_data = ps->all.hits;
depth_data_len = ps->all.hits_len;
/* Move ownership. */
ps->all.hits = nullptr;
ps->all.hits_len = 0;
ps->all.hits_len_alloc = 0;
}
else {
/* #GPU_SELECT_PICK_NEAREST */
/* Over allocate (unlikely we have as many depths as pixels). */
uint depth_data_len_first_pass = 0;
depth_data = MEM_new_array_uninitialized<DepthID>(ps->dst.rect_len, __func__);
/* Partially de-duplicating copy,
* when contiguous ID's are found - update their closest depth.
* This isn't essential but means there is less data to sort. */
#define EVAL_TEST(i_src, i_dst) \
{ \
const uint id = ps->nearest.rect_id[i_dst]; \
if (id != SELECT_ID_NONE) { \
const depth_t depth = rect_depth_final->buf[i_src]; \
if (depth_last == nullptr || depth_last->id != id) { \
DepthID *d = &depth_data[depth_data_len_first_pass++]; \
d->id = id; \
d->depth = depth; \
} \
else if (depth_last->depth > depth) { \
depth_last->depth = depth; \
} \
} \
} \
((void)0)
{
DepthID *depth_last = nullptr;
if (ps->is_cached == false) {
for (uint i = 0; i < ps->src.rect_len; i++) {
EVAL_TEST(i, i);
}
}
else {
/* Same as above but different rectangle sizes. */
uint i_src = ps->cache.sub_rect.start, i_dst = 0;
for (uint j = 0; j < ps->cache.sub_rect.span_len; j++) {
const uint i_src_end = i_src + ps->cache.sub_rect.span;
for (; i_src < i_src_end; i_src++, i_dst++) {
EVAL_TEST(i_src, i_dst);
}
i_src += ps->cache.sub_rect.skip;
}
}
}
#undef EVAL_TEST
qsort(depth_data, depth_data_len_first_pass, sizeof(DepthID), depth_id_cmp);
/* Sort by ID's then keep the best depth for each ID. */
depth_data_len = 0;
{
DepthID *depth_last = nullptr;
for (uint i = 0; i < depth_data_len_first_pass; i++) {
if (depth_last == nullptr || depth_last->id != depth_data[i].id) {
depth_last = &depth_data[depth_data_len++];
*depth_last = depth_data[i];
}
else if (depth_last->depth > depth_data[i].depth) {
depth_last->depth = depth_data[i].depth;
}
}
}
}
/* Finally sort each unique (id, depth) pair by depth
* so the final hit-list is sorted by depth (nearest first). */
uint hits = 0;
/* Leave sorting up to the caller. */
qsort(depth_data, depth_data_len, sizeof(DepthID), depth_cmp);
g_pick_state.buffer->storage.reserve(g_pick_state.buffer->storage.size() + depth_data_len);
for (uint i = 0; i < depth_data_len; i++) {
#ifdef DEBUG_PRINT
printf(" hit: %u: depth %u\n", depth_data[i].id, depth_data[i].depth);
#endif
GPUSelectResult hit_result{};
hit_result.id = depth_data[i].id;
hit_result.depth = depth_data[i].depth;
g_pick_state.buffer->storage.append_unchecked(hit_result);
hits++;
}
MEM_delete(depth_data);
MEM_SAFE_DELETE(ps->gpu.rect_depth);
MEM_SAFE_DELETE(ps->gpu.rect_depth_test);
if (g_pick_state.mode == GPU_SELECT_PICK_ALL) {
/* 'hits' already freed as 'depth_data' */
}
else {
MEM_delete(ps->nearest.rect_id);
ps->nearest.rect_id = nullptr;
}
if (ps->use_cache) {
ps->is_cached = true;
}
return hits;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Caching
*
* Support multiple begin/end's reusing depth buffers.
* \{ */
void gpu_select_pick_cache_begin()
{
BLI_assert(g_pick_state.use_cache == false);
#ifdef DEBUG_PRINT
printf("%s\n", __func__);
#endif
g_pick_state.use_cache = true;
g_pick_state.is_cached = false;
}
void gpu_select_pick_cache_end()
{
#ifdef DEBUG_PRINT
printf("%s: with %d buffers\n", __func__, g_pick_state.cache.bufs.count());
#endif
g_pick_state.use_cache = false;
g_pick_state.is_cached = false;
g_pick_state.cache.bufs.free_no_destruct();
}
bool gpu_select_pick_is_cached()
{
return g_pick_state.is_cached;
}
void gpu_select_pick_cache_load_id()
{
BLI_assert(g_pick_state.is_cached == true);
GPUPickState *ps = &g_pick_state;
#ifdef DEBUG_PRINT
printf("%s (building depth from cache)\n", __func__);
#endif
for (DepthBufCache &rect_depth : ps->cache.bufs) {
if (rect_depth.next != nullptr) {
/* We know the buffers differ, but this sub-region may not.
* Double check before adding an id-pass. */
if (g_pick_state.mode == GPU_SELECT_PICK_ALL) {
if (depth_buf_subrect_depth_any(rect_depth.next, &ps->cache.sub_rect)) {
gpu_select_load_id_pass_all(rect_depth.next);
}
}
else {
if (depth_buf_subrect_depth_any_filled(&rect_depth, rect_depth.next, &ps->cache.sub_rect))
{
gpu_select_load_id_pass_nearest(&rect_depth, rect_depth.next);
}
}
}
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Selection implementations.
*/
#pragma once
#include "BLI_math_vector_types.hh"
#include "GPU_select.hh"
namespace blender {
/* gpu_select_pick */
void gpu_select_pick_begin(GPUSelectBuffer *buffer, const rcti *input, GPUSelectMode mode);
bool gpu_select_pick_load_id(uint id, bool end);
uint gpu_select_pick_end();
void gpu_select_pick_cache_begin();
void gpu_select_pick_cache_end();
/**
* \return true if drawing is not needed.
*/
bool gpu_select_pick_is_cached();
void gpu_select_pick_cache_load_id();
/* gpu_select_sample_query */
void gpu_select_query_begin(GPUSelectBuffer *buffer,
const rcti *input,
GPUSelectMode mode,
int oldhits);
bool gpu_select_query_load_id(uint id);
uint gpu_select_query_end();
/* gpu_select_next */
void gpu_select_next_begin(GPUSelectBuffer *buffer,
const rcti *input,
int radius,
GPUSelectMode mode);
uint gpu_select_next_end();
/**
* Returns the center relative to the corner of the rect stored in the GPUSelectNextState.
*/
int2 gpu_select_next_get_pick_area_center();
GPUSelectMode gpu_select_next_get_mode();
int gpu_select_next_get_radius();
void gpu_select_next_set_result(GPUSelectResult *hit_buf, uint hit_len);
#define SELECT_ID_NONE ((uint)0xffffffff)
} // namespace blender

View File

@@ -0,0 +1,183 @@
/* SPDX-FileCopyrightText: 2014 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Interface for accessing GPU-related methods for selection. The semantics will be
* similar to `glRenderMode(GL_SELECT)` since the goal is to maintain compatibility.
*/
#include <cstdlib>
#include "GPU_debug.hh"
#include "GPU_framebuffer.hh"
#include "GPU_select.hh"
#include "GPU_state.hh"
#include "BLI_rect.h"
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "gpu_backend.hh"
#include "gpu_query.hh"
#include "gpu_select_private.hh"
namespace blender {
using namespace blender::gpu;
struct GPUSelectQueryState {
/** Tracks whether a query has been issued so that gpu_load_id can end the previous one. */
bool query_issued;
/** GPU queries abstraction. Contains an array of queries. */
QueryPool *queries;
/** Array holding the id corresponding id to each query. */
Vector<uint, QUERY_MIN_LEN> *ids;
/** Cache on initialization. */
GPUSelectBuffer *buffer;
/** Mode of operation. */
GPUSelectMode mode;
uint index;
int oldhits;
/** Previous state to restore after drawing. */
int viewport[4];
int scissor[4];
GPUWriteMask write_mask;
GPUDepthTest depth_test;
};
static GPUSelectQueryState g_query_state = {false};
void gpu_select_query_begin(GPUSelectBuffer *buffer,
const rcti *input,
const GPUSelectMode mode,
int oldhits)
{
GPU_debug_group_begin("Selection Queries");
g_query_state.query_issued = false;
g_query_state.buffer = buffer;
g_query_state.mode = mode;
g_query_state.index = 0;
g_query_state.oldhits = oldhits;
g_query_state.ids = new Vector<uint, QUERY_MIN_LEN>();
g_query_state.queries = GPUBackend::get()->querypool_alloc();
g_query_state.queries->init(GPU_QUERY_OCCLUSION);
g_query_state.write_mask = GPU_write_mask_get();
g_query_state.depth_test = GPU_depth_test_get();
GPU_scissor_get(g_query_state.scissor);
GPU_viewport_size_get_i(g_query_state.viewport);
/* Write to color buffer. Seems to fix issues with selecting alpha blended geom (see #7997). */
GPU_color_mask(true, true, true, true);
/* In order to save some fill rate we minimize the viewport using rect.
* We need to get the region of the viewport so that our geometry doesn't
* get rejected before the depth test. Should probably cull rect against
* the viewport but this is a rare case I think */
const int viewport[4] = {
UNPACK2(g_query_state.viewport), BLI_rcti_size_x(input), BLI_rcti_size_y(input)};
GPU_viewport(UNPACK4(viewport));
GPU_scissor(UNPACK4(viewport));
GPU_scissor_test(false);
/* occlusion queries operates on fragments that pass tests and since we are interested on all
* objects in the view frustum independently of their order, we need to disable the depth test */
if (mode == GPU_SELECT_ALL) {
/* #glQueries on Windows+Intel drivers only works with depth testing turned on.
* See #62947 for details */
GPU_depth_test(GPU_DEPTH_ALWAYS);
GPU_depth_mask(true);
}
else if (mode == GPU_SELECT_NEAREST_FIRST_PASS) {
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
GPU_depth_mask(true);
GPU_clear_depth(1.0f);
}
else if (mode == GPU_SELECT_NEAREST_SECOND_PASS) {
GPU_depth_test(GPU_DEPTH_EQUAL);
GPU_depth_mask(false);
}
}
bool gpu_select_query_load_id(uint id)
{
if (g_query_state.query_issued) {
g_query_state.queries->end_query();
}
g_query_state.queries->begin_query();
g_query_state.ids->append(id);
g_query_state.query_issued = true;
if (g_query_state.mode == GPU_SELECT_NEAREST_SECOND_PASS) {
/* Second pass should never run if first pass fails,
* can read past `buffer_len` in this case. */
BLI_assert(g_query_state.oldhits != -1);
if (g_query_state.index < g_query_state.oldhits) {
if (g_query_state.buffer->storage[g_query_state.index].id == id) {
g_query_state.index++;
return true;
}
return false;
}
}
return true;
}
uint gpu_select_query_end()
{
uint hits = 0;
if (g_query_state.query_issued) {
g_query_state.queries->end_query();
}
Span<uint> ids = *g_query_state.ids;
Vector<uint32_t, QUERY_MIN_LEN> result(ids.size());
g_query_state.queries->get_occlusion_result(result);
for (int i = 0; i < result.size(); i++) {
if (result[i] != 0) {
if (g_query_state.mode != GPU_SELECT_NEAREST_SECOND_PASS) {
GPUSelectResult hit_result{};
hit_result.id = ids[i];
hit_result.depth = 0xFFFF;
g_query_state.buffer->storage.append(hit_result);
hits++;
}
else {
int j;
/* search in buffer and make selected object first */
for (j = 0; j < g_query_state.oldhits; j++) {
if (g_query_state.buffer->storage[j].id == ids[i]) {
g_query_state.buffer->storage[j].depth = 0;
}
}
break;
}
}
}
delete g_query_state.queries;
delete g_query_state.ids;
GPU_write_mask(g_query_state.write_mask);
GPU_depth_test(g_query_state.depth_test);
GPU_viewport(UNPACK4(g_query_state.viewport));
GPU_debug_group_end();
return hits;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,325 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "GPU_shader_builtin.hh"
#include "BKE_global.hh"
#include "BLI_utildefines.h"
#include "GPU_capabilities.hh"
#include "GPU_shader.hh"
#include "gpu_shader_private.hh"
namespace blender {
struct BuiltinShader : gpu::StaticShader {
/* WORKAROUND: This is needed for the polyline workaround default initialization. */
bool init = false;
BuiltinShader(std::string info_name) : gpu::StaticShader(info_name) {}
};
/* Cache of built-in shaders (each is created on first use). */
static BuiltinShader *builtin_shaders[GPU_SHADER_CFG_LEN][GPU_SHADER_BUILTIN_LEN] = {{nullptr}};
static const char *builtin_shader_create_info_name(GPUBuiltinShader shader)
{
switch (shader) {
case GPU_SHADER_TEXT:
return "gpu_shader_text";
case GPU_SHADER_KEYFRAME_SHAPE:
return "gpu_shader_keyframe_shape";
case GPU_SHADER_SIMPLE_LIGHTING:
return "gpu_shader_simple_lighting";
case GPU_SHADER_3D_IMAGE:
return "gpu_shader_3D_image";
case GPU_SHADER_3D_IMAGE_SCENE_LINEAR_TO_REC709_SRGB:
return "gpu_shader_3D_image_scene_linear";
case GPU_SHADER_3D_IMAGE_COLOR:
return "gpu_shader_3D_image_color";
case GPU_SHADER_3D_IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB:
return "gpu_shader_3D_image_color_scene_linear";
case GPU_SHADER_2D_CHECKER:
return "gpu_shader_2D_checker";
case GPU_SHADER_2D_DIAG_STRIPES:
return "gpu_shader_2D_diag_stripes";
case GPU_SHADER_ICON:
return "gpu_shader_icon";
case GPU_SHADER_2D_IMAGE_OVERLAYS_MERGE:
return "gpu_shader_2D_image_overlays_merge";
case GPU_SHADER_2D_IMAGE_OVERLAYS_STEREO_MERGE:
return "gpu_shader_2D_image_overlays_stereo_merge";
case GPU_SHADER_2D_IMAGE_DESATURATE_COLOR:
return "gpu_shader_2D_image_desaturate_color";
case GPU_SHADER_2D_IMAGE_SHUFFLE_COLOR:
return "gpu_shader_2D_image_shuffle_color";
case GPU_SHADER_2D_IMAGE_RECT_COLOR:
return "gpu_shader_2D_image_rect_color";
case GPU_SHADER_ICON_MULTI:
return "gpu_shader_icon_multi";
case GPU_SHADER_3D_UNIFORM_COLOR:
return "gpu_shader_3D_uniform_color";
case GPU_SHADER_3D_FLAT_COLOR:
return "gpu_shader_3D_flat_color";
case GPU_SHADER_3D_SMOOTH_COLOR:
return "gpu_shader_3D_smooth_color";
case GPU_SHADER_3D_DEPTH_ONLY:
return "gpu_shader_3D_depth_only";
case GPU_SHADER_3D_CLIPPED_UNIFORM_COLOR:
return "gpu_shader_3D_clipped_uniform_color";
case GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR:
return "gpu_shader_3D_polyline_uniform_color";
case GPU_SHADER_3D_POLYLINE_CLIPPED_UNIFORM_COLOR:
return "gpu_shader_3D_polyline_uniform_color_clipped";
case GPU_SHADER_3D_POLYLINE_FLAT_COLOR:
return "gpu_shader_3D_polyline_flat_color";
case GPU_SHADER_3D_POLYLINE_SMOOTH_COLOR:
return "gpu_shader_3D_polyline_smooth_color";
case GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR:
return "gpu_shader_3D_line_dashed_uniform_color";
case GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA:
return "gpu_shader_2D_point_uniform_size_uniform_color_aa";
case GPU_SHADER_2D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_OUTLINE_AA:
return "gpu_shader_2D_point_uniform_size_uniform_color_outline_aa";
case GPU_SHADER_3D_POINT_VARYING_SIZE_VARYING_COLOR:
return "gpu_shader_3D_point_varying_size_varying_color";
case GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA:
return "gpu_shader_3D_point_uniform_size_uniform_color_aa";
case GPU_SHADER_3D_POINT_FLAT_COLOR:
return "gpu_shader_3D_point_flat_color";
case GPU_SHADER_3D_POINT_UNIFORM_COLOR:
return "gpu_shader_3D_point_uniform_color";
case GPU_SHADER_2D_AREA_BORDERS:
return "gpu_shader_2D_area_borders";
case GPU_SHADER_2D_WIDGET_BASE:
return "gpu_shader_2D_widget_base";
case GPU_SHADER_2D_WIDGET_BASE_INST:
return "gpu_shader_2D_widget_base_inst";
case GPU_SHADER_2D_WIDGET_SHADOW:
return "gpu_shader_2D_widget_shadow";
case GPU_SHADER_2D_NODE_SOCKET:
return "gpu_shader_2D_node_socket";
case GPU_SHADER_2D_NODE_SOCKET_INST:
return "gpu_shader_2D_node_socket_inst";
case GPU_SHADER_2D_NODELINK:
return "gpu_shader_2D_nodelink";
case GPU_SHADER_GPENCIL_STROKE:
return "gpu_shader_gpencil_stroke";
case GPU_SHADER_SEQUENCER_STRIPS:
return "gpu_shader_sequencer_strips";
case GPU_SHADER_SEQUENCER_THUMBS:
return "gpu_shader_sequencer_thumbs";
case GPU_SHADER_SEQUENCER_SCOPE_RASTER:
return "gpu_shader_sequencer_scope_raster";
case GPU_SHADER_SEQUENCER_SCOPE_RESOLVE:
return "gpu_shader_sequencer_scope_resolve";
case GPU_SHADER_SEQUENCER_ZEBRA:
return "gpu_shader_sequencer_zebra";
case GPU_SHADER_INDEXBUF_POINTS:
return "gpu_shader_index_2d_array_points";
case GPU_SHADER_INDEXBUF_LINES:
return "gpu_shader_index_2d_array_lines";
case GPU_SHADER_INDEXBUF_TRIS:
return "gpu_shader_index_2d_array_tris";
case GPU_SHADER_2D_UPDATE_MIPMAPS_UNORM_8:
return "gpu_shader_2D_update_mipmaps_unorm_8";
case GPU_SHADER_2D_UPDATE_MIPMAPS_UNORM_8_LAYERED:
return "gpu_shader_2D_update_mipmaps_unorm_8_layered";
case GPU_SHADER_2D_UPDATE_MIPMAPS_UNORM_8_8_8_8:
return "gpu_shader_2D_update_mipmaps_unorm_8_8_8_8";
case GPU_SHADER_2D_UPDATE_MIPMAPS_UNORM_8_8_8_8_LAYERED:
return "gpu_shader_2D_update_mipmaps_unorm_8_8_8_8_layered";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_16:
return "gpu_shader_2D_update_mipmaps_sfloat_16";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_16_LAYERED:
return "gpu_shader_2D_update_mipmaps_sfloat_16_layered";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_16_16_16_16:
return "gpu_shader_2D_update_mipmaps_sfloat_16_16_16_16";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_16_16_16_16_LAYERED:
return "gpu_shader_2D_update_mipmaps_sfloat_16_16_16_16_layered";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_32:
return "gpu_shader_2D_update_mipmaps_sfloat_32";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_32_LAYERED:
return "gpu_shader_2D_update_mipmaps_sfloat_32_layered";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_32_32_32_32:
return "gpu_shader_2D_update_mipmaps_sfloat_32_32_32_32";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SFLOAT_32_32_32_32_LAYERED:
return "gpu_shader_2D_update_mipmaps_sfloat_32_32_32_32_layered";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SRGBA_8_8_8_8:
return "gpu_shader_2D_update_mipmaps_srgba_8_8_8_8";
case GPU_SHADER_2D_UPDATE_MIPMAPS_SRGBA_8_8_8_8_LAYERED:
return "gpu_shader_2D_update_mipmaps_srgba_8_8_8_8_layered";
default:
BLI_assert_unreachable();
return "";
}
}
static const char *builtin_shader_create_info_name_clipped(GPUBuiltinShader shader)
{
switch (shader) {
case GPU_SHADER_3D_UNIFORM_COLOR:
return "gpu_shader_3D_uniform_color_clipped";
case GPU_SHADER_3D_FLAT_COLOR:
return "gpu_shader_3D_flat_color_clipped";
case GPU_SHADER_3D_SMOOTH_COLOR:
return "gpu_shader_3D_smooth_color_clipped";
case GPU_SHADER_3D_DEPTH_ONLY:
return "gpu_shader_3D_depth_only_clipped";
case GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR:
return "gpu_shader_3D_line_dashed_uniform_color_clipped";
case GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA:
return "gpu_shader_3D_point_uniform_size_uniform_color_aa_clipped";
case GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR:
return "gpu_shader_3D_polyline_uniform_color_clipped";
default:
BLI_assert_msg(false, "Clipped shader configuration not available.");
return "";
}
}
gpu::Shader *GPU_shader_get_builtin_shader_with_config(GPUBuiltinShader shader,
GPUShaderConfig sh_cfg)
{
BLI_assert(shader < GPU_SHADER_BUILTIN_LEN);
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Warray-bounds"
#endif
BuiltinShader **sh_p = &builtin_shaders[sh_cfg][shader];
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
if (*sh_p == nullptr) {
if (sh_cfg == GPU_SHADER_CFG_DEFAULT) {
/* Common case. */
const char *info_name = builtin_shader_create_info_name(shader);
*sh_p = MEM_new<BuiltinShader>(__func__, info_name);
}
else if (sh_cfg == GPU_SHADER_CFG_CLIPPED) {
/* In rare cases geometry shaders calculate clipping themselves. */
const char *info_name_clipped = builtin_shader_create_info_name_clipped(shader);
if (!StringRefNull(info_name_clipped).is_empty()) {
*sh_p = MEM_new<BuiltinShader>(__func__, info_name_clipped);
}
}
else {
BLI_assert(0);
}
}
if ((*sh_p)->init == false) {
(*sh_p)->init = true;
if (ELEM(shader,
GPU_SHADER_3D_POLYLINE_CLIPPED_UNIFORM_COLOR,
GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR,
GPU_SHADER_3D_POLYLINE_FLAT_COLOR,
GPU_SHADER_3D_POLYLINE_SMOOTH_COLOR))
{
gpu::Shader *sh = (*sh_p)->get();
/* Set a default value for `lineSmooth`.
* Ideally this value should be set by the caller. */
GPU_shader_bind(sh);
GPU_shader_uniform_1i(sh, "lineSmooth", 1);
/* WORKAROUND: See is_polyline declaration. */
sh->is_polyline = true;
}
}
return (*sh_p)->get();
}
static void gpu_shader_warm_builtin_shader_async(GPUBuiltinShader shader, GPUShaderConfig sh_cfg)
{
BLI_assert(shader < GPU_SHADER_BUILTIN_LEN);
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Warray-bounds"
#endif
BuiltinShader **sh_p = &builtin_shaders[sh_cfg][shader];
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
if (*sh_p == nullptr) {
if (sh_cfg == GPU_SHADER_CFG_DEFAULT) {
/* Common case. */
const char *info_name = builtin_shader_create_info_name(shader);
*sh_p = MEM_new<BuiltinShader>(__func__, info_name);
}
else if (sh_cfg == GPU_SHADER_CFG_CLIPPED) {
/* In rare cases geometry shaders calculate clipping themselves. */
const char *info_name_clipped = builtin_shader_create_info_name_clipped(shader);
if (!StringRefNull(info_name_clipped).is_empty()) {
*sh_p = MEM_new<BuiltinShader>(__func__, info_name_clipped);
}
}
else {
BLI_assert(0);
}
}
(*sh_p)->ensure_compile_async();
}
gpu::Shader *GPU_shader_get_builtin_shader(GPUBuiltinShader shader)
{
return GPU_shader_get_builtin_shader_with_config(shader, GPU_SHADER_CFG_DEFAULT);
}
void GPU_shader_builtin_warm_up()
{
if ((G.debug & G_DEBUG_GPU) && (GPU_backend_get_type() == GPU_BACKEND_OPENGL)) {
/* On some system (Mesa OpenGL), doing this warm up seems to breaks something related to debug
* hooks and makes the Blender application hang. */
return;
}
if (GPU_use_subprocess_compilation() && (GPU_backend_get_type() == GPU_BACKEND_OPENGL)) {
/* The overhead of creating the subprocesses at this exact moment can create bubbles during the
* startup process. It is usually fast enough on OpenGL that we can skip it. */
return;
}
/* Ordered by first usage in default startup screen.
* Adding more to this list will delay the scheduling of engine shaders and increase time to
* first pixel. */
gpu_shader_warm_builtin_shader_async(GPU_SHADER_TEXT, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_2D_WIDGET_BASE, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_3D_UNIFORM_COLOR, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR,
GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_3D_IMAGE_COLOR, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_2D_NODE_SOCKET, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_2D_WIDGET_BASE_INST, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR,
GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_2D_IMAGE_DESATURATE_COLOR,
GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_3D_POLYLINE_SMOOTH_COLOR,
GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_2D_WIDGET_SHADOW, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_2D_DIAG_STRIPES, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_2D_IMAGE_RECT_COLOR, GPU_SHADER_CFG_DEFAULT);
gpu_shader_warm_builtin_shader_async(GPU_SHADER_2D_AREA_BORDERS, GPU_SHADER_CFG_DEFAULT);
}
void GPU_shader_free_builtin_shaders()
{
/* Make sure non is bound before deleting. */
GPU_shader_unbind();
for (int i = 0; i < GPU_SHADER_CFG_LEN; i++) {
for (int j = 0; j < GPU_SHADER_BUILTIN_LEN; j++) {
MEM_SAFE_DELETE(builtin_shaders[i][j]);
}
}
}
} // namespace blender

View File

@@ -0,0 +1,875 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Descriptor type used to define shader structure, resources and interfaces.
*/
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "BKE_global.hh"
#include "GPU_capabilities.hh"
#include "GPU_context.hh"
#include "GPU_platform.hh"
#include "GPU_shader.hh"
#include "gpu_shader_create_info.hh"
#include "gpu_shader_create_info_private.hh"
#include "gpu_shader_dependency_private.hh"
#undef GPU_SHADER_NAMED_INTERFACE_INFO
#undef GPU_SHADER_INTERFACE_INFO
#undef GPU_SHADER_CREATE_INFO
#undef GPU_SHADER_NAMED_INTERFACE_END
#undef GPU_SHADER_INTERFACE_END
#undef GPU_SHADER_CREATE_END
namespace blender {
namespace gpu::shader {
using CreateInfoDictionary = Map<StringRef, ShaderCreateInfo *>;
using InterfaceDictionary = Map<StringRef, StageInterfaceInfo *>;
static CreateInfoDictionary *g_create_infos = nullptr;
static InterfaceDictionary *g_interfaces = nullptr;
/* -------------------------------------------------------------------- */
/** \name Check Backend Support
*
* \{ */
static bool is_vulkan_compatible_interface(const StageInterfaceInfo &iface)
{
if (iface.instance_name.is_empty()) {
return true;
}
bool use_flat = false;
bool use_smooth = false;
bool use_noperspective = false;
for (const StageInterfaceInfo::InOut &attr : iface.inouts) {
switch (attr.interp) {
case Interpolation::FLAT:
use_flat = true;
break;
case Interpolation::SMOOTH:
use_smooth = true;
break;
case Interpolation::NO_PERSPECTIVE:
use_noperspective = true;
break;
}
}
int num_used_interpolation_types = (use_flat ? 1 : 0) + (use_smooth ? 1 : 0) +
(use_noperspective ? 1 : 0);
#if 0
if (num_used_interpolation_types > 1) {
std::cout << "'" << iface.name << "' uses multiple interpolation types\n";
}
#endif
return num_used_interpolation_types <= 1;
}
bool ShaderCreateInfo::is_vulkan_compatible() const
{
/* Vulkan doesn't support setting an interpolation mode per attribute in a struct. */
for (const StageInterfaceInfo *iface : vertex_out_interfaces_) {
if (!is_vulkan_compatible_interface(*iface)) {
return false;
}
}
for (const StageInterfaceInfo *iface : geometry_out_interfaces_) {
if (!is_vulkan_compatible_interface(*iface)) {
return false;
}
}
return true;
}
std::string ShaderCreateInfo::buffer_typename(StringRefNull type_name, bool uniform_buffer) const
{
if (bool(this->builtins_ & BuiltinBits::NO_BUFFER_TYPE_LINTING) || type_name.startswith("int") ||
type_name.startswith("uint") || type_name.startswith("float") ||
type_name.startswith("packed_"))
{
return type_name;
}
return type_name + "_host_shared_" + (uniform_buffer ? "uniform_" : "");
}
/** \} */
ShaderCreateInfo::ShaderCreateInfo(const char *name) : name_(name)
{
bool last_char_is_underscore = false;
/* Escape the shader name to be able to use it inside an identifier. */
for (char &c : name_) {
if (!std::isalnum(c)) {
c = '_';
}
if (c == '_' && last_char_is_underscore) {
/* Avoid GLSL warning about double underscore being reserved. */
c = 'w';
}
last_char_is_underscore = c == '_';
}
}
std::string ShaderCreateInfo::resource_guard_defines(Span<CompilationConstant> constants) const
{
std::string defines;
defines += "#define CREATE_INFO_" + name_ + "\n";
for (const auto &additional_info : additional_infos_) {
const ShaderCreateInfo &info = *reinterpret_cast<const ShaderCreateInfo *>(
gpu_shader_create_info_get(additional_info.name.c_str()));
if (additional_info.conditions.evaluate(constants)) {
defines += info.resource_guard_defines(constants);
}
}
return defines;
}
void ShaderCreateInfo::finalize(const bool recursive)
{
if (finalized_) {
return;
}
finalized_ = true;
auto deps_merged = std::make_unique<Set<StringRefNull>>();
validate_vertex_attributes();
for (const auto &additional_info : additional_infos_) {
/* Fetch create info. */
const ShaderCreateInfo &info = *reinterpret_cast<const ShaderCreateInfo *>(
gpu_shader_create_info_get(additional_info.name.c_str()));
if (recursive) {
const_cast<ShaderCreateInfo &>(info).finalize(recursive);
}
else {
BLI_assert(info.finalized_);
}
interface_names_size_ += info.interface_names_size_;
/* NOTE: EEVEE Materials can result in nested includes. To avoid duplicate
* shader resources, we need to avoid inserting duplicates.
* TODO: Optimize create info preparation to include each individual "additional_info"
* only a single time. */
vertex_inputs_.extend_non_duplicates(info.vertex_inputs_);
fragment_outputs_.extend_non_duplicates(info.fragment_outputs_);
vertex_out_interfaces_.extend_non_duplicates(info.vertex_out_interfaces_);
geometry_out_interfaces_.extend_non_duplicates(info.geometry_out_interfaces_);
subpass_inputs_.extend_non_duplicates(info.subpass_inputs_);
specialization_constants_.extend_non_duplicates(info.specialization_constants_);
compilation_constants_.extend_non_duplicates(info.compilation_constants_);
shared_variables_.extend(info.shared_variables_);
validate_vertex_attributes(&info);
/* Insert with duplicate check. */
push_constants_.extend_non_duplicates(info.push_constants_);
defines_.extend_non_duplicates(info.defines_);
typedef_sources_.extend_non_duplicates(info.typedef_sources_);
for (const auto &res : info.pass_resources_) {
extend_predicate(pass_resources_, res, additional_info.conditions);
}
for (const auto &res : info.batch_resources_) {
extend_predicate(batch_resources_, res, additional_info.conditions);
}
for (const auto &res : info.geometry_resources_) {
extend_predicate(geometry_resources_, res, additional_info.conditions);
}
/* API-specific parameters.
* We will only copy API-specific parameters if they are otherwise unassigned. */
#ifdef WITH_METAL_BACKEND
if (mtl_max_threads_per_threadgroup_ == 0) {
mtl_max_threads_per_threadgroup_ = info.mtl_max_threads_per_threadgroup_;
}
#endif
if (info.early_fragment_test_) {
early_fragment_test_ = true;
depth_write_ = DepthWrite::UNCHANGED;
}
/* Modify depth write if has been changed from default.
* `UNCHANGED` implies gl_FragDepth is not used at all. */
if (info.depth_write_ != DepthWrite::UNCHANGED) {
depth_write_ = info.depth_write_;
}
/* Inherit builtin bits from additional info. */
builtins_ |= info.builtins_;
/* TODO(fclem): We need to reintroduce this check before compiling.
* The issue is that the new SRT paradigm allows for conflicting resources if they are not
* defined at the same time (using compilation constants). */
// validate_merge(info);
if (!deps_merged->add(info.name_)) {
assert_no_overlap(info, false, "additional info already merged via another info");
}
if (info.compute_layout_.local_size_x != -1) {
assert_no_overlap(
info, compute_layout_.local_size_x == -1, "Compute layout already defined");
compute_layout_ = info.compute_layout_;
}
if (!info.vertex_source_.is_empty()) {
assert_no_overlap(info, vertex_source_.is_empty(), "Vertex source already existing");
vertex_source_ = info.vertex_source_;
}
if (!info.geometry_source_.is_empty()) {
assert_no_overlap(info, geometry_source_.is_empty(), "Geometry source already existing");
geometry_source_ = info.geometry_source_;
geometry_layout_ = info.geometry_layout_;
}
if (!info.fragment_source_.is_empty()) {
assert_no_overlap(info, fragment_source_.is_empty(), "Fragment source already existing");
fragment_source_ = info.fragment_source_;
}
if (!info.compute_source_.is_empty()) {
assert_no_overlap(info, compute_source_.is_empty(), "Compute source already existing");
compute_source_ = info.compute_source_;
}
if (info.vertex_entry_fn_ != "main") {
assert_no_overlap(info, vertex_entry_fn_ == "main", "Vertex function already existing");
vertex_entry_fn_ = info.vertex_entry_fn_;
}
if (info.geometry_entry_fn_ != "main") {
assert_no_overlap(info, geometry_entry_fn_ == "main", "Geometry function already existing");
geometry_entry_fn_ = info.geometry_entry_fn_;
}
if (info.fragment_entry_fn_ != "main") {
assert_no_overlap(info, fragment_entry_fn_ == "main", "Fragment function already existing");
fragment_entry_fn_ = info.fragment_entry_fn_;
}
if (info.compute_entry_fn_ != "main") {
assert_no_overlap(info, compute_entry_fn_ == "main", "Compute function already existing");
compute_entry_fn_ = info.compute_entry_fn_;
}
}
if (!geometry_source_.is_empty() && bool(builtins_ & BuiltinBits::LAYER)) {
std::cout << name_
<< ": Validation failed. BuiltinBits::LAYER shouldn't be used with geometry shaders."
<< std::endl;
BLI_assert(0);
}
if (auto_resource_location_) {
int images = 0, samplers = 0, ubos = 0, ssbos = 0;
for (auto &res : batch_resources_) {
set_resource_slot(res, images, samplers, ubos, ssbos);
}
for (auto &res : pass_resources_) {
set_resource_slot(res, images, samplers, ubos, ssbos);
}
for (auto &res : geometry_resources_) {
set_resource_slot(res, images, samplers, ubos, ssbos);
}
}
}
void ShaderCreateInfo::extend_predicate(Vector<Resource, 0> &resource_vector,
ShaderCreateInfo::Resource res_copy,
Span<ConditionFn> additional_conditions) const
{
res_copy.conditions.extend(additional_conditions);
/** IMPORTANT: We keep duplicates until we evaluate the conditions. */
resource_vector.append(res_copy);
};
void ShaderCreateInfo::assert_no_overlap(const ShaderCreateInfo &info,
const bool test,
const StringRefNull error) const
{
if (!test) {
std::cout << name_ << ": Validation failed while merging " << info.name_ << " : ";
std::cout << error << std::endl;
BLI_assert(0);
}
}
void ShaderCreateInfo::set_resource_slot(
Resource &res, int &images, int &samplers, int &ubos, int &ssbos) const
{
switch (res.bind_type) {
case Resource::BindType::UNIFORM_BUFFER:
res.slot = ubos++;
break;
case Resource::BindType::STORAGE_BUFFER:
res.slot = ssbos++;
break;
case Resource::BindType::SAMPLER:
res.slot = samplers++;
break;
case Resource::BindType::IMAGE:
res.slot = images++;
break;
}
}
std::string ShaderCreateInfo::check_error() const
{
std::string error;
/* At least a vertex shader and a fragment shader are required, or only a compute shader. */
if (this->compute_source_.is_empty()) {
if (this->vertex_source_.is_empty()) {
error += "Missing vertex shader in " + this->name_ + ".\n";
}
if (this->fragment_source_.is_empty()) {
error += "Missing fragment shader in " + this->name_ + ".\n";
}
}
else {
if (!this->vertex_source_.is_empty()) {
error += "Compute shader has vertex_source_ shader attached in " + this->name_ + ".\n";
}
if (!this->geometry_source_.is_empty()) {
error += "Compute shader has geometry_source_ shader attached in " + this->name_ + ".\n";
}
if (!this->fragment_source_.is_empty()) {
error += "Compute shader has fragment_source_ shader attached in " + this->name_ + ".\n";
}
}
if (!this->geometry_source_.is_empty()) {
if (flag_is_set(this->builtins_, BuiltinBits::BARYCENTRIC_COORD)) {
error += "Shader " + this->name_ +
" has geometry stage and uses barycentric coordinates. This is not allowed as "
"fallback injects a geometry stage.\n";
}
if (flag_is_set(this->builtins_, BuiltinBits::VIEWPORT_INDEX)) {
error += "Shader " + this->name_ +
" has geometry stage and uses multi-viewport. This is not allowed as "
"fallback injects a geometry stage.\n";
}
if (flag_is_set(this->builtins_, BuiltinBits::LAYER)) {
error += "Shader " + this->name_ +
" has geometry stage and uses layer output. This is not allowed as "
"fallback injects a geometry stage.\n";
}
}
if ((G.debug & G_DEBUG_GPU) == 0) {
return error;
}
if (flag_is_set(this->builtins_,
BuiltinBits::BARYCENTRIC_COORD | BuiltinBits::VIEWPORT_INDEX |
BuiltinBits::LAYER))
{
for (const StageInterfaceInfo *interface : this->vertex_out_interfaces_) {
if (interface->instance_name.is_empty()) {
error += "Shader " + this->name_ + " uses interface " + interface->name +
" that doesn't contain an instance name, but is required for the fallback "
"geometry shader.\n";
}
}
}
for (const StageInterfaceInfo *interface : this->vertex_out_interfaces_) {
for (const StageInterfaceInfo::InOut &inout : interface->inouts) {
if (inout.name.is_array()) {
error += "Shader " + this->name_ + " : \"" + interface->name + "." + inout.name + "\":";
error += " Array types are not allowed in shader stage interfaces.\n";
}
if (ELEM(inout.type, Type::float3x3_t, Type::float4x4_t)) {
error += "Shader " + this->name_ + " : \"" + interface->name + "." + inout.name + "\":";
error += " Matrix types are not allowed in shader stage interfaces.\n";
}
}
}
if (!this->is_vulkan_compatible()) {
error += this->name_ +
" contains a stage interface using an instance name and mixed interpolation modes. "
"This is not compatible with Vulkan and need to be adjusted.\n";
}
/* Validate specialization constants. */
for (int i = 0; i < specialization_constants_.size(); i++) {
for (int j = i + 1; j < specialization_constants_.size(); j++) {
if (specialization_constants_[i].name == specialization_constants_[j].name) {
error += this->name_ + " contains two specialization constants with the name: " +
std::string(specialization_constants_[i].name);
}
}
}
/* Validate compilation constants. */
for (int i = 0; i < compilation_constants_.size(); i++) {
for (int j = i + 1; j < compilation_constants_.size(); j++) {
if (compilation_constants_[i].name == compilation_constants_[j].name) {
error += this->name_ + " contains two compilation constants with the name: " +
std::string(compilation_constants_[i].name);
}
}
}
/* Validate shared variables. */
for (int i = 0; i < shared_variables_.size(); i++) {
for (int j = i + 1; j < shared_variables_.size(); j++) {
if (shared_variables_[i].name == shared_variables_[j].name) {
error += this->name_ + " contains two specialization constants with the name: " +
std::string(shared_variables_[i].name);
}
}
}
/* Check same bind-points usage. */
Set<int> images, samplers, ubos, ssbos;
auto register_resource = [&](const Resource &res) -> bool {
switch (res.bind_type) {
case Resource::BindType::UNIFORM_BUFFER:
return ubos.add(res.slot);
case Resource::BindType::STORAGE_BUFFER:
return ssbos.add(res.slot);
case Resource::BindType::SAMPLER:
return samplers.add(res.slot);
case Resource::BindType::IMAGE:
return images.add(res.slot);
default:
return false;
}
};
auto print_error_msg = [&](const Resource &res, const Vector<Resource> &resources) {
auto print_resource_name = [&](const Resource &res) {
switch (res.bind_type) {
case Resource::BindType::UNIFORM_BUFFER:
error += "Uniform Buffer " + res.uniformbuf.name;
break;
case Resource::BindType::STORAGE_BUFFER:
error += "Storage Buffer " + res.storagebuf.name;
break;
case Resource::BindType::SAMPLER:
error += "Sampler " + res.sampler.name;
break;
case Resource::BindType::IMAGE:
error += "Image " + res.image.name;
break;
default:
error += "Unknown Type";
break;
}
};
for (const Resource &_res : resources) {
if (&res != &_res && res.bind_type == _res.bind_type && res.slot == _res.slot) {
error += name_ + ": Validation failed : Overlapping ";
print_resource_name(res);
error += " and ";
print_resource_name(_res);
error += " at binding location " + std::to_string(res.slot) + "\n";
}
}
};
for (const auto &res : batch_resources_) {
if (register_resource(res) == false) {
print_error_msg(res, resources_get_all_());
}
}
for (const auto &res : pass_resources_) {
if (register_resource(res) == false) {
print_error_msg(res, resources_get_all_());
}
}
for (const auto &res : geometry_resources_) {
if (register_resource(res) == false) {
print_error_msg(res, resources_get_all_());
}
}
return error;
}
void ShaderCreateInfo::validate_merge(const ShaderCreateInfo &other_info)
{
if (!auto_resource_location_) {
/* Check same bind-points usage in OGL. */
Set<int> images, samplers, ubos, ssbos;
auto register_resource = [&](const Resource &res) -> bool {
switch (res.bind_type) {
case Resource::BindType::UNIFORM_BUFFER:
return images.add(res.slot);
case Resource::BindType::STORAGE_BUFFER:
return samplers.add(res.slot);
case Resource::BindType::SAMPLER:
return ubos.add(res.slot);
case Resource::BindType::IMAGE:
return ssbos.add(res.slot);
default:
return false;
}
};
auto print_error_msg = [&](const Resource &res, const Vector<Resource> &resources) {
auto print_resource_name = [&](const Resource &res) {
switch (res.bind_type) {
case Resource::BindType::UNIFORM_BUFFER:
std::cout << "Uniform Buffer " << res.uniformbuf.name;
break;
case Resource::BindType::STORAGE_BUFFER:
std::cout << "Storage Buffer " << res.storagebuf.name;
break;
case Resource::BindType::SAMPLER:
std::cout << "Sampler " << res.sampler.name;
break;
case Resource::BindType::IMAGE:
std::cout << "Image " << res.image.name;
break;
default:
std::cout << "Unknown Type";
break;
}
};
for (const Resource &_res : resources) {
if (&res != &_res && res.bind_type == _res.bind_type && res.slot == _res.slot) {
std::cout << name_ << ": Validation failed : Overlapping ";
print_resource_name(res);
std::cout << " and ";
print_resource_name(_res);
std::cout << " at (" << res.slot << ") while merging " << other_info.name_ << std::endl;
}
}
};
for (auto &res : batch_resources_) {
if (register_resource(res) == false) {
print_error_msg(res, resources_get_all_());
}
}
for (auto &res : pass_resources_) {
if (register_resource(res) == false) {
print_error_msg(res, resources_get_all_());
}
}
for (auto &res : geometry_resources_) {
if (register_resource(res) == false) {
print_error_msg(res, resources_get_all_());
}
}
}
}
void ShaderCreateInfo::validate_vertex_attributes(const ShaderCreateInfo *other_info)
{
uint32_t attr_bits = 0;
for (auto &attr : vertex_inputs_) {
if (attr.type == Type::float3x3_t) {
std::cout << name_ << ": \"" << attr.name << "\" : float3x3 unsupported as vertex attribute."
<< std::endl;
BLI_assert(0);
}
if (attr.type == Type::float4x4_t) {
std::cout << name_ << ": \"" << attr.name << "\" : float4x4 unsupported as vertex attribute."
<< std::endl;
BLI_assert(0);
}
if (attr.name.is_array()) {
std::cout << name_ << ": \"" << attr.name
<< "\" : arrays are unsupported as vertex attribute." << std::endl;
BLI_assert(0);
}
if (attr.index >= 16 || attr.index < 0) {
std::cout << name_ << ": Invalid index for attribute \"" << attr.name << "\"" << std::endl;
BLI_assert(0);
}
uint32_t attr_new = 0;
if (attr.type == Type::float4x4_t) {
for (int i = 0; i < 4; i++) {
attr_new |= 1 << (attr.index + i);
}
}
else {
attr_new |= 1 << attr.index;
}
if ((attr_bits & attr_new) != 0) {
std::cout << name_ << ": Attribute \"" << attr.name
<< "\" overlap one or more index from another attribute."
" Note that mat4 takes up 4 indices.";
if (other_info) {
std::cout << " While merging " << other_info->name_ << std::endl;
}
std::cout << std::endl;
BLI_assert(0);
}
attr_bits |= attr_new;
}
}
} // namespace gpu::shader
using namespace blender::gpu::shader;
#define GPU_SHADER_NAMED_INTERFACE_INFO(_interface, _inst_name) \
StageInterfaceInfo *ptr_##_interface = new StageInterfaceInfo(#_interface, #_inst_name); \
StageInterfaceInfo &_interface = *ptr_##_interface; \
g_interfaces->add_new(#_interface, ptr_##_interface); \
_interface
#define GPU_SHADER_INTERFACE_INFO(_interface) \
StageInterfaceInfo *ptr_##_interface = new StageInterfaceInfo(#_interface); \
StageInterfaceInfo &_interface = *ptr_##_interface; \
g_interfaces->add_new(#_interface, ptr_##_interface); \
_interface
#define GPU_SHADER_CREATE_INFO(_info) \
ShaderCreateInfo *ptr_##_info = new ShaderCreateInfo(#_info); \
ShaderCreateInfo &_info = *ptr_##_info; \
g_create_infos->add_new(#_info, ptr_##_info); \
_info
#define GPU_SHADER_NAMED_INTERFACE_END(_inst_name) ;
#define GPU_SHADER_INTERFACE_END() ;
#define GPU_SHADER_CREATE_END() ;
#ifdef _MSC_VER
/* Disable optimization for this function with MSVC. It does not like the fact
* shaders info are declared in the same function (same basic block or not does
* not change anything).
* Since it is just a function called to register shaders (once),
* the fact it's optimized or not does not matter, it's not on any hot
* code path. */
# pragma optimize("", off)
#endif
/* Split functions to avoid stack overflow on windows. */
static void init_compositor_infos()
{
/* Declare, register and construct the infos. */
#include "glsl_compositor_infos_list.hh"
}
static void init_draw_infos()
{
/* Declare, register and construct the infos. */
#include "glsl_draw_infos_list.hh"
if (GPU_stencil_clasify_buffer_workaround()) {
/* WORKAROUND: Adding a dummy buffer that isn't used fixes a bug inside the Qualcomm driver. */
eevee_deferred_tile_classify.storage_buf(
12, Qualifier::read_write, "uint", "dummy_workaround_buf[]");
}
}
static void init_gpu_infos()
{
/* Declare, register and construct the infos. */
#include "glsl_gpu_infos_list.hh"
}
static void init_ocio_infos()
{
/* Declare, register and construct the infos. */
#include "glsl_ocio_infos_list.hh"
}
static void init_osd_infos()
{
/* Declare, register and construct the infos. */
#ifdef WITH_OPENSUBDIV
# include "glsl_osd_infos_list.hh"
#endif
}
void gpu_shader_create_info_init()
{
g_create_infos = new CreateInfoDictionary();
g_interfaces = new InterfaceDictionary();
init_compositor_infos();
init_draw_infos();
init_gpu_infos();
init_ocio_infos();
init_osd_infos();
for (ShaderCreateInfo *info : g_create_infos->values()) {
info->is_generated_ = false;
info->builtins_ |= gpu_shader_dependency_get_builtins(info->vertex_source_);
info->builtins_ |= gpu_shader_dependency_get_builtins(info->fragment_source_);
info->builtins_ |= gpu_shader_dependency_get_builtins(info->geometry_source_);
info->builtins_ |= gpu_shader_dependency_get_builtins(info->compute_source_);
if (!info->compute_source_.is_empty()) {
info->shared_variables_.extend(
gpu_shader_dependency_get_shared_variables(info->compute_source_));
}
#if GPU_SHADER_PRINTF_ENABLE
const bool is_material_shader = StringRefNull(info->name_).startswith("eevee_surf_");
if (flag_is_set(info->builtins_, BuiltinBits::USE_PRINTF) ||
(gpu_shader_dependency_force_gpu_print_injection() && is_material_shader))
{
info->additional_info("gpu_print");
}
#endif
#ifndef NDEBUG
/* Automatically amend the create info for ease of use of the debug feature. */
if (flag_is_set(info->builtins_, BuiltinBits::USE_DEBUG_DRAW)) {
info->additional_info("draw_debug_draw");
}
#endif
}
for (ShaderCreateInfo *info : g_create_infos->values()) {
info->finalize(true);
}
/* TEST */
// gpu_shader_create_info_compile(nullptr);
}
#ifdef _MSC_VER
# pragma optimize("", on)
#endif
void gpu_shader_create_info_exit()
{
for (auto *value : g_create_infos->values()) {
delete value;
}
delete g_create_infos;
for (auto *value : g_interfaces->values()) {
delete value;
}
delete g_interfaces;
}
bool gpu_shader_create_info_compile_all(const char *name_starts_with_filter)
{
using namespace blender::gpu;
int success = 0;
int skipped_filter = 0;
int skipped = 0;
int total = 0;
Vector<AsyncCompilationHandle> handles;
for (ShaderCreateInfo *info : g_create_infos->values()) {
info->finalize();
if (info->do_static_compilation_) {
if (name_starts_with_filter &&
!StringRefNull(info->name_).startswith(StringRefNull(name_starts_with_filter)))
{
skipped_filter++;
continue;
}
if ((info->metal_backend_only_ && GPU_backend_get_type() != GPU_BACKEND_METAL) ||
(GPU_geometry_shader_support() == false && info->geometry_source_ != nullptr))
{
skipped++;
continue;
}
total++;
handles.append(
GPU_shader_async_compilation(reinterpret_cast<const GPUShaderCreateInfo *>(info)));
}
}
GPU_shader_compiler_wait_for_all();
for (AsyncCompilationHandle handle : handles) {
if (gpu::Shader *result = GPU_shader_async_compilation_finalize(handle)) {
success++;
#if 0 /* TODO(fclem): This is too verbose for now. Make it a cmake option. */
/* Test if any resource is optimized out and print a warning if that's the case. */
/* TODO(fclem): Limit this to OpenGL backend. */
const ShaderInterface *interface = shader->interface;
Vector<ShaderCreateInfo::Resource> all_resources = info->resources_get_all_();
for (ShaderCreateInfo::Resource &res : all_resources) {
StringRefNull name = "";
const ShaderInput *input = nullptr;
switch (res.bind_type) {
case ShaderCreateInfo::Resource::BindType::UNIFORM_BUFFER:
input = interface->ubo_get(res.slot);
name = res.uniformbuf.name;
break;
case ShaderCreateInfo::Resource::BindType::STORAGE_BUFFER:
input = interface->ssbo_get(res.slot);
name = res.storagebuf.name;
break;
case ShaderCreateInfo::Resource::BindType::SAMPLER:
input = interface->texture_get(res.slot);
name = res.sampler.name;
break;
case ShaderCreateInfo::Resource::BindType::IMAGE:
input = interface->texture_get(res.slot);
name = res.image.name;
break;
}
if (input == nullptr) {
std::cerr << "Error: " << info->name_;
std::cerr << ": Resource « " << name << " » not found in the shader interface\n";
}
else if (input->location == -1) {
std::cerr << "Warning: " << info->name_;
std::cerr << ": Resource « " << name << " » is optimized out\n";
}
}
#endif
GPU_shader_free(result);
}
}
printf("Shader Test compilation result: %d / %d passed", success, total);
if (skipped_filter > 0) {
printf(" (skipped %d when filtering)", skipped_filter);
}
if (skipped > 0) {
printf(" (skipped %d for compatibility reasons)", skipped);
}
printf("\n");
return success == total;
}
const GPUShaderCreateInfo *gpu_shader_create_info_get(const char *info_name)
{
if (g_create_infos->contains(info_name) == false) {
printf("Error: Cannot find shader create info named \"%s\"\n", info_name);
return nullptr;
}
ShaderCreateInfo *info = g_create_infos->lookup(info_name);
return reinterpret_cast<const GPUShaderCreateInfo *>(info);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "GPU_primitive.hh"
#include "gpu_state_private.hh"
#include "gpu_texture_private.hh"
namespace blender::gpu::shader {
/**
* \brief Description of a graphical pipeline to pre-compile during shader creation.
*/
struct PipelineState {
Vector<SpecializationConstant::Value> specialization_constants_;
/* Vertex input */
GPUPrimType primitive_;
/* Pre-fragment and Fragment stage. */
GPUState state_ = {{GPU_WRITE_COLOR}};
uint32_t viewport_count_;
/* Attachment formats. */
TextureTargetFormat depth_format_;
TextureTargetFormat stencil_format_;
Vector<TextureTargetFormat> color_formats_;
using Self = PipelineState;
Self &state(GPUWriteMask write_mask,
GPUBlend blend,
GPUFaceCullTest culling_test,
GPUDepthTest depth_test,
GPUStencilTest stencil_test,
GPUStencilOp stencil_op,
GPUProvokingVertex provoking_vert)
{
state_.write_mask = write_mask;
state_.blend = blend;
state_.culling_test = culling_test;
state_.depth_test = depth_test;
state_.stencil_test = stencil_test;
state_.stencil_op = stencil_op;
state_.provoking_vert = provoking_vert;
return *this;
}
Self &logic_op_xor()
{
state_.logic_op_xor = 1;
return *this;
}
Self &primitive(GPUPrimType primitive_type)
{
primitive_ = primitive_type;
return *this;
}
Self &viewports(uint32_t viewport_count)
{
viewport_count_ = viewport_count;
return *this;
}
Self &add_specialization_constant(SpecializationConstant::Value specialization_constant)
{
specialization_constants_.append(specialization_constant);
return *this;
}
Self &depth_format(TextureTargetFormat depth_format)
{
BLI_assert(bool(to_format_flag(to_texture_format(depth_format)) & GPU_FORMAT_DEPTH));
depth_format_ = depth_format;
return *this;
}
Self &stencil_format(TextureTargetFormat stencil_format)
{
BLI_assert(bool(to_format_flag(to_texture_format(stencil_format)) & GPU_FORMAT_STENCIL));
stencil_format_ = stencil_format;
return *this;
}
Self &color_format(TextureTargetFormat color_format)
{
BLI_assert(bool(to_format_flag(to_texture_format(color_format)) &
(GPU_FORMAT_STENCIL | GPU_FORMAT_DEPTH)) == false);
color_formats_.append(color_format);
return *this;
}
};
} // namespace blender::gpu::shader

View File

@@ -0,0 +1,31 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Descriptor type used to define shader structure, resources and interfaces.
*
* Some rule of thumb:
* - Do not include anything else than this file in each descriptor file.
*/
#pragma once
#include "GPU_shader.hh"
namespace blender {
void gpu_shader_create_info_init();
void gpu_shader_create_info_exit();
bool gpu_shader_create_info_compile_all(const char *name_starts_with_filter);
/**
* Runtime created shader information is not registered in the dictionary
* and cannot be searched.
*/
const GPUShaderCreateInfo *gpu_shader_create_info_get(const char *info_name);
} // namespace blender

View File

@@ -0,0 +1,417 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_bit_vector.hh"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "shader_tool/lexit/lexit.hh"
namespace blender::gpu {
/* -------------------------------------------------------------------- */
/** \name Streaming Dead Code Eliminator.
* \{ */
/* Token stream that parses (some) symbols definitions, build a graph of usage and then prune
* unused definitions for a given set of entry point functions. */
struct PruningStream {
using Token = lexit::Token;
using TokenType = lexit::TokenType;
using TokenAtom = lexit::TokenAtom;
private:
/* Data to output. Think of it as a lazy string builder. */
Vector<StringRef> stream;
/* Total output length. */
size_t total_length = 0;
/* Ranges of character in the stream to remove when constructing the result. */
Vector<std::pair<int32_t, int32_t>> removals;
/* Simple circular buffer to access last few token data. */
class TokenHistoryBuffer {
private:
std::array<Token, 2> buffer;
int head = 0;
public:
TokenHistoryBuffer(Token invalid) : buffer({invalid, invalid}) {}
void push(Token tok)
{
buffer[head] = tok;
head = (head + 1) % buffer.size();
}
/* Access elements from newest (0) to oldest (count-1). */
const Token &operator[](int index) const
{
BLI_assert(index < buffer.size());
int actual_index = (head + buffer.size() - index - 1) % buffer.size();
return buffer[actual_index];
}
} token_history;
struct FunctionDeclaration {
Token type, name, end;
};
struct FunctionGraph {
Vector<FunctionDeclaration> declarations;
/* Function call (from, to). */
Vector<std::pair<TokenAtom, TokenAtom>> edges;
} graph;
TokenAtom current_fn = 0;
/* State of the parser. Some section have DCE turned off because of unsupported syntax. */
bool enabled_ = true;
int stack_depth = 0;
const TokenAtom return_atom;
const TokenAtom thread_atom;
const TokenAtom device_atom;
/* Bit vector allowing to filter atoms that designate builtin functions or constructors that we
* should avoid creating graph for. */
bits::BitVector<> builtin_atoms;
public:
PruningStream(Token invalid_tok,
TokenAtom return_atom,
TokenAtom thread_atom,
TokenAtom device_atom,
Span<TokenAtom> builtin_atoms)
: token_history(invalid_tok),
return_atom(return_atom),
thread_atom(thread_atom),
device_atom(device_atom),
/* Reserve enough for all atoms, even generated ones during macro expansions. */
builtin_atoms(0x10000)
{
for (auto atom : builtin_atoms) {
/* Atom values can only be separated by at least 2 values. */
this->builtin_atoms[atom / 2].set(true);
}
}
void set_enabled_parsing(bool value)
{
enabled_ = value;
}
/**
* Checks if str is a contiguous continuation of the last inserted string ref.
* If yes, merge it; else append str to the stream.
*/
PruningStream &operator<<(StringRef str)
{
if (str.is_empty()) {
return *this;
}
if (!stream.is_empty()) {
StringRef &last = stream.last();
/* Check if the memory addresses are contiguous */
if (last.data() + last.size() == str.data()) {
last = StringRef(last.data(), last.size() + str.size());
}
else {
stream.append(str);
}
}
else {
stream.append(str);
}
total_length += str.size();
return *this;
}
BLI_NOINLINE void parse_token(const Token tok)
{
if (!enabled_) {
return;
}
parse_token_impl(set_start_char(tok));
}
BLI_NOINLINE void parse_token_range(const Token start, const Token end)
{
if (!enabled_) {
return;
}
const char *start_char = start.str_with_whitespace().data();
Token tok = start;
/* Process first 2 token to avoid branching in the loop. */
parse_token_impl(set_start_char(tok));
if (tok == end) {
return;
}
tok = tok.next();
parse_token_impl(set_start_char(tok, start_char));
if (tok == end) {
return;
}
tok = tok.next();
blender::IndexRange range(int(end) - int(tok) + 1);
Span<TokenType> types(&tok.type(), range.size());
for (int i : range) {
switch (types[i]) {
case TokenType::ParOpen:
/* Fill the needed history JIT. */
token_history.push(set_start_char(tok.next(i).prev(2), start_char));
token_history.push(set_start_char(tok.next(i).prev(1), start_char));
process_function();
break;
case TokenType::BracketOpen:
stack_depth += (current_fn != 0);
break;
case TokenType::BracketClose:
stack_depth -= (current_fn != 0);
ATTR_FALLTHROUGH;
case TokenType::SemiColon:
/* Finding a semicolon in global scope after a function signature means that this is
* a forward declaration. Step out of function in this case. */
register_function_end(set_start_char(tok.next(i), start_char));
break;
default:
break;
}
}
/* Register last two tokens. */
token_history.push(set_start_char(end.prev(), start_char));
token_history.push(set_start_char(end, start_char));
}
std::string str() const
{
if (total_length == 0) {
return {};
}
std::string result;
result.reserve(total_length);
size_t global_offset = 0;
const auto *rem_it = removals.begin();
for (const auto &segment : stream) {
size_t seg_idx = 0;
/* Process the current segment until we reach its end */
while (seg_idx < segment.size()) {
size_t current_global_pos = global_offset + seg_idx;
/* If no active removals remain, copy the rest of the segment. */
if (rem_it == removals.end()) {
result.append(segment, seg_idx, std::string::npos);
break;
}
auto [r_start, r_end] = *rem_it;
/* Safety check: ensure we haven't somehow passed the removal (if unsorted) */
BLI_assert(current_global_pos < r_end);
if (current_global_pos < r_start) {
/* We are before the removal starts.
* Copy until the segment ends OR the removal starts */
size_t len = std::min(size_t(segment.size()) - seg_idx, r_start - current_global_pos);
result.append(segment, seg_idx, len);
seg_idx += len;
continue;
}
/* We are inside the removal range.
* Process until the segment ends OR the removal ends */
size_t len = std::min(size_t(segment.size()) - seg_idx, r_end - current_global_pos);
/* Replace the removed chunk by newlines. */
std::string_view chunk(segment.data() + seg_idx, len);
size_t newlines = std::count(chunk.begin(), chunk.end(), '\n');
result.append(newlines, '\n');
seg_idx += len;
/* If we reached the end of this removal range, move to the next one */
if (global_offset + seg_idx >= r_end) {
++rem_it;
}
}
/* Update global offset before moving to next segment */
global_offset += segment.size();
}
return result;
}
void optimize(const Span<TokenAtom> entry_points)
{
prune_unused_functions(entry_points);
}
private:
Token set_start_char(Token tok, const char *start_offset = nullptr)
{
int offset = start_offset ? tok.str_with_whitespace().data() - start_offset : 0;
tok.flag = int32_t(this->total_length + offset);
return tok;
}
BLI_INLINE_METHOD void parse_token_impl(const Token tok)
{
switch (tok.type()) {
case TokenType::ParOpen:
process_function();
break;
case TokenType::BracketOpen:
stack_depth += (current_fn != 0);
break;
case TokenType::BracketClose:
stack_depth -= (current_fn != 0);
ATTR_FALLTHROUGH;
case TokenType::SemiColon:
/* Finding a semicolon in global scope after a function signature means that this is
* a forward declaration. Step out of function in this case. */
register_function_end(tok);
break;
default:
break;
}
token_history.push(tok);
}
void process_function()
{
Token name_tok = token_history[0];
if (name_tok.type() != TokenType::Word) {
return;
}
Token type_tok = token_history[1];
/* Filter MSL & GLSL specific identifiers that could have confused the parser. */
if (type_tok.atom() == thread_atom || type_tok.atom() == device_atom) {
return;
}
if (builtin_atoms[name_tok.atom() / 2]) {
return;
}
if (type_tok.type() == TokenType::Word && type_tok.atom() != return_atom) {
register_function_declaration(type_tok, name_tok);
}
else if (current_fn != 0) {
register_function_call(name_tok);
}
}
BLI_INLINE_METHOD void register_function_end(Token tok)
{
if (stack_depth == 0 && current_fn != 0) {
graph.declarations.last().end = tok;
current_fn = 0;
}
}
/* Register function declaration at this token position.
* Associate ID with the token string if first encountering the symbol.
* Does not differentiate overloads. */
void register_function_declaration(Token type_tok, Token name_tok)
{
BLI_assert_msg(graph.declarations.is_empty() ||
graph.declarations.last().name.flag != graph.declarations.last().end.flag,
"Missing call to register_function_end");
graph.declarations.append(FunctionDeclaration{type_tok, name_tok, name_tok});
current_fn = name_tok.atom();
}
/* Register a function call made inside the body of a function by creating an edge inside the
* graph. */
void register_function_call(Token name_tok)
{
graph.edges.append_as(current_fn, name_tok.atom());
}
Map<TokenAtom, Vector<TokenAtom>> build_adjacency()
{
Map<TokenAtom, Vector<TokenAtom>> adj;
adj.reserve(graph.declarations.size());
for (const auto &[from, to] : graph.edges) {
adj.lookup_or_add_default(from).append(to);
}
return adj;
}
Set<TokenAtom> compute_used_functions(Span<TokenAtom> &roots)
{
Set<TokenAtom> used;
used.reserve(graph.declarations.size());
auto adj = build_adjacency();
std::vector<TokenAtom> stack;
stack.reserve(64);
for (TokenAtom root : roots) {
if (used.add(root)) {
stack.push_back(root);
}
while (!stack.empty()) {
TokenAtom f = stack.back();
stack.pop_back();
const auto *calls = adj.lookup_ptr(f);
if (calls == nullptr) {
continue;
}
for (TokenAtom callee : *calls) {
if (used.add(callee)) {
stack.push_back(callee);
}
}
}
}
return used;
}
void prune_unused_functions(Span<TokenAtom> entry_points)
{
Set<TokenAtom> used = compute_used_functions(entry_points);
for (auto [type_tok, name_tok, end_tok] : graph.declarations) {
if (used.contains(name_tok.atom())) {
continue;
}
if (name_tok.flag == end_tok.flag) {
removals.clear();
BLI_assert("Bug in parser, function name not detected");
break;
}
remove_range(type_tok.flag, end_tok.flag + 1);
}
}
/* Remove a range of character from the final string but keeping spaces. */
void remove_range(int start_char, int end_char)
{
removals.append_as(start_char, end_char);
}
};
/** \} */
} // namespace blender::gpu

View File

@@ -0,0 +1,696 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Shader source dependency builder that make possible to support #include directive inside the
* shader files.
*/
#include <algorithm>
#include <fmt/format.h>
#include <iomanip>
#include <iostream>
#include <regex>
#include <string>
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "CLG_log.h"
#include "gpu_capabilities_private.hh"
#include "gpu_material_library.hh"
#include "gpu_shader_create_info.hh"
#include "gpu_shader_dependency_private.hh"
#include "gpu_shader_metadata_private.hh"
#ifdef WITH_OPENSUBDIV
# include "opensubdiv_capi_type.hh"
# include "opensubdiv_evaluator_capi.hh"
#endif
#include "../shader_tool/metadata.hh"
#include "../shader_tool/processor.hh"
namespace blender {
extern "C" {
#define SHADER_SOURCE(filename_underscore, filename, filepath) \
extern char datatoc_##filename_underscore[];
#include "glsl_compositor_source_list.h"
#include "glsl_draw_source_list.h"
#include "glsl_gpu_source_list.h"
#include "glsl_ocio_source_list.h"
#ifdef WITH_OPENSUBDIV
# include "glsl_osd_source_list.h"
#endif
#undef SHADER_SOURCE
}
static CLG_LogRef LOG = {"gpu.shader.deps"};
namespace gpu::shader {
static bool g_shader_use_printf = false;
shader::BuiltinBits convert_builtin_bit(shader::metadata::Builtin builtin)
{
using namespace blender::gpu::shader;
using namespace blender::gpu::shader::metadata;
switch (builtin) {
case Builtin::FragCoord:
return BuiltinBits::FRAG_COORD;
case Builtin::FragStencilRef:
return BuiltinBits::STENCIL_REF;
case Builtin::FrontFacing:
return BuiltinBits::FRONT_FACING;
case Builtin::GlobalInvocationID:
return BuiltinBits::GLOBAL_INVOCATION_ID;
case Builtin::InstanceIndex:
case Builtin::BaseInstance:
case Builtin::InstanceID:
return BuiltinBits::INSTANCE_ID;
case Builtin::LocalInvocationID:
return BuiltinBits::LOCAL_INVOCATION_ID;
case Builtin::LocalInvocationIndex:
return BuiltinBits::LOCAL_INVOCATION_INDEX;
case Builtin::NumWorkGroup:
return BuiltinBits::NUM_WORK_GROUP;
case Builtin::PointCoord:
return BuiltinBits::POINT_COORD;
case Builtin::PointSize:
return BuiltinBits::POINT_SIZE;
case Builtin::PrimitiveID:
return BuiltinBits::PRIMITIVE_ID;
case Builtin::VertexID:
return BuiltinBits::VERTEX_ID;
case Builtin::WorkGroupID:
return BuiltinBits::WORK_GROUP_ID;
case Builtin::WorkGroupSize:
return BuiltinBits::WORK_GROUP_SIZE;
case Builtin::ClipDistance:
return BuiltinBits::CLIP_DISTANCES;
case Builtin::drw_debug:
#ifndef NDEBUG
return BuiltinBits::USE_DEBUG_DRAW;
#else
return BuiltinBits::NONE;
#endif
case Builtin::assert:
case Builtin::printf:
#if GPU_SHADER_PRINTF_ENABLE
g_shader_use_printf = true;
return BuiltinBits::USE_PRINTF;
#else
return BuiltinBits::NONE;
#endif
case Builtin::runtime_generated:
return BuiltinBits::RUNTIME_GENERATED;
}
BLI_assert_unreachable();
return BuiltinBits::NONE;
}
} // namespace gpu::shader
namespace gpu {
using GPUPrintFormatMap = Map<uint32_t, shader::PrintfFormat>;
using GPUSourceDictionary = Map<StringRef, GPUSource *>;
using GPUFunctionDictionary = Map<StringRef, GPUFunction *>;
struct GPUSource {
StringRefNull fullpath;
StringRefNull filename;
StringRefNull source;
std::string patched_source;
Vector<StringRef> dependencies_names;
Vector<GPUSource *> dependencies;
bool dependencies_init = false;
shader::BuiltinBits builtins = shader::BuiltinBits::NONE;
/* True if this file content is supposed to be generated at runtime. */
bool generated = false;
Vector<shader::ShaderCreateInfo::SharedVariable, 0> shared_variables;
/* NOTE: The next few functions are needed to keep isolation of the preprocessor.
* Eventually, this should be revisited and the preprocessor should output
* GPU structures. */
GPUFunctionQual convert_qualifier(shader::metadata::Qualifier qualifier)
{
using namespace blender::gpu::shader;
switch (qualifier) {
case metadata::Qualifier::in:
return FUNCTION_QUAL_IN;
case metadata::Qualifier::out:
return FUNCTION_QUAL_OUT;
case metadata::Qualifier::inout:
return FUNCTION_QUAL_INOUT;
}
BLI_assert_unreachable();
return FUNCTION_QUAL_IN;
}
GPUType convert_type(shader::metadata::Type type)
{
using namespace blender::gpu::shader;
switch (type) {
case metadata::Type::float1:
return GPU_FLOAT;
case metadata::Type::float2:
return GPU_VEC2;
case metadata::Type::float3:
return GPU_VEC3;
case metadata::Type::float4:
return GPU_VEC4;
case metadata::Type::float3x3:
return GPU_MAT3;
case metadata::Type::float4x4:
return GPU_MAT4;
case metadata::Type::sampler1DArray:
return GPU_TEX1D_ARRAY;
case metadata::Type::sampler2DArray:
return GPU_TEX2D_ARRAY;
case metadata::Type::sampler2D:
return GPU_TEX2D;
case metadata::Type::sampler3D:
return GPU_TEX3D;
case metadata::Type::Closure:
return GPU_CLOSURE;
}
BLI_assert_unreachable();
return GPU_NONE;
}
GPUSource(
const char *path,
const char *file,
const char *datatoc,
GPUFunctionDictionary *g_functions,
GPUPrintFormatMap *g_formats,
std::function<void(GPUSource &, GPUFunctionDictionary *, GPUPrintFormatMap *)> metadata_fn)
: fullpath(path), filename(file), source(datatoc)
{
BLI_assert_msg(source.find("//") == std::string::npos &&
source.find("/*") == std::string::npos,
"Input source should have no comments.");
metadata_fn(*this, g_functions, g_formats);
};
void add_builtin(shader::metadata::Builtin builtin)
{
builtins |= shader::convert_builtin_bit(builtin);
}
void add_dependency(StringRef line)
{
dependencies_names.append(line);
}
void add_shared_variable(const shader::Type type, const StringRefNull name)
{
shared_variables.append({type, name});
}
void add_printf_format(uint32_t format_hash, std::string format, GPUPrintFormatMap *format_map)
{
/* TODO(fclem): Move this to gpu log. */
if (format_map->contains(format_hash)) {
if (format_map->lookup(format_hash).format_str != format) {
print_error(format, 0, "printf format hash collision.");
}
else {
/* The format map already have the same format. */
}
}
else {
shader::PrintfFormat fmt;
/* Save for hash collision comparison. */
fmt.format_str = format;
/* Escape characters replacement. Do the most common ones. */
format = std::regex_replace(format, std::regex(R"(\\n)"), "\n");
format = std::regex_replace(format, std::regex(R"(\\v)"), "\v");
format = std::regex_replace(format, std::regex(R"(\\t)"), "\t");
format = std::regex_replace(format, std::regex(R"(\\')"), "\'");
format = std::regex_replace(format, std::regex(R"(\\")"), "\"");
format = std::regex_replace(format, std::regex(R"(\\\\)"), "\\");
shader::PrintfFormat::Block::ArgumentType type =
shader::PrintfFormat::Block::ArgumentType::NONE;
int64_t start = 0, end = 0, cursor = -1;
while ((end = format.find_first_of('%', cursor + 1)) != -1) {
if (end - start > 0) {
/* Add the previous block without the newly found % character. */
fmt.format_blocks.append({type, format.substr(start, end - start)});
}
/* Format type of the next block. */
/* TODO(fclem): This doesn't support advance formats like `%3.2f`. */
switch (format[end + 1]) {
case 's':
type = shader::PrintfFormat::Block::ArgumentType::STRING;
break;
case 'x':
case 'u':
type = shader::PrintfFormat::Block::ArgumentType::UINT;
break;
case 'd':
type = shader::PrintfFormat::Block::ArgumentType::INT;
break;
case 'f':
type = shader::PrintfFormat::Block::ArgumentType::FLOAT;
break;
default:
BLI_assert_msg(0, "Printing format unsupported");
break;
}
/* Start of the next block. */
start = end;
cursor = end;
}
fmt.format_blocks.append({type, format.substr(start, format.size() - start)});
format_map->add(format_hash, fmt);
}
}
void add_function(StringRefNull name,
Span<shader::metadata::ArgumentFormat> arguments,
GPUFunctionDictionary *g_functions)
{
GPUFunction *func = MEM_new<GPUFunction>(__func__);
name.copy_utf8_truncated(func->name, sizeof(func->name));
func->source = reinterpret_cast<void *>(this);
func->totparam = 0;
for (auto arg : arguments) {
if (func->totparam >= ARRAY_SIZE(func->paramtype)) {
print_error(source, source.find(name), "Too many parameters in function");
break;
}
func->paramqual[func->totparam] = convert_qualifier(arg.qualifier);
func->paramtype[func->totparam] = convert_type(arg.type);
func->totparam++;
}
bool insert = g_functions->add(func->name, func);
/* NOTE: We allow overloading non void function, but only if the function comes from the
* same file. Otherwise the dependency system breaks. */
if (!insert) {
GPUSource *other_source = reinterpret_cast<GPUSource *>(g_functions->lookup(name)->source);
if (other_source != this) {
const char *msg = "Function redefinition or overload in two different files ...";
print_error(source, source.find(name), msg);
print_error(other_source->source,
other_source->source.find(name),
"... previous definition was here");
}
else {
/* Non-void function overload. */
MEM_delete(func);
}
}
}
void print_error(const StringRef &input, int64_t offset, const StringRef message)
{
StringRef sub = input.substr(0, offset);
int64_t line_number = std::count(sub.begin(), sub.end(), '\n') + 1;
int64_t line_end = input.find("\n", offset);
int64_t line_start = input.rfind("\n", offset) + 1;
int64_t char_number = offset - line_start + 1;
/* TODO Use clog. */
std::cerr << fullpath << ":" << line_number << ":" << char_number;
std::cerr << " error: " << message << "\n";
std::cerr << std::setw(5) << line_number << " | "
<< input.substr(line_start, line_end - line_start) << "\n";
std::cerr << " | ";
for (int64_t i = 0; i < char_number - 1; i++) {
std::cerr << " ";
}
std::cerr << "^\n";
}
/* Return 1 one error. */
int init_dependencies(const GPUSourceDictionary &dict)
{
if (this->dependencies_init) {
return 0;
}
this->dependencies_init = true;
using namespace shader;
/* Auto dependency injection for debug capabilities. */
if (flag_is_set(builtins, BuiltinBits::USE_PRINTF)) {
dependencies.append_non_duplicates(dict.lookup("gpu_shader_print_lib.glsl"));
}
if (flag_is_set(builtins, BuiltinBits::USE_DEBUG_DRAW)) {
dependencies.append_non_duplicates(dict.lookup("draw_debug_draw_lib.glsl"));
}
for (auto dependency_name : dependencies_names) {
GPUSource *dependency_source = dict.lookup_default(dependency_name, nullptr);
if (dependency_source == nullptr) {
std::string error = std::string("Dependency not found : ") + dependency_name;
print_error(source, 0, error.c_str());
return 1;
}
/* Recursive. */
int result = dependency_source->init_dependencies(dict);
if (result != 0) {
return 1;
}
dependencies.append_non_duplicates(dependency_source);
this->shared_variables.extend(dependency_source->shared_variables);
}
dependencies_names.clear();
return 0;
}
void source_get(Vector<StringRefNull> &result,
const shader::GeneratedSourceList &generated_sources,
const GPUSourceDictionary &dict,
const GPUSource &from) const
{
#define CLOG_FILE_INCLUDE(_from, _include) \
if (CLOG_CHECK(&LOG, CLG_LEVEL_TRACE) && \
(from).filename.c_str() != (_include).filename.c_str()) { \
const char *from_filename = (_from).filename.c_str(); \
const char *include_filename = (_include).filename.c_str(); \
const int from_size = int((_from).source.size()); \
const int include_size = int((_include).source.size()); \
std::string link = fmt::format( \
"{}_{} --> {}_{}\n", from_filename, from_size, include_filename, include_size); \
std::string style = fmt::format("style {}_{} fill:#{:x}{:x}0\n", \
include_filename, \
include_size, \
min_uu(15, include_size / 1000), \
15 - min_uu(15, include_size / 1000)); \
CLG_log_raw(LOG.type, link.c_str()); \
CLG_log_raw(LOG.type, style.c_str()); \
}
/* Check if this file was already included. */
for (const StringRefNull &source_content : result) {
/* Yes, compare pointer instead of string for speed.
* Each source is guaranteed to be unique and non-moving during the building process. */
if (source_content.c_str() == this->source.c_str()) {
/* Already included. */
CLOG_FILE_INCLUDE(from, *this);
return;
}
}
if (!flag_is_set(this->builtins, shader::BuiltinBits::RUNTIME_GENERATED)) {
for (const auto &dependency : this->dependencies) {
/* WATCH: Recursive. */
dependency->source_get(result, generated_sources, dict, *this);
}
CLOG_FILE_INCLUDE(from, *this);
result.append(this->source);
return;
}
/* Linear lookup since we won't have more than a few per shaders.
* Also avoid the complexity of a Map in info creation. */
for (const shader::GeneratedSource &generated_src : generated_sources) {
if (generated_src.filename == this->filename) {
/* Include dependencies before the generated file. */
for (const auto &dependency_name : generated_src.dependencies) {
BLI_assert_msg(dependency_name != this->filename, "Recursive include");
GPUSource *dependency_source = dict.lookup_default(dependency_name, nullptr);
if (dependency_source == nullptr) {
/* Will certainly fail compilation. But avoid crashing the application. */
std::cerr << "Generated dependency not found : " + dependency_name << std::endl;
return;
}
/* WATCH: Recursive. */
dependency_source->source_get(result, generated_sources, dict, *this);
}
CLOG_FILE_INCLUDE(from, *this);
result.append(generated_src.content);
return;
}
}
std::cerr << "warn: Generated source not provided. Using fallback for : " << this->filename
<< std::endl;
/* Dependencies for generated sources are not folded on startup.
* This allows for different set of dependencies at runtime. */
for (const auto &dependency : this->dependencies) {
/* WATCH: Recursive. */
dependency->source_get(result, generated_sources, dict, *this);
}
CLOG_FILE_INCLUDE(from, *this);
result.append(this->source);
}
/* Returns the final string with all includes done. */
void build(Vector<StringRefNull> &result,
const shader::GeneratedSourceList &generated_sources,
const GPUSourceDictionary &dict) const
{
source_get(result, generated_sources, dict, *this);
}
shader::BuiltinBits builtins_get() const
{
shader::BuiltinBits out_builtins = builtins;
for (auto *dep : dependencies) {
out_builtins |= dep->builtins_get();
}
return out_builtins;
}
bool is_from_material_library() const
{
return (filename.startswith("gpu_shader_material_") ||
filename.startswith("gpu_shader_common_") ||
filename.startswith("gpu_shader_compositor_")) &&
filename.endswith(".glsl");
}
};
namespace shader {
#include "glsl_compositor_metadata_list.hh"
#include "glsl_draw_metadata_list.hh"
#include "glsl_gpu_metadata_list.hh"
#include "glsl_ocio_metadata_list.hh"
#ifdef WITH_OPENSUBDIV
# include "glsl_osd_metadata_list.hh"
#endif
} // namespace shader
} // namespace gpu
using namespace blender::gpu;
static GPUPrintFormatMap *g_formats = nullptr;
static GPUSourceDictionary *g_sources = nullptr;
static GPUFunctionDictionary *g_functions = nullptr;
static bool force_printf_injection = false;
#ifdef WITH_OPENSUBDIV
/* Using a global string to avoid dealing with memory allocation/ownership. */
static std::string osd_patch_basis;
#endif
void gpu_shader_dependency_init()
{
g_formats = new GPUPrintFormatMap();
g_sources = new GPUSourceDictionary();
g_functions = new GPUFunctionDictionary();
#define SHADER_SOURCE(filename_underscore, filename, filepath) \
g_sources->add_new(filename, \
new GPUSource(filepath, \
filename, \
datatoc_##filename_underscore, \
g_functions, \
g_formats, \
gpu::shader::metadata_##filename_underscore));
#include "glsl_compositor_source_list.h"
#include "glsl_draw_source_list.h"
#include "glsl_gpu_source_list.h"
#include "glsl_ocio_source_list.h"
#ifdef WITH_OPENSUBDIV
# include "glsl_osd_source_list.h"
#endif
#undef SHADER_SOURCE
#ifdef WITH_OPENSUBDIV
osd_patch_basis = openSubdiv_getGLSLPatchBasisSource();
osd_patch_basis = shader::SourceProcessor(
osd_patch_basis, "osd_patch_basis.glsl", gpu::shader::Language::GLSL)
.remove_comments();
auto source_ptr_opt = g_sources->pop_try("osd_patch_basis.glsl");
if (source_ptr_opt) {
delete source_ptr_opt.value();
}
g_sources->add_new(
"osd_patch_basis.glsl",
new GPUSource("osd_patch_basis.glsl",
"osd_patch_basis.glsl",
(osd_patch_basis).c_str(),
g_functions,
g_formats,
[](GPUSource &, GPUFunctionDictionary *, GPUPrintFormatMap *) {}));
#endif
int errors = 0;
for (auto *value : g_sources->values()) {
errors += value->init_dependencies(*g_sources);
}
BLI_assert_msg(errors == 0, "Dependency errors detected: Aborting");
UNUSED_VARS_NDEBUG(errors);
#if GPU_SHADER_PRINTF_ENABLE
if (!g_formats->is_empty()) {
/* Detect if there is any printf in node lib files.
* See gpu_shader_dependency_force_gpu_print_injection(). */
for (auto *value : g_sources->values()) {
if (flag_is_set(value->builtins, shader::BuiltinBits::USE_PRINTF)) {
if (value->filename.startswith("gpu_shader_material_")) {
force_printf_injection = true;
break;
}
}
}
}
#endif
}
void gpu_shader_dependency_exit()
{
for (auto *value : g_sources->values()) {
delete value;
}
for (auto *value : g_functions->values()) {
MEM_delete(value);
}
delete g_formats;
delete g_sources;
delete g_functions;
g_formats = nullptr;
g_sources = nullptr;
g_functions = nullptr;
}
GPUFunction *gpu_material_library_get_function(const char *name)
{
GPUFunction *function = g_functions->lookup_default(name, nullptr);
BLI_assert_msg(function != nullptr, "Requested function not in the function library");
return function;
}
void gpu_material_library_use_function(Set<StringRefNull> &used_libraries, const char *name)
{
GPUFunction *function = g_functions->lookup_default(name, nullptr);
BLI_assert_msg(function != nullptr, "Requested function not in the function library");
GPUSource *source = reinterpret_cast<GPUSource *>(function->source);
used_libraries.add(source->filename.c_str());
}
namespace gpu::shader {
bool gpu_shader_dependency_force_gpu_print_injection()
{
/* WORKAROUND: We cannot know what shader will require printing if the printf is inside shader
* node code. In this case, we just force injection inside all shaders. */
return force_printf_injection;
}
bool gpu_shader_dependency_has_printf()
{
return (g_formats != nullptr) && g_shader_use_printf;
}
const PrintfFormat &gpu_shader_dependency_get_printf_format(uint32_t format_hash)
{
return g_formats->lookup(format_hash);
}
BuiltinBits gpu_shader_dependency_get_builtins(const StringRefNull shader_source_name)
{
if (shader_source_name.is_empty()) {
return shader::BuiltinBits::NONE;
}
if (g_sources->contains(shader_source_name) == false) {
std::cerr << "Error: Could not find \"" << shader_source_name
<< "\" in the list of registered source.\n";
BLI_assert(0);
return shader::BuiltinBits::NONE;
}
GPUSource *source = g_sources->lookup(shader_source_name);
return source->builtins_get();
}
Span<ShaderCreateInfo::SharedVariable> gpu_shader_dependency_get_shared_variables(
const StringRefNull shader_source_name)
{
if (shader_source_name.is_empty()) {
return {};
}
if (g_sources->contains(shader_source_name) == false) {
std::cerr << "Error: Could not find \"" << shader_source_name
<< "\" in the list of registered source.\n";
BLI_assert(0);
return {};
}
GPUSource *source = g_sources->lookup(shader_source_name);
return source->shared_variables;
}
Vector<StringRefNull> gpu_shader_dependency_get_resolved_source(
const StringRefNull shader_source_name,
const shader::GeneratedSourceList &generated_sources,
const StringRefNull shader_name)
{
Vector<StringRefNull> result;
GPUSource *src = g_sources->lookup_default(shader_source_name, nullptr);
if (src == nullptr) {
std::cerr << "Error source not found : " << shader_source_name << std::endl;
}
CLOG_TRACE(&LOG, "Resolved Source Tree (Mermaid flowchart) %s", shader_name.c_str());
if (CLOG_CHECK(&LOG, CLG_LEVEL_TRACE)) {
CLG_log_raw(LOG.type, "flowchart LR\n");
}
src->build(result, generated_sources, *g_sources);
if (CLOG_CHECK(&LOG, CLG_LEVEL_TRACE)) {
CLG_log_raw(LOG.type, "\n");
}
return result;
}
StringRefNull gpu_shader_dependency_get_source(const StringRefNull shader_source_name)
{
GPUSource *src = g_sources->lookup_default(shader_source_name, nullptr);
if (src == nullptr) {
std::cerr << "Error source not found : " << shader_source_name << std::endl;
}
return src->source;
}
StringRefNull gpu_shader_dependency_get_filename_from_source_string(const StringRef source_string)
{
for (auto &source : g_sources->values()) {
if (source->source == source_string) {
return source->filename;
}
}
return "";
}
} // namespace gpu::shader
} // namespace blender

View File

@@ -0,0 +1,68 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Shader source dependency builder that make possible to support #include directive inside the
* shader files.
*/
#pragma once
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "gpu_shader_create_info.hh"
namespace blender {
void gpu_shader_dependency_init();
void gpu_shader_dependency_exit();
namespace gpu::shader {
BuiltinBits gpu_shader_dependency_get_builtins(const StringRefNull source_name);
Span<ShaderCreateInfo::SharedVariable> gpu_shader_dependency_get_shared_variables(
const StringRefNull shader_source_name);
/* Returns true is any shader code has a printf statement. */
bool gpu_shader_dependency_has_printf();
bool gpu_shader_dependency_force_gpu_print_injection();
struct PrintfFormat {
struct Block {
enum ArgumentType {
NONE = 0,
STRING,
UINT,
INT,
FLOAT,
} type = NONE;
std::string fmt;
};
Vector<Block> format_blocks;
std::string format_str;
};
const PrintfFormat &gpu_shader_dependency_get_printf_format(uint32_t format_hash);
Vector<StringRefNull> gpu_shader_dependency_get_resolved_source(
StringRefNull source_name,
const GeneratedSourceList &generated_sources,
StringRefNull shader_name = "");
StringRefNull gpu_shader_dependency_get_source(StringRefNull source_name);
/**
* \brief Find the name of the file from which the given string was generated.
* \return filename or empty string.
* \note source_string needs to be identical to the one given by gpu_shader_dependency_get_source()
*/
StringRefNull gpu_shader_dependency_get_filename_from_source_string(StringRef source_string);
} // namespace gpu::shader
} // namespace blender

View File

@@ -0,0 +1,9 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Intentionally empty for compiling shader builder.
*/

View File

@@ -0,0 +1,148 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU shader interface (C --> GLSL)
*/
#include "MEM_guardedalloc.h"
#include "BLI_span.hh"
#include "BLI_vector.hh"
#include "gpu_shader_create_info.hh"
#include "gpu_shader_interface.hh"
namespace blender::gpu {
/* TODO(fclem): add unique ID for debugging. */
ShaderInterface::ShaderInterface()
{
image_formats_.fill(TextureWriteFormat::Invalid);
}
ShaderInterface::~ShaderInterface()
{
/* Free memory used by name_buffer. */
MEM_SAFE_DELETE(name_buffer_);
MEM_SAFE_DELETE(inputs_);
}
static void sort_input_list(MutableSpan<ShaderInput> dst)
{
if (dst.is_empty()) {
return;
}
Vector<ShaderInput> inputs_vec = Vector<ShaderInput>(dst.size());
MutableSpan<ShaderInput> src = inputs_vec.as_mutable_span();
src.copy_from(dst);
/* Simple sorting by going through the array and selecting the biggest element each time. */
for (uint i = 0; i < dst.size(); i++) {
ShaderInput *input_src = src.data();
for (uint j = 1; j < src.size(); j++) {
if (src[j].name_hash > input_src->name_hash) {
input_src = &src[j];
}
}
dst[i] = *input_src;
input_src->name_hash = 0;
}
}
void ShaderInterface::sort_inputs()
{
/* Sorts all inputs inside their respective array.
* This is to allow fast hash collision detection.
* See `ShaderInterface::input_lookup` for more details. */
uint offset = 0;
sort_input_list(MutableSpan<ShaderInput>(inputs_ + offset, attr_len_));
offset += attr_len_;
sort_input_list(MutableSpan<ShaderInput>(inputs_ + offset, ubo_len_));
offset += ubo_len_;
sort_input_list(MutableSpan<ShaderInput>(inputs_ + offset, uniform_len_));
offset += uniform_len_;
sort_input_list(MutableSpan<ShaderInput>(inputs_ + offset, ssbo_len_));
offset += ssbo_len_;
sort_input_list(MutableSpan<ShaderInput>(inputs_ + offset, constant_len_));
offset += constant_len_;
}
void ShaderInterface::set_image_formats_from_info(const shader::ShaderCreateInfo &info)
{
for (const shader::ShaderCreateInfo::Resource &res : info.pass_resources_) {
if (res.bind_type == shader::ShaderCreateInfo::Resource::BindType::IMAGE) {
image_formats_[res.slot] = TextureWriteFormat(res.image.format);
}
}
for (const shader::ShaderCreateInfo::Resource &res : info.batch_resources_) {
if (res.bind_type == shader::ShaderCreateInfo::Resource::BindType::IMAGE) {
image_formats_[res.slot] = TextureWriteFormat(res.image.format);
}
}
for (const shader::ShaderCreateInfo::Resource &res : info.geometry_resources_) {
if (res.bind_type == shader::ShaderCreateInfo::Resource::BindType::IMAGE) {
image_formats_[res.slot] = TextureWriteFormat(res.image.format);
}
}
}
void ShaderInterface::debug_print() const
{
Span<ShaderInput> attrs = Span<ShaderInput>(inputs_, attr_len_);
Span<ShaderInput> ubos = Span<ShaderInput>(inputs_ + attr_len_, ubo_len_);
Span<ShaderInput> uniforms = Span<ShaderInput>(inputs_ + attr_len_ + ubo_len_, uniform_len_);
Span<ShaderInput> ssbos = Span<ShaderInput>(inputs_ + attr_len_ + ubo_len_ + uniform_len_,
ssbo_len_);
char *name_buf = name_buffer_;
const char format[] = " | %.8x : %4d : %s\n";
if (attrs.size() > 0) {
printf("\n Attributes :\n");
}
for (const ShaderInput &attr : attrs) {
printf(format, attr.name_hash, attr.location, name_buf + attr.name_offset);
}
if (uniforms.size() > 0) {
printf("\n Uniforms :\n");
}
for (const ShaderInput &uni : uniforms) {
/* Bypass samplers. */
if (uni.binding == -1) {
printf(format, uni.name_hash, uni.location, name_buf + uni.name_offset);
}
}
if (ubos.size() > 0) {
printf("\n Uniform Buffer Objects :\n");
}
for (const ShaderInput &ubo : ubos) {
printf(format, ubo.name_hash, ubo.binding, name_buf + ubo.name_offset);
}
if (enabled_tex_mask_ > 0) {
printf("\n Samplers :\n");
}
for (const ShaderInput &samp : uniforms) {
/* Bypass uniforms. */
if (samp.binding != -1) {
printf(format, samp.name_hash, samp.binding, name_buf + samp.name_offset);
}
}
if (ssbos.size() > 0) {
printf("\n Shader Storage Objects :\n");
}
for (const ShaderInput &ssbo : ssbos) {
printf(format, ssbo.name_hash, ssbo.binding, name_buf + ssbo.name_offset);
}
printf("\n");
}
} // namespace blender::gpu

View File

@@ -0,0 +1,360 @@
/* SPDX-FileCopyrightText: 2016 by Mike Erwin. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU shader interface (C --> GLSL)
*
* Structure detailing needed vertex inputs and resources for a specific shader.
* A shader interface can be shared between two similar shaders.
*/
#pragma once
#include <cstring> /* required for STREQ later on. */
#include "BLI_hash.h"
#include "BLI_sys_types.h"
#include "GPU_format.hh"
#include "GPU_shader.hh"
#include "GPU_vertex_format.hh" /* GPU_VERT_ATTR_MAX_LEN */
#include "gpu_shader_create_info.hh"
#include "gpu_texture_private.hh"
namespace blender::gpu {
struct ShaderInput {
uint32_t name_offset;
uint32_t name_hash;
/**
* Location is openGl legacy and its legacy usages should be phased out in Blender 3.7.
*
* Vulkan backend use location to encode the descriptor set binding. This binding is different
* than the binding stored in the binding attribute. In Vulkan the binding inside a descriptor
* set must be unique. In future the location will also be used to select the right descriptor
* set.
*/
int32_t location;
/** Defined at interface creation or in shader. Only for Samplers, UBOs and Vertex Attributes. */
int32_t binding;
};
/**
* Implementation of Shader interface.
* Base class which is then specialized for each implementation (GL, VK, ...).
*/
class ShaderInterface {
friend shader::ShaderCreateInfo;
/* TODO(fclem): should be protected. */
public:
/** Flat array. In this order: Attributes, Ubos, Uniforms, SSBOs, Constants. */
ShaderInput *inputs_ = nullptr;
/** Buffer containing all inputs names separated by '\0'. */
char *name_buffer_ = nullptr;
/** Input counts inside input array. */
uint attr_len_ = 0;
uint ubo_len_ = 0;
uint uniform_len_ = 0;
uint ssbo_len_ = 0;
uint constant_len_ = 0;
/** Enabled bind-points that needs to be fed with data. */
uint16_t enabled_attr_mask_ = 0;
uint16_t enabled_ubo_mask_ = 0;
uint8_t enabled_ima_mask_ = 0;
uint64_t enabled_tex_mask_ = 0;
uint16_t enabled_ssbo_mask_ = 0;
/* Bitmask to apply to enabled_ssbo_mask_ to get attributes that are sourced from SSBOs. */
uint16_t ssbo_attr_mask_ = 0;
/** Location of builtin uniforms. Fast access, no lookup needed. */
int32_t builtins_[GPU_NUM_UNIFORMS];
int32_t builtin_blocks_[GPU_NUM_UNIFORM_BLOCKS];
/**
* Currently only used for `GPU_shader_get_attribute_info`.
* This utility is useful for automatic creation of `GPUVertFormat` in Python.
* Use `ShaderInput::location` to identify the `Type`.
*/
uint8_t attr_types_[GPU_VERT_ATTR_MAX_LEN];
/* Formats of all image units. */
std::array<TextureWriteFormat, GPU_MAX_IMAGE> image_formats_;
ShaderInterface();
virtual ~ShaderInterface();
void debug_print() const;
const ShaderInput *attr_get(const StringRefNull name) const
{
return input_lookup(inputs_, attr_len_, name);
}
const ShaderInput *attr_get(const int binding) const
{
return input_lookup(inputs_, attr_len_, binding);
}
const ShaderInput *ubo_get(const StringRefNull name) const
{
return input_lookup(inputs_ + attr_len_, ubo_len_, name);
}
const ShaderInput *ubo_get(const int binding) const
{
return input_lookup(inputs_ + attr_len_, ubo_len_, binding);
}
const ShaderInput *uniform_get(const StringRefNull name) const
{
return input_lookup(inputs_ + attr_len_ + ubo_len_, uniform_len_, name);
}
const ShaderInput *texture_get(const int binding) const
{
return input_lookup(inputs_ + attr_len_ + ubo_len_, uniform_len_, binding);
}
const ShaderInput *ssbo_get(const StringRefNull name) const
{
return input_lookup(inputs_ + attr_len_ + ubo_len_ + uniform_len_, ssbo_len_, name);
}
const ShaderInput *ssbo_get(const int binding) const
{
return input_lookup(inputs_ + attr_len_ + ubo_len_ + uniform_len_, ssbo_len_, binding);
}
const ShaderInput *constant_get(const StringRefNull name) const
{
return input_lookup(
inputs_ + attr_len_ + ubo_len_ + uniform_len_ + ssbo_len_, constant_len_, name);
}
const char *input_name_get(const ShaderInput *input) const
{
return name_buffer_ + input->name_offset;
}
/* Returns uniform location. */
int32_t uniform_builtin(const GPUUniformBuiltin builtin) const
{
BLI_assert(builtin >= 0 && builtin < GPU_NUM_UNIFORMS);
return builtins_[builtin];
}
/* Returns binding position. */
int32_t ubo_builtin(const GPUUniformBlockBuiltin builtin) const
{
BLI_assert(builtin >= 0 && builtin < GPU_NUM_UNIFORM_BLOCKS);
return builtin_blocks_[builtin];
}
inline uint valid_bindings_get(const ShaderInput *const inputs, const uint inputs_len) const;
bool attr_len_get() const
{
return attr_len_;
}
bool ubo_len_get() const
{
return ubo_len_;
}
bool uniform_len_get() const
{
return uniform_len_;
}
bool ssbo_len_get() const
{
return ssbo_len_;
}
bool constant_len_get() const
{
return constant_len_;
}
protected:
static inline const char *builtin_uniform_name(GPUUniformBuiltin u);
static inline const char *builtin_uniform_block_name(GPUUniformBlockBuiltin u);
inline uint32_t set_input_name(ShaderInput *input, char *name, uint32_t name_len) const;
inline void copy_input_name(ShaderInput *input,
const StringRefNull &name,
char *name_buffer,
uint32_t &name_buffer_offset) const;
/**
* Finalize interface construction by sorting the #ShaderInputs for faster lookups.
*/
void sort_inputs();
void set_image_formats_from_info(const shader::ShaderCreateInfo &info);
private:
inline const ShaderInput *input_lookup(const ShaderInput *const inputs,
uint inputs_len,
StringRefNull name) const;
inline const ShaderInput *input_lookup(const ShaderInput *const inputs,
uint inputs_len,
int binding) const;
};
inline const char *ShaderInterface::builtin_uniform_name(GPUUniformBuiltin u)
{
switch (u) {
case GPU_UNIFORM_MODEL:
return "ModelMatrix";
case GPU_UNIFORM_VIEW:
return "ViewMatrix";
case GPU_UNIFORM_MODELVIEW:
return "ModelViewMatrix";
case GPU_UNIFORM_PROJECTION:
return "ProjectionMatrix";
case GPU_UNIFORM_VIEWPROJECTION:
return "ViewProjectionMatrix";
case GPU_UNIFORM_MVP:
return "ModelViewProjectionMatrix";
case GPU_UNIFORM_MODEL_INV:
return "ModelMatrixInverse";
case GPU_UNIFORM_VIEW_INV:
return "ViewMatrixInverse";
case GPU_UNIFORM_MODELVIEW_INV:
return "ModelViewMatrixInverse";
case GPU_UNIFORM_PROJECTION_INV:
return "ProjectionMatrixInverse";
case GPU_UNIFORM_VIEWPROJECTION_INV:
return "ViewProjectionMatrixInverse";
case GPU_UNIFORM_NORMAL:
return "NormalMatrix";
case GPU_UNIFORM_CLIPPLANES:
return "WorldClipPlanes";
case GPU_UNIFORM_COLOR:
return "color";
case GPU_UNIFORM_BASE_INSTANCE:
return "gpu_BaseInstance";
case GPU_UNIFORM_SRGB_TRANSFORM:
return "srgbTarget";
case GPU_UNIFORM_SCENE_LINEAR_XFORM:
return "gpu_scene_linear_to_rec709";
default:
return nullptr;
}
}
inline const char *ShaderInterface::builtin_uniform_block_name(GPUUniformBlockBuiltin u)
{
switch (u) {
case GPU_UNIFORM_BLOCK_VIEW:
return "viewBlock";
case GPU_UNIFORM_BLOCK_MODEL:
return "modelBlock";
case GPU_UNIFORM_BLOCK_INFO:
return "infoBlock";
case GPU_UNIFORM_BLOCK_DRW_VIEW:
return "drw_view_";
case GPU_UNIFORM_BLOCK_DRW_MODEL:
return "drw_matrices";
case GPU_UNIFORM_BLOCK_DRW_INFOS:
return "drw_infos";
case GPU_UNIFORM_BLOCK_DRW_CLIPPING:
return "drw_clipping_";
default:
return nullptr;
}
}
/* Returns string length including '\0' terminator. */
inline uint32_t ShaderInterface::set_input_name(ShaderInput *input,
char *name,
uint32_t name_len) const
{
/* remove "[0]" from array name */
if (name[name_len - 1] == ']') {
for (; name_len > 1; name_len--) {
if (name[name_len] == '[') {
name[name_len] = '\0';
break;
}
}
}
input->name_offset = uint32_t(name - name_buffer_);
input->name_hash = BLI_hash_string(name);
return name_len + 1; /* include NULL terminator */
}
inline void ShaderInterface::copy_input_name(ShaderInput *input,
const StringRefNull &name,
char *name_buffer,
uint32_t &name_buffer_offset) const
{
uint32_t name_len = name.size();
/* Copy include NULL terminator. */
memcpy(name_buffer + name_buffer_offset, name.c_str(), name_len + 1);
name_buffer_offset += set_input_name(input, name_buffer + name_buffer_offset, name_len);
}
inline const ShaderInput *ShaderInterface::input_lookup(const ShaderInput *const inputs,
const uint inputs_len,
const StringRefNull name) const
{
const uint name_hash = BLI_hash_string(name.c_str());
/* Simple linear search for now. */
for (int i = inputs_len - 1; i >= 0; i--) {
if (inputs[i].name_hash == name_hash) {
if ((i > 0) && UNLIKELY(inputs[i - 1].name_hash == name_hash)) {
/* Hash collision resolve. */
for (; i >= 0 && inputs[i].name_hash == name_hash; i--) {
if (name == (name_buffer_ + inputs[i].name_offset)) {
return inputs + i; /* not found */
}
}
return nullptr; /* not found */
}
/* This is a bit dangerous since we could have a hash collision.
* where the asked uniform that does not exist has the same hash
* as a real uniform. */
BLI_assert(name == (name_buffer_ + inputs[i].name_offset));
return inputs + i;
}
}
return nullptr; /* not found */
}
inline const ShaderInput *ShaderInterface::input_lookup(const ShaderInput *const inputs,
const uint inputs_len,
const int binding) const
{
/* Simple linear search for now. */
for (int i = inputs_len - 1; i >= 0; i--) {
if (inputs[i].binding == binding) {
return inputs + i;
}
}
return nullptr; /* not found */
}
inline uint ShaderInterface::valid_bindings_get(const ShaderInput *const inputs,
const uint inputs_len) const
{
/* Simple linear search for now. */
int valid_bindings = 0;
for (int i = inputs_len - 1; i >= 0; i--) {
if (inputs[i].binding > -1) {
valid_bindings++;
}
}
return valid_bindings;
}
} // namespace blender::gpu

View File

@@ -0,0 +1,466 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "MEM_guardedalloc.h"
#include "BLI_dynstr.h"
#include "BLI_vector.hh"
#include "GPU_storage_buffer.hh"
#include "gpu_context_private.hh"
#include "gpu_shader_dependency_private.hh"
#include "gpu_shader_private.hh"
#include "CLG_log.h"
#include <fmt/format.h>
#include <fmt/ranges.h>
namespace blender {
static CLG_LogRef LOG = {"gpu.shader"};
namespace gpu {
/* -------------------------------------------------------------------- */
/** \name Debug functions
* \{ */
/* Number of lines before and after the error line to print for compilation errors. */
#define DEBUG_CONTEXT_LINES 0
/**
* Print dependencies sources list before the shader report.
* Useful to debug include order or missing dependencies.
*/
#define DEBUG_DEPENDENCIES 0
void Shader::print_log(Span<StringRefNull> sources,
const char *log,
const char *stage,
const bool error,
GPULogParser *parser)
{
const char line_prefix[] = " | ";
char err_col[] = "\033[31;1m";
char warn_col[] = "\033[33;1m";
char info_col[] = "\033[0;2m";
char reset_col[] = "\033[0;0m";
std::string sources_combined = fmt::to_string(fmt::join(sources, ""));
DynStr *dynstr = BLI_dynstr_new();
if (!CLG_color_support_get(&LOG)) {
err_col[0] = warn_col[0] = info_col[0] = reset_col[0] = '\0';
}
BLI_dynstr_appendf(dynstr, "\n");
#if DEBUG_DEPENDENCIES
BLI_dynstr_appendf(
dynstr, "%s%sIncluded files (in order):%s\n", info_col, line_prefix, reset_col);
#endif
Vector<int64_t> sources_end_line;
for (StringRef src : sources) {
int64_t cursor = 0, line_count = 0;
while ((cursor = src.find('\n', cursor) + 1)) {
line_count++;
}
if (sources_end_line.is_empty() == false) {
line_count += sources_end_line.last();
}
sources_end_line.append(line_count);
#if DEBUG_DEPENDENCIES
StringRefNull filename = shader::gpu_shader_dependency_get_filename_from_source_string(src);
if (!filename.is_empty()) {
BLI_dynstr_appendf(
dynstr, "%s%s %s%s\n", info_col, line_prefix, filename.c_str(), reset_col);
}
#endif
}
if (sources_end_line.is_empty()) {
sources_end_line.append(0);
}
const char *log_line = log, *line_end;
LogCursor previous_location;
bool found_line_id = false;
while ((line_end = strchr(log_line, '\n'))) {
/* Skip empty lines. */
if (line_end == log_line) {
log_line++;
continue;
}
/* Silence not useful lines. */
StringRef logref = StringRefNull(log_line).substr(0, size_t(line_end) - size_t(log_line));
if (logref.endswith(" shader failed to compile with the following errors:") ||
logref.endswith(" No code generated"))
{
log_line += size_t(line_end) - size_t(log_line);
continue;
}
GPULogItem log_item;
log_line = parser->parse_line(sources_combined.c_str(), log_line, log_item);
/* Empty line, skip. */
if ((log_item.cursor.row == -1) && ELEM(log_line[0], '\n', '\0')) {
continue;
}
/* Sanitize output. Really bad values can happen when the error line is buggy. */
if (log_item.cursor.source >= sources.size()) {
log_item.cursor.source = -1;
}
if (log_item.cursor.row >= sources_end_line.last()) {
log_item.cursor.source = -1;
log_item.cursor.row = -1;
}
if (log_item.cursor.row == -1) {
found_line_id = false;
}
const char *src_line = sources_combined.c_str();
std::string log_line_str(log_line, (line_end + 1) - log_line);
if (log_line_str.ends_with(" used uninitialized\n")) {
/* Mesa GLSL compiler warns about uninitialized variables are mostly false positive.
* Avoid noise. */
log_line = line_end + 1;
continue;
}
/* Separate from previous block. */
if (previous_location.source != log_item.cursor.source ||
previous_location.row != log_item.cursor.row)
{
BLI_dynstr_appendf(dynstr, "%s%s%s\n", info_col, line_prefix, reset_col);
}
else if (log_item.cursor.column != previous_location.column) {
BLI_dynstr_appendf(dynstr, "%s\n", line_prefix);
}
/* Print line from the source file that is producing the error. */
if ((log_item.cursor.row != -1) && (log_item.cursor.row != previous_location.row ||
log_item.cursor.column != previous_location.column))
{
const char *src_line_end;
found_line_id = false;
/* Lines are 1 based. */
int src_line_index = 1;
while ((src_line_end = strchr(src_line, '\n'))) {
if (src_line_index >= log_item.cursor.row) {
found_line_id = true;
break;
}
if (src_line_index >= log_item.cursor.row - DEBUG_CONTEXT_LINES) {
BLI_dynstr_appendf(dynstr, "%5d | ", src_line_index);
BLI_dynstr_nappend(dynstr, src_line, (src_line_end + 1) - src_line);
}
/* Continue to next line. */
src_line = src_line_end + 1;
src_line_index++;
}
/* Print error source. */
if (found_line_id) {
if (log_item.cursor.row != previous_location.row) {
BLI_dynstr_appendf(dynstr, "%5d | ", src_line_index);
}
else {
BLI_dynstr_appendf(dynstr, line_prefix);
}
BLI_dynstr_nappend(dynstr, src_line, (src_line_end + 1) - src_line);
/* Print char offset. */
BLI_dynstr_appendf(dynstr, line_prefix);
if (log_item.cursor.column != -1) {
for (int i = 0; i < log_item.cursor.column; i++) {
BLI_dynstr_appendf(dynstr, " ");
}
BLI_dynstr_appendf(dynstr, "^");
}
BLI_dynstr_appendf(dynstr, "\n");
/* Skip the error line. */
src_line = src_line_end + 1;
src_line_index++;
while ((src_line_end = strchr(src_line, '\n'))) {
if (src_line_index > log_item.cursor.row + DEBUG_CONTEXT_LINES) {
break;
}
BLI_dynstr_appendf(dynstr, "%5d | ", src_line_index);
BLI_dynstr_nappend(dynstr, src_line, (src_line_end + 1) - src_line);
/* Continue to next line. */
src_line = src_line_end + 1;
src_line_index++;
}
}
}
BLI_dynstr_appendf(dynstr, line_prefix);
/* Search the correct source index. */
int row_in_file = log_item.cursor.row;
int source_index = log_item.cursor.source;
if (source_index <= 0) {
for (auto i : sources_end_line.index_range()) {
if (log_item.cursor.row <= sources_end_line[i]) {
source_index = i;
break;
}
}
}
if (source_index > 0) {
row_in_file -= sources_end_line[source_index - 1];
}
/* Print the filename the error line is coming from. */
if (!log_item.cursor.file_name_and_error_line.empty()) {
char name_buf[256];
std::string string = log_item.cursor.file_name_and_error_line.substr(0,
sizeof(name_buf) - 1);
StringRefNull(string).copy_utf8_truncated(name_buf);
BLI_dynstr_appendf(dynstr, "%s%s: %s", info_col, name_buf, reset_col);
}
else if (source_index > 0) {
StringRefNull filename = shader::gpu_shader_dependency_get_filename_from_source_string(
sources[source_index]);
if (!filename.is_empty()) {
BLI_dynstr_appendf(dynstr,
"%s%s:%d:%d: %s",
info_col,
filename.c_str(),
row_in_file,
log_item.cursor.column + 1,
reset_col);
}
}
if (log_item.severity == Severity::Error) {
BLI_dynstr_appendf(dynstr, "%s%s%s: ", err_col, "Error", info_col);
}
else if (log_item.severity == Severity::Warning) {
BLI_dynstr_appendf(dynstr, "%s%s%s: ", warn_col, "Warning", info_col);
}
else if (log_item.severity == Severity::Note) {
BLI_dynstr_appendf(dynstr, "%s%s%s: ", warn_col, "Note", info_col);
}
/* Print the error itself. */
BLI_dynstr_append(dynstr, info_col);
BLI_dynstr_nappend(dynstr, log_line, (line_end + 1) - log_line);
BLI_dynstr_append(dynstr, reset_col);
/* Continue to next line. */
log_line = line_end + 1;
previous_location = log_item.cursor;
}
CLG_Level level = error ? CLG_LEVEL_ERROR : CLG_LEVEL_WARN;
if (CLOG_CHECK(&LOG, level)) {
if (DEBUG_LOG_SHADER_SRC_ON_ERROR && error) {
CLG_log_raw(LOG.type, sources_combined.c_str());
}
const char *_str = BLI_dynstr_get_cstring(dynstr);
CLOG_AT_LEVEL(&LOG, level, "%s %s: %s", this->name, stage, _str);
MEM_delete(_str);
}
BLI_dynstr_free(dynstr);
}
const char *GPULogParser::skip_severity(const char *log_line,
GPULogItem &log_item,
const char *error_msg,
const char *warning_msg,
const char *note_msg) const
{
if (STREQLEN(log_line, error_msg, strlen(error_msg))) {
log_line += strlen(error_msg);
log_item.severity = Severity::Error;
}
else if (STREQLEN(log_line, warning_msg, strlen(warning_msg))) {
log_line += strlen(warning_msg);
log_item.severity = Severity::Warning;
}
else if (STREQLEN(log_line, note_msg, strlen(note_msg))) {
log_line += strlen(note_msg);
log_item.severity = Severity::Note;
}
return log_line;
}
const char *GPULogParser::skip_separators(const char *log_line, const StringRef separators) const
{
while (at_any(log_line, separators)) {
log_line++;
}
return log_line;
}
const char *GPULogParser::skip_until(const char *log_line, char stop_char) const
{
const char *cursor = log_line;
while (!ELEM(cursor[0], '\n', '\0')) {
if (cursor[0] == stop_char) {
return cursor;
}
cursor++;
}
return log_line;
}
bool GPULogParser::at_number(const char *log_line) const
{
return log_line[0] >= '0' && log_line[0] <= '9';
}
bool GPULogParser::at_any(const char *log_line, const StringRef chars) const
{
return chars.find(log_line[0]) != StringRef::not_found;
}
int GPULogParser::parse_number(const char *log_line, const char **r_new_position) const
{
return int(strtol(log_line, const_cast<char **>(r_new_position), 10));
}
size_t GPULogParser::line_start_get(StringRefNull source_combined, size_t target_line)
{
size_t cursor = 0;
size_t current_line = 1;
for (char c : source_combined) {
if (current_line >= target_line) {
return cursor + 1;
}
if (c == '\n') {
current_line++;
}
cursor++;
}
return -1;
}
StringRef GPULogParser::filename_get(StringRefNull source_combined, size_t pos)
{
StringRef sub_str = source_combined.substr(0, pos);
StringRefNull directive = "#line 1 \"";
size_t nearest_line_directive = sub_str.rfind(directive);
if (nearest_line_directive != std::string::npos) {
size_t start_of_file_name = nearest_line_directive + directive.size();
size_t end_of_file_name = sub_str.find('\"', start_of_file_name);
if (end_of_file_name != std::string::npos) {
return sub_str.substr(start_of_file_name, end_of_file_name - start_of_file_name);
}
}
return {};
}
/* Original source file line. Found by looking up #line directives. */
size_t GPULogParser::source_line_get(StringRefNull source_combined, size_t pos)
{
StringRef sub_str = source_combined.substr(0, pos);
StringRefNull directive = "#line ";
size_t nearest_line_directive = sub_str.rfind(directive);
size_t line_count = 1;
if (nearest_line_directive != std::string::npos) {
sub_str = sub_str.substr(nearest_line_directive + directive.size());
line_count = std::stoll(sub_str) - 1;
}
return line_count + std::count(sub_str.begin(), sub_str.end(), '\n');
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shader Debug Printf
* \{ */
void printf_begin(Context *ctx)
{
#if GPU_SHADER_PRINTF_ENABLE == 0
return;
#endif
if (ctx == nullptr) {
return;
}
if (!shader::gpu_shader_dependency_has_printf()) {
return;
}
StorageBuf *printf_buf = GPU_storagebuf_create(GPU_SHADER_PRINTF_MAX_CAPACITY *
sizeof(uint32_t));
GPU_storagebuf_clear_to_zero(printf_buf);
ctx->printf_buf.append(printf_buf);
}
void printf_end(Context *ctx)
{
#if GPU_SHADER_PRINTF_ENABLE == 0
return;
#endif
if (ctx == nullptr) {
return;
}
if (ctx->printf_buf.is_empty()) {
return;
}
StorageBuf *printf_buf = ctx->printf_buf.pop_last();
Vector<uint32_t> data(GPU_SHADER_PRINTF_MAX_CAPACITY);
GPU_storagebuf_read(printf_buf, data.data());
GPU_storagebuf_free(printf_buf);
uint32_t data_len = data[0];
if (data_len == 0) {
return;
}
/* data[0] contains the length of the data. */
int cursor = 1;
while (cursor < data_len + 1) {
uint32_t format_hash = data[cursor++];
const shader::PrintfFormat &format = shader::gpu_shader_dependency_get_printf_format(
format_hash);
if (cursor + format.format_blocks.size() >= GPU_SHADER_PRINTF_MAX_CAPACITY) {
printf("Printf buffer overflow.\n");
break;
}
for (const shader::PrintfFormat::Block &block : format.format_blocks) {
switch (block.type) {
case shader::PrintfFormat::Block::NONE:
printf("%s", block.fmt.c_str());
break;
case shader::PrintfFormat::Block::STRING: {
/* This is actually just the string that is referenced, not a format. */
const shader::PrintfFormat &str = shader::gpu_shader_dependency_get_printf_format(
*reinterpret_cast<uint32_t *>(&data[cursor++]));
printf(block.fmt.c_str(), str.format_str.c_str());
break;
}
case shader::PrintfFormat::Block::UINT:
printf(block.fmt.c_str(), *reinterpret_cast<uint32_t *>(&data[cursor++]));
break;
case shader::PrintfFormat::Block::INT:
printf(block.fmt.c_str(), *reinterpret_cast<int32_t *>(&data[cursor++]));
break;
case shader::PrintfFormat::Block::FLOAT:
printf(block.fmt.c_str(), *reinterpret_cast<float *>(&data[cursor++]));
break;
default:
BLI_assert_unreachable();
break;
}
}
}
}
/** \} */
} // namespace gpu
} // namespace blender

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* Shader source dependency builder that make possible to support #include directive inside the
* shader files.
*/
#pragma once
#include "gpu_shader_create_info.hh"
#include "shader_tool/metadata.hh"
namespace blender::gpu::shader {
BuiltinBits convert_builtin_bit(metadata::Builtin builtin);
} // namespace blender::gpu::shader

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,297 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_map.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "GPU_shader.hh"
#include "GPU_worker.hh"
#include "gpu_shader_create_info.hh"
#include "gpu_shader_interface.hh"
#include <deque>
#include <string>
namespace blender {
namespace gpu {
class GPULogParser;
class Context;
/* Set to 1 to log the full source of shaders that fail to compile. */
#define DEBUG_LOG_SHADER_SRC_ON_ERROR 0
/**
* Compilation is done on a list of GLSL sources. This list contains placeholders that should be
* provided by the backend shader. These defines contains the locations where the backend can patch
* the sources.
*/
#define SOURCES_INDEX_VERSION 0
#define SOURCES_INDEX_SPECIALIZATION_CONSTANTS 1
struct PatchedShaderCreateInfo {
shader::ShaderCreateInfo info;
shader::ShaderCreateInfoStringCache names;
PatchedShaderCreateInfo(const shader::ShaderCreateInfo &info_) : info(info_) {}
};
/**
* Implementation of shader compilation and uniforms handling.
* Base class which is then specialized for each implementation (GL, VK, ...).
*/
class Shader {
public:
/** Uniform & attribute locations for shader. */
ShaderInterface *interface = nullptr;
/** Bit-set indicating the frame-buffer color attachments that this shader writes to. */
uint16_t fragment_output_bits = 0;
/* Default specialization constants state as defined inside ShaderCreateInfo.
* Should be considered as const after init(). */
std::unique_ptr<const shader::SpecializationConstants> constants;
/* WORKAROUND: True if this shader is a polyline shader and needs an appropriate setup to render.
* Eventually, in the future, we should modify the user code instead of relying on such hacks. */
bool is_polyline = false;
/* WORKAROUND: Skip preprocessor for shader that we can't ensure the content.
* This avoid crashes until we make the preprocessor robust to errors.
* Also the preprocessor doesn't support forward declaration at the moment and needs manual
* tweaking. */
bool skip_preprocessor = false;
protected:
/** For debugging purpose. */
char name[64];
/* Parent shader can be used for shaders which are derived from the same source material.
* The child shader can pull information from its parent to prepare additional resources
* such as PSOs upfront. This enables asynchronous PSO compilation which mitigates stuttering
* when updating new materials. */
Shader *parent_shader_ = nullptr;
/* In some situation, a backend might want to transform the create infos before it is being
* parsed. */
std::unique_ptr<PatchedShaderCreateInfo> patched_info_;
public:
Shader(const char *name);
virtual ~Shader();
virtual void init(const shader::ShaderCreateInfo &info, bool is_codegen_only) = 0;
/* Patch create infos for any additional resources that could be needed. */
virtual const shader::ShaderCreateInfo &patch_create_info(
const shader::ShaderCreateInfo &original_info) = 0;
virtual void vertex_shader_from_glsl(const shader::ShaderCreateInfo &info,
MutableSpan<StringRefNull> sources) = 0;
virtual void geometry_shader_from_glsl(const shader::ShaderCreateInfo &info,
MutableSpan<StringRefNull> sources) = 0;
virtual void fragment_shader_from_glsl(const shader::ShaderCreateInfo &info,
MutableSpan<StringRefNull> sources) = 0;
virtual void compute_shader_from_glsl(const shader::ShaderCreateInfo &info,
MutableSpan<StringRefNull> sources) = 0;
virtual bool finalize(const shader::ShaderCreateInfo *info = nullptr) = 0;
/* Pre-warms PSOs using parent shader's cached PSO descriptors. Limit specifies maximum PSOs to
* warm. If -1, compiles all PSO permutations in parent shader.
*
* See `GPU_shader_warm_cache(..)` in `GPU_shader.hh` for more information. */
virtual void warm_cache(int limit) = 0;
virtual void bind(const shader::SpecializationConstants *constants_state) = 0;
virtual void unbind() = 0;
virtual void uniform_float(int location, int comp_len, int array_size, const float *data) = 0;
virtual void uniform_int(int location, int comp_len, int array_size, const int *data) = 0;
/* Add specialization constant declarations to shader instance. */
void specialization_constants_init(const shader::ShaderCreateInfo &info);
static std::string defines_declare(const shader::ShaderCreateInfo &info);
virtual std::string resources_declare(const shader::ShaderCreateInfo &info) const = 0;
virtual std::string vertex_interface_declare(const shader::ShaderCreateInfo &info) const = 0;
virtual std::string fragment_interface_declare(const shader::ShaderCreateInfo &info) const = 0;
virtual std::string geometry_interface_declare(const shader::ShaderCreateInfo &info) const = 0;
virtual std::string geometry_layout_declare(const shader::ShaderCreateInfo &info) const = 0;
virtual std::string compute_layout_declare(const shader::ShaderCreateInfo &info) const = 0;
StringRefNull name_get() const
{
return name;
}
void parent_set(Shader *parent)
{
parent_shader_ = parent;
}
Shader *parent_get() const
{
return parent_shader_;
}
static void set_scene_linear_to_xyz_uniform(gpu::Shader *shader);
static void set_srgb_uniform(Context *ctx, gpu::Shader *shader);
static void set_framebuffer_srgb_target(int use_srgb_to_linear);
static void dump_source_to_disk(StringRef shader_name,
StringRef shader_name_with_stage_name,
StringRef extension,
StringRef source);
static std::string run_preprocessor(StringRef source, bool no_dead_code_elimination);
protected:
void print_log(Span<StringRefNull> sources,
const char *log,
const char *stage,
bool error,
GPULogParser *parser);
};
class ShaderCompiler {
struct Sources {
std::string vert;
std::string geom;
std::string frag;
std::string comp;
};
struct AsyncCompilation;
struct ParallelWork {
ShaderCompiler *compiler = nullptr;
ShaderCompiler::AsyncCompilation *compilation = nullptr;
WorkID id = 0;
};
struct AsyncCompilation {
Shader *shader = nullptr;
const shader::ShaderCreateInfo *info = nullptr;
std::unique_ptr<ShaderSpecialization> specialization = nullptr;
std::unique_ptr<ParallelWork> work;
std::atomic<bool> is_ready = false;
bool is_specialization()
{
return specialization != nullptr;
}
};
Map<AsyncCompilationHandle, AsyncCompilation *> async_compilations_;
std::mutex mutex_;
std::condition_variable compilation_finished_notification_;
std::unique_ptr<GPUWorker> compilation_worker_;
bool support_specializations_;
static void do_work_static_cb(void *payload);
void do_work(ParallelWork &work);
AsyncCompilationHandle next_handle_ = 1;
bool is_compiling_impl();
bool is_paused_ = false;
std::condition_variable pause_finished_notification_;
protected:
/* Must be called earlier from the destructor of the subclass if the compilation process relies
* on subclass resources. */
void destruct_compilation_worker()
{
compilation_worker_.reset();
}
public:
ShaderCompiler(uint32_t threads_count = 1,
GPUWorker::ContextType context_type = GPUWorker::ContextType::PerThread,
bool support_specializations = false);
virtual ~ShaderCompiler();
Shader *compile(const shader::ShaderCreateInfo &info, bool is_codegen_only);
virtual Shader *compile_shader(const shader::ShaderCreateInfo &info);
virtual void specialize_shader(const ShaderSpecialization & /*specialization*/) {};
AsyncCompilationHandle async_compilation(const shader::ShaderCreateInfo *info,
CompilationPriority priority);
void asyc_compilation_cancel(AsyncCompilationHandle &handle);
bool async_compilation_is_ready(AsyncCompilationHandle handle);
Shader *async_compilation_finalize(AsyncCompilationHandle &handle);
AsyncSpecializationHandle async_specialization(const ShaderSpecialization &specialization,
CompilationPriority priority);
bool async_specialization_is_ready(AsyncSpecializationHandle &handle);
bool is_compiling();
void wait_for_all();
void pause_all();
void continue_all();
};
enum class Severity {
Unknown,
Warning,
Error,
Note,
};
struct LogCursor {
int source = -1;
int row = -1;
int column = -1;
std::string file_name_and_error_line;
};
struct GPULogItem {
LogCursor cursor;
Severity severity = Severity::Unknown;
};
class GPULogParser {
public:
virtual const char *parse_line(const char *source_combined,
const char *log_line,
GPULogItem &log_item) = 0;
protected:
const char *skip_severity(const char *log_line,
GPULogItem &log_item,
const char *error_msg,
const char *warning_msg,
const char *note_msg) const;
const char *skip_separators(const char *log_line, const StringRef separators) const;
const char *skip_until(const char *log_line, char stop_char) const;
bool at_number(const char *log_line) const;
bool at_any(const char *log_line, const StringRef chars) const;
int parse_number(const char *log_line, const char **r_new_position) const;
static size_t line_start_get(StringRefNull source_combined, size_t target_line);
static StringRef filename_get(StringRefNull source_combined, size_t pos);
static size_t source_line_get(StringRefNull source_combined, size_t pos);
MEM_CXX_CLASS_ALLOC_FUNCS("GPULogParser");
};
void printf_begin(Context *ctx);
void printf_end(Context *ctx);
} // namespace gpu
/* XXX do not use it. Special hack to use OCIO with batch API. */
gpu::Shader *immGetShader();
} // namespace blender

View File

@@ -0,0 +1,370 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#ifndef GPU_STANDALONE
# include "DNA_userdef_types.h"
# define PIXELSIZE (U.pixelsize)
#else
# define PIXELSIZE (1.0f)
#endif
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"
#include "GPU_state.hh"
#include "gpu_backend.hh"
#include "gpu_context_private.hh"
#include "gpu_state_private.hh"
namespace blender {
using namespace blender::gpu;
#define SET_STATE(_prefix, _state, _value) \
do { \
StateManager *stack = Context::get()->state_manager; \
auto &state_object = stack->_prefix##state; \
state_object._state = (_value); \
} while (0)
#define SET_IMMUTABLE_STATE(_state, _value) SET_STATE(, _state, _value)
#define SET_MUTABLE_STATE(_state, _value) SET_STATE(mutable_, _state, _value)
/* -------------------------------------------------------------------- */
/** \name Immutable state Setters
* \{ */
void GPU_blend(GPUBlend blend)
{
SET_IMMUTABLE_STATE(blend, blend);
}
void GPU_face_culling(GPUFaceCullTest culling)
{
SET_IMMUTABLE_STATE(culling_test, culling);
}
GPUFaceCullTest GPU_face_culling_get()
{
GPUState &state = Context::get()->state_manager->state;
return GPUFaceCullTest(state.culling_test);
}
void GPU_front_facing(bool invert)
{
SET_IMMUTABLE_STATE(invert_facing, invert);
}
void GPU_provoking_vertex(GPUProvokingVertex vert)
{
SET_IMMUTABLE_STATE(provoking_vert, vert);
}
void GPU_depth_test(GPUDepthTest test)
{
SET_IMMUTABLE_STATE(depth_test, test);
}
void GPU_stencil_test(GPUStencilTest test)
{
SET_IMMUTABLE_STATE(stencil_test, test);
}
void GPU_line_smooth(bool enable)
{
SET_IMMUTABLE_STATE(line_smooth, enable);
}
void GPU_polygon_smooth(bool enable)
{
SET_IMMUTABLE_STATE(polygon_smooth, enable);
}
void GPU_logic_op_xor_set(bool enable)
{
SET_IMMUTABLE_STATE(logic_op_xor, enable);
}
void GPU_write_mask(GPUWriteMask mask)
{
SET_IMMUTABLE_STATE(write_mask, mask);
}
void GPU_color_mask(bool r, bool g, bool b, bool a)
{
StateManager *stack = Context::get()->state_manager;
auto &state = stack->state;
uint32_t write_mask = state.write_mask;
SET_FLAG_FROM_TEST(write_mask, r, uint32_t(GPU_WRITE_RED));
SET_FLAG_FROM_TEST(write_mask, g, uint32_t(GPU_WRITE_GREEN));
SET_FLAG_FROM_TEST(write_mask, b, uint32_t(GPU_WRITE_BLUE));
SET_FLAG_FROM_TEST(write_mask, a, uint32_t(GPU_WRITE_ALPHA));
state.write_mask = write_mask;
}
void GPU_depth_mask(bool depth)
{
StateManager *stack = Context::get()->state_manager;
auto &state = stack->state;
uint32_t write_mask = state.write_mask;
SET_FLAG_FROM_TEST(write_mask, depth, uint32_t(GPU_WRITE_DEPTH));
state.write_mask = write_mask;
}
void GPU_clip_distances(int distances_enabled)
{
SET_IMMUTABLE_STATE(clip_distances, distances_enabled);
}
void GPU_state_set(GPUWriteMask write_mask,
GPUBlend blend,
GPUFaceCullTest culling_test,
GPUDepthTest depth_test,
GPUStencilTest stencil_test,
GPUStencilOp stencil_op,
GPUProvokingVertex provoking_vert)
{
StateManager *stack = Context::get()->state_manager;
auto &state = stack->state;
state.write_mask = uint32_t(write_mask);
state.blend = uint32_t(blend);
state.culling_test = uint32_t(culling_test);
state.depth_test = uint32_t(depth_test);
state.stencil_test = uint32_t(stencil_test);
state.stencil_op = uint32_t(stencil_op);
state.provoking_vert = uint32_t(provoking_vert);
}
void GPU_clip_control_unit_range(bool enable)
{
SET_IMMUTABLE_STATE(clip_control, enable);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mutable State Setters
* \{ */
void GPU_line_width(float width)
{
width = max_ff(1.0f, width * PIXELSIZE);
SET_MUTABLE_STATE(line_width, width);
}
void GPU_point_size(float size)
{
StateManager *stack = Context::get()->state_manager;
auto &state = stack->mutable_state;
/* Keep the sign of point_size since it represents the enable state. */
state.point_size = size * ((state.point_size > 0.0) ? 1.0f : -1.0f);
}
void GPU_program_point_size(bool enable)
{
StateManager *stack = Context::get()->state_manager;
auto &state = stack->mutable_state;
/* Set point size sign negative to disable. */
state.point_size = fabsf(state.point_size) * (enable ? 1 : -1);
}
void GPU_scissor_test(bool enable)
{
Context::get()->active_fb->scissor_test_set(enable);
}
void GPU_scissor(int x, int y, int width, int height)
{
int scissor_rect[4] = {x, y, width, height};
Context::get()->active_fb->scissor_set(scissor_rect);
}
void GPU_viewport(int x, int y, int width, int height)
{
int viewport_rect[4] = {x, y, width, height};
Context::get()->active_fb->viewport_set(viewport_rect);
}
void GPU_stencil_reference_set(uint reference)
{
SET_MUTABLE_STATE(stencil_reference, uint8_t(reference));
}
void GPU_stencil_write_mask_set(uint write_mask)
{
SET_MUTABLE_STATE(stencil_write_mask, uint8_t(write_mask));
}
void GPU_stencil_compare_mask_set(uint compare_mask)
{
SET_MUTABLE_STATE(stencil_compare_mask, uint8_t(compare_mask));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name State Getters
* \{ */
GPUBlend GPU_blend_get()
{
GPUState &state = Context::get()->state_manager->state;
return GPUBlend(state.blend);
}
GPUWriteMask GPU_write_mask_get()
{
GPUState &state = Context::get()->state_manager->state;
return GPUWriteMask(state.write_mask);
}
uint GPU_stencil_mask_get()
{
const GPUStateMutable &state = Context::get()->state_manager->mutable_state;
return state.stencil_write_mask;
}
GPUDepthTest GPU_depth_test_get()
{
GPUState &state = Context::get()->state_manager->state;
return GPUDepthTest(state.depth_test);
}
GPUStencilTest GPU_stencil_test_get()
{
GPUState &state = Context::get()->state_manager->state;
return GPUStencilTest(state.stencil_test);
}
float GPU_line_width_get()
{
const GPUStateMutable &state = Context::get()->state_manager->mutable_state;
return state.line_width;
}
bool GPU_line_smooth_get()
{
const GPUState &state = Context::get()->state_manager->state;
return bool(state.line_smooth);
}
void GPU_scissor_get(int coords[4])
{
Context::get()->active_fb->scissor_get(coords);
}
void GPU_viewport_size_get_f(float coords[4])
{
int viewport[4];
Context::get()->active_fb->viewport_get(viewport);
for (int i = 0; i < 4; i++) {
coords[i] = viewport[i];
}
}
void GPU_viewport_size_get_i(int coords[4])
{
Context::get()->active_fb->viewport_get(coords);
}
bool GPU_depth_mask_get()
{
const GPUState &state = Context::get()->state_manager->state;
return (state.write_mask & GPU_WRITE_DEPTH) != 0;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Context Utils
* \{ */
void GPU_flush()
{
Context::get()->flush();
}
void GPU_finish()
{
Context::get()->finish();
}
void GPU_apply_state()
{
Context::get()->state_manager->apply_state();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Synchronization Utils
* \{ */
void GPU_memory_barrier(GPUBarrier barrier)
{
Context::get()->state_manager->issue_barrier(barrier);
}
GPUFence *GPU_fence_create()
{
Fence *fence = GPUBackend::get()->fence_alloc();
return wrap(fence);
}
void GPU_fence_free(GPUFence *fence)
{
delete unwrap(fence);
}
void GPU_fence_signal(GPUFence *fence)
{
unwrap(fence)->signal();
}
void GPU_fence_wait(GPUFence *fence)
{
unwrap(fence)->wait();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Default State
* \{ */
StateManager::StateManager()
{
/* Set default state. */
state.write_mask = GPU_WRITE_COLOR;
state.blend = GPU_BLEND_NONE;
state.culling_test = GPU_CULL_NONE;
state.depth_test = GPU_DEPTH_NONE;
state.stencil_test = GPU_STENCIL_NONE;
state.stencil_op = GPU_STENCIL_OP_NONE;
state.provoking_vert = GPU_VERTEX_LAST;
state.logic_op_xor = false;
state.invert_facing = false;
state.clip_distances = 0;
state.clip_control = false;
state.polygon_smooth = false;
state.line_smooth = false;
mutable_state.point_size = -1.0f; /* Negative is not using point size. */
mutable_state.line_width = 1.0f;
mutable_state.stencil_write_mask = 0x00;
mutable_state.stencil_compare_mask = 0x00;
mutable_state.stencil_reference = 0x00;
image_formats.fill(TextureWriteFormat::Invalid);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,190 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "BLI_utildefines.h"
#include "GPU_state.hh"
#include "gpu_texture_private.hh"
#include <cstring>
namespace blender::gpu {
/* Encapsulate all pipeline state that we need to track.
* Try to keep small to reduce validation time. */
union GPUState {
struct {
/** GPUWriteMask */
uint32_t write_mask : 13;
/** GPUBlend */
uint32_t blend : 4;
/** GPUFaceCullTest */
uint32_t culling_test : 2;
/** GPUDepthTest */
uint32_t depth_test : 3;
/** GPUStencilTest */
uint32_t stencil_test : 3;
/** GPUStencilOp */
uint32_t stencil_op : 3;
/** GPUProvokingVertex */
uint32_t provoking_vert : 1;
/** Enable bits. */
uint32_t logic_op_xor : 1;
uint32_t invert_facing : 1;
/** Clip range of 0..1 on OpenGL. */
uint32_t clip_control : 1;
/** Number of clip distances enabled. */
/* TODO(fclem): This should be a shader property. */
uint32_t clip_distances : 3;
/* TODO(fclem): remove, old opengl features. */
uint32_t polygon_smooth : 1;
uint32_t line_smooth : 1;
};
/* Here to allow fast bit-wise ops. */
uint64_t data;
};
BLI_STATIC_ASSERT(sizeof(GPUState) == sizeof(uint64_t), "GPUState is too big.");
inline bool operator==(const GPUState &a, const GPUState &b)
{
return a.data == b.data;
}
inline bool operator!=(const GPUState &a, const GPUState &b)
{
return !(a == b);
}
inline GPUState operator^(const GPUState &a, const GPUState &b)
{
GPUState r;
r.data = a.data ^ b.data;
return r;
}
inline GPUState operator~(const GPUState &a)
{
GPUState r;
r.data = ~a.data;
return r;
}
/* Mutable state that does not require pipeline change. */
union GPUStateMutable {
struct {
/* Viewport State */
/** Positive if using program point size. */
/* TODO(fclem): should be passed as uniform to all shaders. */
float point_size;
/** Not supported on every platform. Prefer using wide-line shader. */
float line_width;
/** Mutable stencil states. */
uint8_t stencil_write_mask;
uint8_t stencil_compare_mask;
uint8_t stencil_reference;
uint8_t _pad0[5];
/* IMPORTANT: ensure x64 struct alignment. */
};
/* Here to allow fast bit-wise ops. */
uint64_t data[2];
};
BLI_STATIC_ASSERT(sizeof(GPUStateMutable) == sizeof(GPUStateMutable::data),
"GPUStateMutable is too big.");
inline bool operator==(const GPUStateMutable &a, const GPUStateMutable &b)
{
return a.data[0] == b.data[0] && a.data[1] == b.data[1];
}
inline bool operator!=(const GPUStateMutable &a, const GPUStateMutable &b)
{
return !(a == b);
}
inline GPUStateMutable operator^(const GPUStateMutable &a, const GPUStateMutable &b)
{
GPUStateMutable r;
for (int i = 0; i < ARRAY_SIZE(a.data); i++) {
r.data[i] = a.data[i] ^ b.data[i];
}
return r;
}
inline GPUStateMutable operator~(const GPUStateMutable &a)
{
GPUStateMutable r;
for (int i = 0; i < ARRAY_SIZE(a.data); i++) {
r.data[i] = ~a.data[i];
}
return r;
}
/**
* State manager keeping track of the draw state and applying it before drawing.
* Base class which is then specialized for each implementation (GL, VK, ...).
*/
class StateManager {
public:
GPUState state;
GPUStateMutable mutable_state;
/* Formats of all image units. */
std::array<TextureWriteFormat, GPU_MAX_IMAGE> image_formats;
StateManager();
virtual ~StateManager() = default;
virtual void apply_state() = 0;
virtual void force_state() = 0;
virtual void issue_barrier(GPUBarrier barrier_bits) = 0;
virtual void texture_bind(Texture *tex, GPUSamplerState sampler, int unit) = 0;
virtual void texture_unbind(Texture *tex) = 0;
virtual void texture_unbind_all() = 0;
virtual void image_bind(Texture *tex, int unit) = 0;
virtual void image_unbind(Texture *tex) = 0;
virtual void image_unbind_all() = 0;
};
/**
* GPUFence.
*/
class Fence {
protected:
bool signalled_ = false;
public:
Fence() = default;
virtual ~Fence() = default;
virtual void signal() = 0;
virtual void wait() = 0;
};
/* Syntactic sugar. */
static inline GPUFence *wrap(Fence *fence)
{
return reinterpret_cast<GPUFence *>(fence);
}
static inline Fence *unwrap(GPUFence *fence)
{
return reinterpret_cast<Fence *>(fence);
}
static inline const Fence *unwrap(const GPUFence *fence)
{
return reinterpret_cast<const Fence *>(fence);
}
} // namespace blender::gpu

View File

@@ -0,0 +1,142 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "MEM_guardedalloc.h"
#include <cstring>
#include "BLI_string.h"
#include "BKE_global.hh"
#include "gpu_backend.hh"
#include "GPU_storage_buffer.hh"
#include "GPU_vertex_buffer.hh" /* For GPUUsageType. */
#include "gpu_context_private.hh"
#include "gpu_storage_buffer_private.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
* \{ */
namespace gpu {
StorageBuf::StorageBuf(size_t size, const char *name)
{
size_in_bytes_ = usage_size_in_bytes_ = size;
STRNCPY(name_, name);
}
StorageBuf::~StorageBuf()
{
MEM_SAFE_DELETE_VOID(data_);
}
void StorageBuf::usage_size_set(size_t usage_size)
{
BLI_assert(usage_size <= size_in_bytes_);
usage_size_in_bytes_ = usage_size;
}
} // namespace gpu
/** \} */
/* -------------------------------------------------------------------- */
/** \name C-API
* \{ */
using namespace blender::gpu;
gpu::StorageBuf *GPU_storagebuf_create_ex(size_t size,
const void *data,
GPUUsageType usage,
const char *name)
{
StorageBuf *ssbo = GPUBackend::get()->storagebuf_alloc(size, usage, name);
/* Direct init. */
if (data != nullptr) {
ssbo->update(data);
}
else if (G.debug & G_DEBUG_GPU) {
/* Fill the buffer with poison values.
* (NaN for floats, -1 for `int` and "max value" for `uint`). */
Vector<uchar> uninitialized_data(size, 0xFF);
ssbo->update(uninitialized_data.data());
}
return ssbo;
}
void GPU_storagebuf_free(gpu::StorageBuf *ssbo)
{
delete ssbo;
}
void GPU_storagebuf_usage_size_set(gpu::StorageBuf *ssbo, size_t usage_size)
{
ssbo->usage_size_set(usage_size);
}
void GPU_storagebuf_update(gpu::StorageBuf *ssbo, const void *data)
{
ssbo->update(data);
}
void GPU_storagebuf_bind(gpu::StorageBuf *ssbo, int slot)
{
ssbo->bind(slot);
}
void GPU_storagebuf_unbind(gpu::StorageBuf *ssbo)
{
ssbo->unbind();
}
void GPU_storagebuf_debug_unbind_all()
{
Context::get()->debug_unbind_all_ssbo();
}
void GPU_storagebuf_clear_to_zero(gpu::StorageBuf *ssbo)
{
GPU_storagebuf_clear(ssbo, 0);
}
void GPU_storagebuf_clear(gpu::StorageBuf *ssbo, uint32_t clear_value)
{
ssbo->clear(clear_value);
}
void GPU_storagebuf_copy_sub_from_vertbuf(
gpu::StorageBuf *ssbo, gpu::VertBuf *src, uint dst_offset, uint src_offset, uint copy_size)
{
ssbo->copy_sub(src, dst_offset, src_offset, copy_size);
}
void GPU_storagebuf_sync_to_host(gpu::StorageBuf *ssbo)
{
ssbo->async_flush_to_host();
}
void GPU_storagebuf_read(gpu::StorageBuf *ssbo, void *data)
{
ssbo->read(data);
}
void GPU_storagebuf_sync_as_indirect_buffer(gpu::StorageBuf *ssbo)
{
ssbo->sync_as_indirect_buffer();
}
/** \} */
} // namespace blender

Some files were not shown because too many files have changed in this diff Show More