From 073139e32943b863828474ca70bbdfc258520769 Mon Sep 17 00:00:00 2001 From: Jason Fielder Date: Thu, 12 May 2022 20:49:09 +0200 Subject: Metal: MTLState module implementation. MTLState module implementation and supporting functionality in MTLContext for state tracking, texture binding and sampler state caching. Ref T96261 Reviewed By: fclem Maniphest Tasks: T96261 Differential Revision: https://developer.blender.org/D14827 --- source/blender/gpu/CMakeLists.txt | 3 + source/blender/gpu/GPU_common_types.h | 18 + source/blender/gpu/GPU_state.h | 12 + source/blender/gpu/metal/mtl_context.hh | 304 +++++++++++++- source/blender/gpu/metal/mtl_context.mm | 236 +++++++++++ source/blender/gpu/metal/mtl_state.hh | 73 ++++ source/blender/gpu/metal/mtl_state.mm | 675 ++++++++++++++++++++++++++++++++ source/blender/gpu/metal/mtl_texture.hh | 47 +-- 8 files changed, 1320 insertions(+), 48 deletions(-) create mode 100644 source/blender/gpu/GPU_common_types.h create mode 100644 source/blender/gpu/metal/mtl_state.hh create mode 100644 source/blender/gpu/metal/mtl_state.mm (limited to 'source') diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 6ed2c083a6b..f963d0e3b39 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -85,6 +85,7 @@ set(SRC GPU_buffers.h GPU_capabilities.h GPU_common.h + GPU_common_types.h GPU_compute.h GPU_context.h GPU_debug.h @@ -189,6 +190,7 @@ set(METAL_SRC metal/mtl_backend.mm metal/mtl_context.mm metal/mtl_debug.mm + metal/mtl_state.mm metal/mtl_texture.mm metal/mtl_texture_util.mm @@ -197,6 +199,7 @@ set(METAL_SRC metal/mtl_common.hh metal/mtl_context.hh metal/mtl_debug.hh + metal/mtl_state.hh metal/mtl_texture.hh ) diff --git a/source/blender/gpu/GPU_common_types.h b/source/blender/gpu/GPU_common_types.h new file mode 100644 index 00000000000..e08143c2449 --- /dev/null +++ b/source/blender/gpu/GPU_common_types.h @@ -0,0 +1,18 @@ +/** \file + * \ingroup gpu + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum eGPUFrontFace { + GPU_CLOCKWISE, + GPU_COUNTERCLOCKWISE, +} eGPUFrontFace; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/blender/gpu/GPU_state.h b/source/blender/gpu/GPU_state.h index 99b60351dcc..7641b50fe24 100644 --- a/source/blender/gpu/GPU_state.h +++ b/source/blender/gpu/GPU_state.h @@ -35,6 +35,18 @@ typedef enum eGPUBarrier { ENUM_OPERATORS(eGPUBarrier, GPU_BARRIER_ELEMENT_ARRAY) +/* Note: For Metal and Vulkan only. TODO(Metal): Update barrier calls to use stage flags. */ +typedef enum eGPUStageBarrierBits { + 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), +} eGPUStageBarrierBits; + +ENUM_OPERATORS(eGPUStageBarrierBits, GPU_BARRIER_STAGE_COMPUTE) + /** * Defines the fixed pipeline blending equation. * SRC is the output color from the shader. diff --git a/source/blender/gpu/metal/mtl_context.hh b/source/blender/gpu/metal/mtl_context.hh index aa198482291..938ea8edc13 100644 --- a/source/blender/gpu/metal/mtl_context.hh +++ b/source/blender/gpu/metal/mtl_context.hh @@ -7,8 +7,10 @@ #include "gpu_context_private.hh" +#include "GPU_common_types.h" #include "GPU_context.h" +#include "mtl_capabilities.hh" #include "mtl_texture.hh" #include @@ -21,6 +23,112 @@ namespace blender::gpu { +class MTLShader; +class MTLUniformBuf; +class MTLBuffer; + +/* Depth Stencil State */ +typedef struct MTLContextDepthStencilState { + + /* Depth State. */ + bool depth_write_enable; + bool depth_test_enabled; + float depth_range_near; + float depth_range_far; + MTLCompareFunction depth_function; + float depth_bias; + float depth_slope_scale; + bool depth_bias_enabled_for_points; + bool depth_bias_enabled_for_lines; + bool depth_bias_enabled_for_tris; + + /* Stencil State. */ + bool stencil_test_enabled; + unsigned int stencil_read_mask; + unsigned int stencil_write_mask; + unsigned int stencil_ref; + MTLCompareFunction stencil_func; + + MTLStencilOperation stencil_op_front_stencil_fail; + MTLStencilOperation stencil_op_front_depth_fail; + MTLStencilOperation stencil_op_front_depthstencil_pass; + + MTLStencilOperation stencil_op_back_stencil_fail; + MTLStencilOperation stencil_op_back_depth_fail; + MTLStencilOperation stencil_op_back_depthstencil_pass; + + /* Framebuffer State -- We need to mark this, incase stencil state remains unchanged, + * but attachment state has changed. */ + bool has_depth_target; + bool has_stencil_target; + + /* TODO(Metal): Consider optimising this function using memcmp. + * Un-used, but differing, stencil state leads to over-generation + * of state objects when doing trivial compare. */ + inline bool operator==(const MTLContextDepthStencilState &other) const + { + bool depth_state_equality = (has_depth_target == other.has_depth_target && + depth_write_enable == other.depth_write_enable && + depth_test_enabled == other.depth_test_enabled && + depth_function == other.depth_function); + + bool stencil_state_equality = true; + if (has_stencil_target) { + stencil_state_equality = + (has_stencil_target == other.has_stencil_target && + stencil_test_enabled == other.stencil_test_enabled && + stencil_op_front_stencil_fail == other.stencil_op_front_stencil_fail && + stencil_op_front_depth_fail == other.stencil_op_front_depth_fail && + stencil_op_front_depthstencil_pass == other.stencil_op_front_depthstencil_pass && + stencil_op_back_stencil_fail == other.stencil_op_back_stencil_fail && + stencil_op_back_depth_fail == other.stencil_op_back_depth_fail && + stencil_op_back_depthstencil_pass == other.stencil_op_back_depthstencil_pass && + stencil_func == other.stencil_func && stencil_read_mask == other.stencil_read_mask && + stencil_write_mask == other.stencil_write_mask); + } + + return depth_state_equality && stencil_state_equality; + } + + /* Depth stencil state will get hashed in order to prepare + * MTLDepthStencilState objects. The hash should comprise of + * all elements which fill the MTLDepthStencilDescriptor. + * These are bound when [rec setDepthStencilState:...] is called. + * Depth bias and stencil reference value are set dynamically on the RenderCommandEncoder: + * - setStencilReferenceValue: + * - setDepthBias:slopeScale:clamp: + */ + inline std::size_t hash() const + { + std::size_t boolean_bitmask = (this->depth_write_enable ? 1 : 0) | + ((this->depth_test_enabled ? 1 : 0) << 1) | + ((this->depth_bias_enabled_for_points ? 1 : 0) << 2) | + ((this->depth_bias_enabled_for_lines ? 1 : 0) << 3) | + ((this->depth_bias_enabled_for_tris ? 1 : 0) << 4) | + ((this->stencil_test_enabled ? 1 : 0) << 5) | + ((this->has_depth_target ? 1 : 0) << 6) | + ((this->has_stencil_target ? 1 : 0) << 7); + + std::size_t stencilop_bitmask = ((std::size_t)this->stencil_op_front_stencil_fail) | + ((std::size_t)this->stencil_op_front_depth_fail << 3) | + ((std::size_t)this->stencil_op_front_depthstencil_pass << 6) | + ((std::size_t)this->stencil_op_back_stencil_fail << 9) | + ((std::size_t)this->stencil_op_back_depth_fail << 12) | + ((std::size_t)this->stencil_op_back_depthstencil_pass << 15); + + std::size_t main_hash = (std::size_t)this->depth_function; + if (this->has_stencil_target) { + main_hash += (std::size_t)(this->stencil_read_mask & 0xFF) << 8; + main_hash += (std::size_t)(this->stencil_write_mask & 0xFF) << 16; + } + main_hash ^= (std::size_t)this->stencil_func << 16; + main_hash ^= stencilop_bitmask; + + std::size_t final_hash = (main_hash << 8) | boolean_bitmask; + return final_hash; + } +} MTLContextDepthStencilState; + typedef struct MTLContextTextureUtils { /* Depth Update Utilities */ @@ -108,11 +216,149 @@ typedef struct MTLContextTextureUtils { } MTLContextTextureUtils; +/* Structs containing information on current binding state for textures and samplers. */ +typedef struct MTLTextureBinding { + bool used; + + /* Same value as index in bindings array. */ + unsigned int texture_slot_index; + gpu::MTLTexture *texture_resource; + +} MTLTextureBinding; + +typedef struct MTLSamplerBinding { + bool used; + MTLSamplerState state; + + bool operator==(MTLSamplerBinding const &other) const + { + return (used == other.used && state == other.state); + } +} MTLSamplerBinding; + +/* Combined sampler state configuration for Argument Buffer caching. */ +struct MTLSamplerArray { + unsigned int num_samplers; + /* MTLSamplerState permutations between 0..256 - slightly more than a byte. */ + MTLSamplerState mtl_sampler_flags[MTL_MAX_TEXTURE_SLOTS]; + id mtl_sampler[MTL_MAX_TEXTURE_SLOTS]; + + inline bool operator==(const MTLSamplerArray &other) const + { + if (this->num_samplers != other.num_samplers) { + return false; + } + return (memcmp(this->mtl_sampler_flags, + other.mtl_sampler_flags, + sizeof(MTLSamplerState) * this->num_samplers) == 0); + } + + inline uint32_t hash() const + { + uint32_t hash = this->num_samplers; + for (int i = 0; i < this->num_samplers; i++) { + hash ^= (uint32_t)this->mtl_sampler_flags[i] << (i % 3); + } + return hash; + } +}; + +typedef enum MTLPipelineStateDirtyFlag { + MTL_PIPELINE_STATE_NULL_FLAG = 0, + /* Whether we need to call setViewport. */ + MTL_PIPELINE_STATE_VIEWPORT_FLAG = (1 << 0), + /* Whether we need to call setScissor.*/ + MTL_PIPELINE_STATE_SCISSOR_FLAG = (1 << 1), + /* Whether we need to update/rebind active depth stencil state. */ + MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG = (1 << 2), + /* Whether we need to update/rebind active PSO. */ + MTL_PIPELINE_STATE_PSO_FLAG = (1 << 3), + /* Whether we need to update the frontFacingWinding state. */ + MTL_PIPELINE_STATE_FRONT_FACING_FLAG = (1 << 4), + /* Whether we need to update the culling state. */ + MTL_PIPELINE_STATE_CULLMODE_FLAG = (1 << 5), + /* Full pipeline state needs applying. Occurs when beginning a new render pass. */ + MTL_PIPELINE_STATE_ALL_FLAG = + (MTL_PIPELINE_STATE_VIEWPORT_FLAG | MTL_PIPELINE_STATE_SCISSOR_FLAG | + MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG | MTL_PIPELINE_STATE_PSO_FLAG | + MTL_PIPELINE_STATE_FRONT_FACING_FLAG | MTL_PIPELINE_STATE_CULLMODE_FLAG) +} MTLPipelineStateDirtyFlag; + +/* Ignore full flag bit-mask `MTL_PIPELINE_STATE_ALL_FLAG`. */ +ENUM_OPERATORS(MTLPipelineStateDirtyFlag, MTL_PIPELINE_STATE_CULLMODE_FLAG); + +typedef struct MTLUniformBufferBinding { + bool bound; + MTLUniformBuf *ubo; +} MTLUniformBufferBinding; + typedef struct MTLContextGlobalShaderPipelineState { - /* ..TODO(Metal): More elements to be added as backend fleshed out.. */ + bool initialised; + + /* Whether the pipeline state has been modified since application. + * `dirty_flags` is a bitmask of the types of state which have been updated. + * This is in order to optimise calls and only re-apply state as needed. + * Some state parameters are dynamically applied on the RenderCommandEncoder, + * others may be encapsulated in GPU-resident state objects such as + * MTLDepthStencilState or MTLRenderPipelineState. */ + bool dirty; + MTLPipelineStateDirtyFlag dirty_flags; + + /* Shader resources. */ + MTLShader *null_shader; + + /* Active Shader State. */ + MTLShader *active_shader; + + /* Global Uniform Buffers. */ + MTLUniformBufferBinding ubo_bindings[MTL_MAX_UNIFORM_BUFFER_BINDINGS]; + + /* Context Texture bindings. */ + MTLTextureBinding texture_bindings[MTL_MAX_TEXTURE_SLOTS]; + MTLSamplerBinding sampler_bindings[MTL_MAX_SAMPLER_SLOTS]; + + /*** --- Render Pipeline State --- ***/ + /* Track global render pipeline state for the current context. The functions in GPU_state.h + * modify these parameters. Certain values, tagged [PSO], are parameters which are required to be + * passed into PSO creation, rather than dynamic state functions on the RenderCommandEncoder. + */ - /*** DATA and IMAGE access state ***/ + /* Blending State. */ + MTLColorWriteMask color_write_mask; /* [PSO] */ + bool blending_enabled; /* [PSO] */ + MTLBlendOperation alpha_blend_op; /* [PSO] */ + MTLBlendOperation rgb_blend_op; /* [PSO] */ + MTLBlendFactor dest_alpha_blend_factor; /* [PSO] */ + MTLBlendFactor dest_rgb_blend_factor; /* [PSO] */ + MTLBlendFactor src_alpha_blend_factor; /* [PSO] */ + MTLBlendFactor src_rgb_blend_factor; /* [PSO] */ + + /* Culling State. */ + bool culling_enabled; + eGPUFaceCullTest cull_mode; + eGPUFrontFace front_face; + + /* Depth State. */ + MTLContextDepthStencilState depth_stencil_state; + + /* Viewport/Scissor Region. */ + int viewport_offset_x; + int viewport_offset_y; + int viewport_width; + int viewport_height; + bool scissor_enabled; + int scissor_x; + int scissor_y; + int scissor_width; + int scissor_height; + + /* Image data access state. */ uint unpack_row_length; + + /* Render parameters. */ + float point_size = 1.0f; + float line_width = 1.0f; + } MTLContextGlobalShaderPipelineState; /* Metal Buffer */ @@ -127,7 +373,7 @@ typedef struct MTLTemporaryBufferRange { bool requires_flush(); } MTLTemporaryBufferRange; -/** MTLContext -- Core render loop and state management **/ +/** MTLContext -- Core render loop and state management. **/ /* Note(Metal): Partial MTLContext stub to provide wrapper functionality * for work-in-progress MTL* classes. */ @@ -138,8 +384,24 @@ class MTLContext : public Context { /* Compute and specialization caches. */ MTLContextTextureUtils texture_utils_; + /* Texture Samplers. */ + /* Cache of generated MTLSamplerState objects based on permutations of `eGPUSamplerState`. */ + id sampler_state_cache_[GPU_SAMPLER_MAX] = {0}; + id default_sampler_state_ = nil; + + /* When texture sampler count exceeds the resource bind limit, an + * argument buffer is used to pass samplers to the shader. + * Each unique configurations of multiple samplers can be cached, so as to not require + * re-generation. `samplers_` stores the current list of bound sampler objects. + * `cached_sampler_buffers_` is a cache of encoded argument buffers which can be re-used. */ + MTLSamplerArray samplers_; + blender::Map cached_sampler_buffers_; + public: - /* METAL API Resource Handles. */ + /* Shaders and Pipeline state. */ + MTLContextGlobalShaderPipelineState pipeline_state; + + /* Metal API Resource Handles. */ id queue = nil; id device = nil; @@ -160,24 +422,40 @@ class MTLContext : public Context { void debug_group_begin(const char *name, int index) override; void debug_group_end(void) override; - /*** Context Utility functions */ + /*** MTLContext Utility functions. */ /* * All below functions modify the global state for the context, controlling the flow of * rendering, binding resources, setting global state, resource management etc; */ - /* Metal Context Core functions */ - /* Command Buffer Management */ + /* Metal Context Core functions. */ + /* Command Buffer Management. */ id get_active_command_buffer(); - /* Render Pass State and Management */ + /* Render Pass State and Management. */ void begin_render_pass(); void end_render_pass(); - - /* Shaders and Pipeline state */ - MTLContextGlobalShaderPipelineState pipeline_state; - - /* Texture utilities */ + bool is_render_pass_active(); + + /* Texture Binding. */ + void texture_bind(gpu::MTLTexture *mtl_texture, unsigned int texture_unit); + void sampler_bind(MTLSamplerState, unsigned int sampler_unit); + void texture_unbind(gpu::MTLTexture *mtl_texture); + void texture_unbind_all(void); + id get_sampler_from_state(MTLSamplerState state); + id generate_sampler_from_state(MTLSamplerState state); + id get_default_sampler_state(); + + /* Metal Context pipeline state. */ + void pipeline_state_init(void); + MTLShader *get_active_shader(void); + + /* State assignment. */ + void set_viewport(int origin_x, int origin_y, int width, int height); + void set_scissor(int scissor_x, int scissor_y, int scissor_width, int scissor_height); + void set_scissor_enabled(bool scissor_enabled); + + /* Texture utilities. */ MTLContextTextureUtils &get_texture_utils() { return this->texture_utils_; diff --git a/source/blender/gpu/metal/mtl_context.mm b/source/blender/gpu/metal/mtl_context.mm index 18ed38c373d..de72340a9a8 100644 --- a/source/blender/gpu/metal/mtl_context.mm +++ b/source/blender/gpu/metal/mtl_context.mm @@ -5,6 +5,11 @@ */ #include "mtl_context.hh" #include "mtl_debug.hh" +#include "mtl_state.hh" + +#include "DNA_userdef_types.h" + +#include "GPU_capabilities.h" using namespace blender; using namespace blender::gpu; @@ -44,6 +49,9 @@ MTLContext::MTLContext(void *ghost_window) /* Init debug. */ debug::mtl_debug_init(); + /* Initialise Metal modules. */ + this->state_manager = new MTLStateManager(this); + /* TODO(Metal): Implement. */ } @@ -98,6 +106,234 @@ void MTLContext::end_render_pass() /* TODO(Metal): Implement. */ } +bool MTLContext::is_render_pass_active() +{ + /* TODO(Metal): Implement. */ + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Global Context State + * \{ */ + +/* Metal Context Pipeline State. */ +void MTLContext::pipeline_state_init() +{ + /*** Initialise state only once. ***/ + if (!this->pipeline_state.initialised) { + this->pipeline_state.initialised = true; + this->pipeline_state.active_shader = NULL; + + /* Clear bindings state. */ + for (int t = 0; t < GPU_max_textures(); t++) { + this->pipeline_state.texture_bindings[t].used = false; + this->pipeline_state.texture_bindings[t].texture_slot_index = t; + this->pipeline_state.texture_bindings[t].texture_resource = NULL; + } + for (int s = 0; s < MTL_MAX_SAMPLER_SLOTS; s++) { + this->pipeline_state.sampler_bindings[s].used = false; + } + for (int u = 0; u < MTL_MAX_UNIFORM_BUFFER_BINDINGS; u++) { + this->pipeline_state.ubo_bindings[u].bound = false; + this->pipeline_state.ubo_bindings[u].ubo = NULL; + } + } + + /*** State defaults -- restored by GPU_state_init. ***/ + /* Clear blending State. */ + this->pipeline_state.color_write_mask = MTLColorWriteMaskRed | MTLColorWriteMaskGreen | + MTLColorWriteMaskBlue | MTLColorWriteMaskAlpha; + this->pipeline_state.blending_enabled = false; + this->pipeline_state.alpha_blend_op = MTLBlendOperationAdd; + this->pipeline_state.rgb_blend_op = MTLBlendOperationAdd; + this->pipeline_state.dest_alpha_blend_factor = MTLBlendFactorZero; + this->pipeline_state.dest_rgb_blend_factor = MTLBlendFactorZero; + this->pipeline_state.src_alpha_blend_factor = MTLBlendFactorOne; + this->pipeline_state.src_rgb_blend_factor = MTLBlendFactorOne; + + /* Viewport and scissor. */ + this->pipeline_state.viewport_offset_x = 0; + this->pipeline_state.viewport_offset_y = 0; + this->pipeline_state.viewport_width = 0; + this->pipeline_state.viewport_height = 0; + this->pipeline_state.scissor_x = 0; + this->pipeline_state.scissor_y = 0; + this->pipeline_state.scissor_width = 0; + this->pipeline_state.scissor_height = 0; + this->pipeline_state.scissor_enabled = false; + + /* Culling State. */ + this->pipeline_state.culling_enabled = false; + this->pipeline_state.cull_mode = GPU_CULL_NONE; + this->pipeline_state.front_face = GPU_COUNTERCLOCKWISE; + + /* DATA and IMAGE access state. */ + this->pipeline_state.unpack_row_length = 0; + + /* Depth State. */ + this->pipeline_state.depth_stencil_state.depth_write_enable = false; + this->pipeline_state.depth_stencil_state.depth_test_enabled = false; + this->pipeline_state.depth_stencil_state.depth_range_near = 0.0; + this->pipeline_state.depth_stencil_state.depth_range_far = 1.0; + this->pipeline_state.depth_stencil_state.depth_function = MTLCompareFunctionAlways; + this->pipeline_state.depth_stencil_state.depth_bias = 0.0; + this->pipeline_state.depth_stencil_state.depth_slope_scale = 0.0; + this->pipeline_state.depth_stencil_state.depth_bias_enabled_for_points = false; + this->pipeline_state.depth_stencil_state.depth_bias_enabled_for_lines = false; + this->pipeline_state.depth_stencil_state.depth_bias_enabled_for_tris = false; + + /* Stencil State. */ + this->pipeline_state.depth_stencil_state.stencil_test_enabled = false; + this->pipeline_state.depth_stencil_state.stencil_read_mask = 0xFF; + this->pipeline_state.depth_stencil_state.stencil_write_mask = 0xFF; + this->pipeline_state.depth_stencil_state.stencil_ref = 0; + this->pipeline_state.depth_stencil_state.stencil_func = MTLCompareFunctionAlways; + this->pipeline_state.depth_stencil_state.stencil_op_front_stencil_fail = MTLStencilOperationKeep; + this->pipeline_state.depth_stencil_state.stencil_op_front_depth_fail = MTLStencilOperationKeep; + this->pipeline_state.depth_stencil_state.stencil_op_front_depthstencil_pass = + MTLStencilOperationKeep; + this->pipeline_state.depth_stencil_state.stencil_op_back_stencil_fail = MTLStencilOperationKeep; + this->pipeline_state.depth_stencil_state.stencil_op_back_depth_fail = MTLStencilOperationKeep; + this->pipeline_state.depth_stencil_state.stencil_op_back_depthstencil_pass = + MTLStencilOperationKeep; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Texture State Management + * \{ */ + +void MTLContext::texture_bind(gpu::MTLTexture *mtl_texture, unsigned int texture_unit) +{ + BLI_assert(this); + BLI_assert(mtl_texture); + + if (texture_unit < 0 || texture_unit >= GPU_max_textures() || + texture_unit >= MTL_MAX_TEXTURE_SLOTS) { + MTL_LOG_WARNING("Attempting to bind texture '%s' to invalid texture unit %d\n", + mtl_texture->get_name(), + texture_unit); + BLI_assert(false); + return; + } + + /* Bind new texture. */ + this->pipeline_state.texture_bindings[texture_unit].texture_resource = mtl_texture; + this->pipeline_state.texture_bindings[texture_unit].used = true; + mtl_texture->is_bound_ = true; +} + +void MTLContext::sampler_bind(MTLSamplerState sampler_state, unsigned int sampler_unit) +{ + BLI_assert(this); + if (sampler_unit < 0 || sampler_unit >= GPU_max_textures() || + sampler_unit >= MTL_MAX_SAMPLER_SLOTS) { + MTL_LOG_WARNING("Attempting to bind sampler to invalid sampler unit %d\n", sampler_unit); + BLI_assert(false); + return; + } + + /* Apply binding. */ + this->pipeline_state.sampler_bindings[sampler_unit] = {true, sampler_state}; +} + +void MTLContext::texture_unbind(gpu::MTLTexture *mtl_texture) +{ + BLI_assert(mtl_texture); + + /* Iterate through textures in state and unbind. */ + for (int i = 0; i < min_uu(GPU_max_textures(), MTL_MAX_TEXTURE_SLOTS); i++) { + if (this->pipeline_state.texture_bindings[i].texture_resource == mtl_texture) { + this->pipeline_state.texture_bindings[i].texture_resource = nullptr; + this->pipeline_state.texture_bindings[i].used = false; + } + } + + /* Locally unbind texture. */ + mtl_texture->is_bound_ = false; +} + +void MTLContext::texture_unbind_all() +{ + /* Iterate through context's bound textures. */ + for (int t = 0; t < min_uu(GPU_max_textures(), MTL_MAX_TEXTURE_SLOTS); t++) { + if (this->pipeline_state.texture_bindings[t].used && + this->pipeline_state.texture_bindings[t].texture_resource) { + + this->pipeline_state.texture_bindings[t].used = false; + this->pipeline_state.texture_bindings[t].texture_resource = nullptr; + } + } +} + +id MTLContext::get_sampler_from_state(MTLSamplerState sampler_state) +{ + BLI_assert((unsigned int)sampler_state >= 0 && ((unsigned int)sampler_state) < GPU_SAMPLER_MAX); + return this->sampler_state_cache_[(unsigned int)sampler_state]; +} + +id MTLContext::generate_sampler_from_state(MTLSamplerState sampler_state) +{ + /* Check if samper already exists for given state. */ + id st = this->sampler_state_cache_[(unsigned int)sampler_state]; + if (st != nil) { + return st; + } + else { + MTLSamplerDescriptor *descriptor = [[MTLSamplerDescriptor alloc] init]; + descriptor.normalizedCoordinates = true; + + MTLSamplerAddressMode clamp_type = (sampler_state.state & GPU_SAMPLER_CLAMP_BORDER) ? + MTLSamplerAddressModeClampToBorderColor : + MTLSamplerAddressModeClampToEdge; + descriptor.rAddressMode = (sampler_state.state & GPU_SAMPLER_REPEAT_R) ? + MTLSamplerAddressModeRepeat : + clamp_type; + descriptor.sAddressMode = (sampler_state.state & GPU_SAMPLER_REPEAT_S) ? + MTLSamplerAddressModeRepeat : + clamp_type; + descriptor.tAddressMode = (sampler_state.state & GPU_SAMPLER_REPEAT_T) ? + MTLSamplerAddressModeRepeat : + clamp_type; + descriptor.borderColor = MTLSamplerBorderColorTransparentBlack; + descriptor.minFilter = (sampler_state.state & GPU_SAMPLER_FILTER) ? + MTLSamplerMinMagFilterLinear : + MTLSamplerMinMagFilterNearest; + descriptor.magFilter = (sampler_state.state & GPU_SAMPLER_FILTER) ? + MTLSamplerMinMagFilterLinear : + MTLSamplerMinMagFilterNearest; + descriptor.mipFilter = (sampler_state.state & GPU_SAMPLER_MIPMAP) ? + MTLSamplerMipFilterLinear : + MTLSamplerMipFilterNotMipmapped; + descriptor.lodMinClamp = -1000; + descriptor.lodMaxClamp = 1000; + float aniso_filter = max_ff(16, U.anisotropic_filter); + descriptor.maxAnisotropy = (sampler_state.state & GPU_SAMPLER_MIPMAP) ? aniso_filter : 1; + descriptor.compareFunction = (sampler_state.state & GPU_SAMPLER_COMPARE) ? + MTLCompareFunctionLessEqual : + MTLCompareFunctionAlways; + descriptor.supportArgumentBuffers = true; + + id state = [this->device newSamplerStateWithDescriptor:descriptor]; + this->sampler_state_cache_[(unsigned int)sampler_state] = state; + + BLI_assert(state != nil); + [descriptor autorelease]; + return state; + } +} + +id MTLContext::get_default_sampler_state() +{ + if (this->default_sampler_state_ == nil) { + this->default_sampler_state_ = this->get_sampler_from_state(DEFAULT_SAMPLER_STATE); + } + return this->default_sampler_state_; +} + /** \} */ } // blender::gpu diff --git a/source/blender/gpu/metal/mtl_state.hh b/source/blender/gpu/metal/mtl_state.hh new file mode 100644 index 00000000000..f2d85f9648b --- /dev/null +++ b/source/blender/gpu/metal/mtl_state.hh @@ -0,0 +1,73 @@ +/** \file + * \ingroup gpu + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_utildefines.h" + +#include "GPU_state.h" +#include "gpu_state_private.hh" + +namespace blender::gpu { + +/* Forward Declarations. */ +class MTLContext; + +/** + * State manager keeping track of the draw state and applying it before drawing. + * Metal Implementation. + **/ +class MTLStateManager : public StateManager { + public: + private: + /* Current state of the associated MTLContext. + * Avoids resetting the whole state for every change. */ + GPUState current_; + GPUStateMutable current_mutable_; + MTLContext *context_; + + public: + MTLStateManager(MTLContext *ctx); + + void apply_state(void) override; + void force_state(void) override; + + void issue_barrier(eGPUBarrier barrier_bits) override; + + void texture_bind(Texture *tex, eGPUSamplerState sampler, int unit) override; + void texture_unbind(Texture *tex) override; + void texture_unbind_all(void) override; + + void image_bind(Texture *tex, int unit) override; + void image_unbind(Texture *tex) override; + void image_unbind_all(void) override; + + void texture_unpack_row_length_set(uint len) override; + + private: + void set_write_mask(const eGPUWriteMask value); + void set_depth_test(const eGPUDepthTest value); + void set_stencil_test(const eGPUStencilTest test, const eGPUStencilOp operation); + void set_stencil_mask(const eGPUStencilTest test, const GPUStateMutable state); + void set_clip_distances(const int new_dist_len, const int old_dist_len); + void set_logic_op(const bool enable); + void set_facing(const bool invert); + void set_backface_culling(const eGPUFaceCullTest test); + void set_provoking_vert(const eGPUProvokingVertex vert); + void set_shadow_bias(const bool enable); + void set_blend(const eGPUBlend value); + + void set_state(const GPUState &state); + void set_mutable_state(const GPUStateMutable &state); + + /* METAL State utility functions. */ + void mtl_state_init(void); + void mtl_depth_range(float near, float far); + void mtl_stencil_mask(unsigned int mask); + void mtl_stencil_set_func(eGPUStencilTest stencil_func, int ref, unsigned int mask); + + MEM_CXX_CLASS_ALLOC_FUNCS("MTLStateManager") +}; + +} // namespace blender::gpu diff --git a/source/blender/gpu/metal/mtl_state.mm b/source/blender/gpu/metal/mtl_state.mm new file mode 100644 index 00000000000..5cd88c4ff7b --- /dev/null +++ b/source/blender/gpu/metal/mtl_state.mm @@ -0,0 +1,675 @@ +/** \file + * \ingroup gpu + */ + +#include "BLI_math_base.h" +#include "BLI_math_bits.h" + +#include "GPU_framebuffer.h" + +#include "mtl_context.hh" +#include "mtl_state.hh" + +namespace blender::gpu { + +/* -------------------------------------------------------------------- */ +/** \name MTLStateManager + * \{ */ + +void MTLStateManager::mtl_state_init(void) +{ + BLI_assert(this->context_); + this->context_->pipeline_state_init(); +} + +MTLStateManager::MTLStateManager(MTLContext *ctx) : StateManager() +{ + /* Initialise State. */ + this->context_ = ctx; + mtl_state_init(); + + /* Force update using default state. */ + current_ = ~state; + current_mutable_ = ~mutable_state; + set_state(state); + set_mutable_state(mutable_state); +} + +void MTLStateManager::apply_state(void) +{ + this->set_state(this->state); + this->set_mutable_state(this->mutable_state); + /* TODO(Metal): Enable after integration of MTLFrameBuffer. */ + /* static_cast(this->context_->active_fb)->apply_state(); */ +}; + +void MTLStateManager::force_state(void) +{ + /* Little exception for clip distances since they need to keep the old count correct. */ + uint32_t clip_distances = current_.clip_distances; + current_ = ~this->state; + current_.clip_distances = clip_distances; + current_mutable_ = ~this->mutable_state; + this->set_state(this->state); + this->set_mutable_state(this->mutable_state); +}; + +void MTLStateManager::set_state(const GPUState &state) +{ + GPUState changed = state ^ current_; + + if (changed.blend != 0) { + set_blend((eGPUBlend)state.blend); + } + if (changed.write_mask != 0) { + set_write_mask((eGPUWriteMask)state.write_mask); + } + if (changed.depth_test != 0) { + set_depth_test((eGPUDepthTest)state.depth_test); + } + if (changed.stencil_test != 0 || changed.stencil_op != 0) { + set_stencil_test((eGPUStencilTest)state.stencil_test, (eGPUStencilOp)state.stencil_op); + set_stencil_mask((eGPUStencilTest)state.stencil_test, mutable_state); + } + if (changed.clip_distances != 0) { + set_clip_distances(state.clip_distances, current_.clip_distances); + } + if (changed.culling_test != 0) { + set_backface_culling((eGPUFaceCullTest)state.culling_test); + } + if (changed.logic_op_xor != 0) { + set_logic_op(state.logic_op_xor); + } + if (changed.invert_facing != 0) { + set_facing(state.invert_facing); + } + if (changed.provoking_vert != 0) { + set_provoking_vert((eGPUProvokingVertex)state.provoking_vert); + } + if (changed.shadow_bias != 0) { + set_shadow_bias(state.shadow_bias); + } + + /* TODO remove (Following GLState). */ + if (changed.polygon_smooth) { + /* Note: Unsupported in Metal. */ + } + if (changed.line_smooth) { + /* Note: Unsupported in Metal. */ + } + + current_ = state; +} + +void MTLStateManager::mtl_depth_range(float near, float far) +{ + BLI_assert(this->context_); + BLI_assert(near >= 0.0 && near < 1.0); + BLI_assert(far > 0.0 && far <= 1.0); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + MTLContextDepthStencilState &ds_state = pipeline_state.depth_stencil_state; + + ds_state.depth_range_near = near; + ds_state.depth_range_far = far; + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_VIEWPORT_FLAG; +} + +void MTLStateManager::set_mutable_state(const GPUStateMutable &state) +{ + GPUStateMutable changed = state ^ current_mutable_; + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + + if (float_as_uint(changed.point_size) != 0) { + pipeline_state.point_size = state.point_size; + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG; + } + + if (changed.line_width != 0) { + pipeline_state.line_width = state.line_width; + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG; + } + + if (changed.depth_range[0] != 0 || changed.depth_range[1] != 0) { + /* TODO remove, should modify the projection matrix instead. */ + mtl_depth_range(state.depth_range[0], state.depth_range[1]); + } + + if (changed.stencil_compare_mask != 0 || changed.stencil_reference != 0 || + changed.stencil_write_mask != 0) { + set_stencil_mask((eGPUStencilTest)current_.stencil_test, state); + } + + current_mutable_ = state; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name State setting functions + * \{ */ + +void MTLStateManager::set_write_mask(const eGPUWriteMask value) +{ + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + pipeline_state.depth_stencil_state.depth_write_enable = ((value & GPU_WRITE_DEPTH) != 0); + pipeline_state.color_write_mask = + (((value & GPU_WRITE_RED) != 0) ? MTLColorWriteMaskRed : MTLColorWriteMaskNone) | + (((value & GPU_WRITE_GREEN) != 0) ? MTLColorWriteMaskGreen : MTLColorWriteMaskNone) | + (((value & GPU_WRITE_BLUE) != 0) ? MTLColorWriteMaskBlue : MTLColorWriteMaskNone) | + (((value & GPU_WRITE_ALPHA) != 0) ? MTLColorWriteMaskAlpha : MTLColorWriteMaskNone); + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG; +} + +static MTLCompareFunction gpu_depth_function_to_metal(eGPUDepthTest depth_func) +{ + switch (depth_func) { + case GPU_DEPTH_NONE: + return MTLCompareFunctionNever; + case GPU_DEPTH_LESS: + return MTLCompareFunctionLess; + case GPU_DEPTH_EQUAL: + return MTLCompareFunctionEqual; + case GPU_DEPTH_LESS_EQUAL: + return MTLCompareFunctionLessEqual; + case GPU_DEPTH_GREATER: + return MTLCompareFunctionGreater; + case GPU_DEPTH_GREATER_EQUAL: + return MTLCompareFunctionGreaterEqual; + case GPU_DEPTH_ALWAYS: + return MTLCompareFunctionAlways; + default: + BLI_assert(false && "Invalid eGPUDepthTest"); + break; + } + return MTLCompareFunctionAlways; +} + +static MTLCompareFunction gpu_stencil_func_to_metal(eGPUStencilTest stencil_func) +{ + switch (stencil_func) { + case GPU_STENCIL_NONE: + return MTLCompareFunctionAlways; + case GPU_STENCIL_EQUAL: + return MTLCompareFunctionEqual; + case GPU_STENCIL_NEQUAL: + return MTLCompareFunctionNotEqual; + case GPU_STENCIL_ALWAYS: + return MTLCompareFunctionAlways; + default: + BLI_assert(false && "Unrecognised eGPUStencilTest function"); + break; + } + return MTLCompareFunctionAlways; +} + +void MTLStateManager::set_depth_test(const eGPUDepthTest value) +{ + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + MTLContextDepthStencilState &ds_state = pipeline_state.depth_stencil_state; + + ds_state.depth_test_enabled = (value != GPU_DEPTH_NONE); + ds_state.depth_function = gpu_depth_function_to_metal(value); + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG; +} + +void MTLStateManager::mtl_stencil_mask(unsigned int mask) +{ + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + pipeline_state.depth_stencil_state.stencil_write_mask = mask; + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG; +} + +void MTLStateManager::mtl_stencil_set_func(eGPUStencilTest stencil_func, + int ref, + unsigned int mask) +{ + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + MTLContextDepthStencilState &ds_state = pipeline_state.depth_stencil_state; + + ds_state.stencil_func = gpu_stencil_func_to_metal(stencil_func); + ds_state.stencil_ref = ref; + ds_state.stencil_read_mask = mask; + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG; +} + +static void mtl_stencil_set_op_separate(MTLContext *context, + eGPUFaceCullTest face, + MTLStencilOperation stencil_fail, + MTLStencilOperation depth_test_fail, + MTLStencilOperation depthstencil_pass) +{ + BLI_assert(context); + MTLContextGlobalShaderPipelineState &pipeline_state = context->pipeline_state; + MTLContextDepthStencilState &ds_state = pipeline_state.depth_stencil_state; + + if (face == GPU_CULL_FRONT) { + ds_state.stencil_op_front_stencil_fail = stencil_fail; + ds_state.stencil_op_front_depth_fail = depth_test_fail; + ds_state.stencil_op_front_depthstencil_pass = depthstencil_pass; + } + else if (face == GPU_CULL_BACK) { + ds_state.stencil_op_back_stencil_fail = stencil_fail; + ds_state.stencil_op_back_depth_fail = depth_test_fail; + ds_state.stencil_op_back_depthstencil_pass = depthstencil_pass; + } + + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG; +} + +static void mtl_stencil_set_op(MTLContext *context, + MTLStencilOperation stencil_fail, + MTLStencilOperation depth_test_fail, + MTLStencilOperation depthstencil_pass) +{ + mtl_stencil_set_op_separate( + context, GPU_CULL_FRONT, stencil_fail, depth_test_fail, depthstencil_pass); + mtl_stencil_set_op_separate( + context, GPU_CULL_BACK, stencil_fail, depth_test_fail, depthstencil_pass); +} + +void MTLStateManager::set_stencil_test(const eGPUStencilTest test, const eGPUStencilOp operation) +{ + switch (operation) { + case GPU_STENCIL_OP_REPLACE: + mtl_stencil_set_op(this->context_, + MTLStencilOperationKeep, + MTLStencilOperationKeep, + MTLStencilOperationReplace); + break; + case GPU_STENCIL_OP_COUNT_DEPTH_PASS: + /* Winding inversed due to flipped Y coordinate system in Metal. */ + mtl_stencil_set_op_separate(this->context_, + GPU_CULL_FRONT, + MTLStencilOperationKeep, + MTLStencilOperationKeep, + MTLStencilOperationIncrementWrap); + mtl_stencil_set_op_separate(this->context_, + GPU_CULL_BACK, + MTLStencilOperationKeep, + MTLStencilOperationKeep, + MTLStencilOperationDecrementWrap); + break; + case GPU_STENCIL_OP_COUNT_DEPTH_FAIL: + /* Winding inversed due to flipped Y coordinate system in Metal. */ + mtl_stencil_set_op_separate(this->context_, + GPU_CULL_FRONT, + MTLStencilOperationKeep, + MTLStencilOperationDecrementWrap, + MTLStencilOperationKeep); + mtl_stencil_set_op_separate(this->context_, + GPU_CULL_BACK, + MTLStencilOperationKeep, + MTLStencilOperationIncrementWrap, + MTLStencilOperationKeep); + break; + case GPU_STENCIL_OP_NONE: + default: + mtl_stencil_set_op(this->context_, + MTLStencilOperationKeep, + MTLStencilOperationKeep, + MTLStencilOperationKeep); + } + + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + pipeline_state.depth_stencil_state.stencil_test_enabled = (test != GPU_STENCIL_NONE); + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG; +} + +void MTLStateManager::set_stencil_mask(const eGPUStencilTest test, const GPUStateMutable state) +{ + if (test == GPU_STENCIL_NONE) { + mtl_stencil_mask(0x00); + mtl_stencil_set_func(GPU_STENCIL_ALWAYS, 0x00, 0x00); + } + else { + mtl_stencil_mask(state.stencil_write_mask); + mtl_stencil_set_func(test, state.stencil_reference, state.stencil_compare_mask); + } +} + +void MTLStateManager::set_clip_distances(const int new_dist_len, const int old_dist_len) +{ + /* TODO(Metal): Support Clip distances in METAL. Clip distance + * assignment via shader is supported, but global clip-states require + * support. */ +} + +void MTLStateManager::set_logic_op(const bool enable) +{ + /* Note(Metal): Logic Operations not directly supported. */ +} + +void MTLStateManager::set_facing(const bool invert) +{ + /* Check Current Context. */ + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + + /* Apply State -- opposite of GL, as METAL default is GPU_CLOCKWISE, GL default is + * COUNTERCLOCKWISE. This needs to be the inverse of the default. */ + pipeline_state.front_face = (invert) ? GPU_COUNTERCLOCKWISE : GPU_CLOCKWISE; + + /* Mark Dirty - Ensure context updates state between draws. */ + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_FRONT_FACING_FLAG; + pipeline_state.dirty = true; +} + +void MTLStateManager::set_backface_culling(const eGPUFaceCullTest test) +{ + /* Check Current Context. */ + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + + /* Apply State. */ + pipeline_state.culling_enabled = (test != GPU_CULL_NONE); + pipeline_state.cull_mode = test; + + /* Mark Dirty - Ensure context updates state between draws. */ + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_CULLMODE_FLAG; + pipeline_state.dirty = true; +} + +void MTLStateManager::set_provoking_vert(const eGPUProvokingVertex vert) +{ + /* Note(Metal): Provoking vertex is not a feature in the Metal API. + * Shaders are handled on a case-by-case basis using a modified vertex shader. + * For example, wireframe rendering and edit-mesh shaders utilise an SSBO-based + * vertex fetching mechanism which considers the inverse convention for flat + * shading, to ensure consistent results with OpenGL. */ +} + +void MTLStateManager::set_shadow_bias(const bool enable) +{ + /* Check Current Context. */ + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + MTLContextDepthStencilState &ds_state = pipeline_state.depth_stencil_state; + + /* Apply State. */ + if (enable) { + ds_state.depth_bias_enabled_for_lines = true; + ds_state.depth_bias_enabled_for_tris = true; + ds_state.depth_bias = 2.0f; + ds_state.depth_slope_scale = 1.0f; + } + else { + ds_state.depth_bias_enabled_for_lines = false; + ds_state.depth_bias_enabled_for_tris = false; + ds_state.depth_bias = 0.0f; + ds_state.depth_slope_scale = 0.0f; + } + + /* Mark Dirty - Ensure context updates depth-stencil state between draws. */ + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_DEPTHSTENCIL_FLAG; + pipeline_state.dirty = true; +} + +void MTLStateManager::set_blend(const eGPUBlend value) +{ + /** + * Factors to the equation. + * SRC is fragment shader output. + * DST is framebuffer color. + * final.rgb = SRC.rgb * src_rgb + DST.rgb * dst_rgb; + * final.a = SRC.a * src_alpha + DST.a * dst_alpha; + **/ + MTLBlendFactor src_rgb; + MTLBlendFactor dst_rgb; + MTLBlendFactor src_alpha; + MTLBlendFactor dst_alpha; + switch (value) { + default: + case GPU_BLEND_ALPHA: { + src_rgb = MTLBlendFactorSourceAlpha; + dst_rgb = MTLBlendFactorOneMinusSourceAlpha; + src_alpha = MTLBlendFactorOne; + dst_alpha = MTLBlendFactorOneMinusSourceAlpha; + break; + } + case GPU_BLEND_ALPHA_PREMULT: { + src_rgb = MTLBlendFactorOne; + dst_rgb = MTLBlendFactorOneMinusSourceAlpha; + src_alpha = MTLBlendFactorOne; + dst_alpha = MTLBlendFactorOneMinusSourceAlpha; + break; + } + case GPU_BLEND_ADDITIVE: { + /* Do not let alpha accumulate but premult the source RGB by it. */ + src_rgb = MTLBlendFactorSourceAlpha; + dst_rgb = MTLBlendFactorOne; + src_alpha = MTLBlendFactorZero; + dst_alpha = MTLBlendFactorOne; + break; + } + case GPU_BLEND_SUBTRACT: + case GPU_BLEND_ADDITIVE_PREMULT: { + /* Let alpha accumulate. */ + src_rgb = MTLBlendFactorOne; + dst_rgb = MTLBlendFactorOne; + src_alpha = MTLBlendFactorOne; + dst_alpha = MTLBlendFactorOne; + break; + } + case GPU_BLEND_MULTIPLY: { + src_rgb = MTLBlendFactorDestinationColor; + dst_rgb = MTLBlendFactorZero; + src_alpha = MTLBlendFactorDestinationAlpha; + dst_alpha = MTLBlendFactorZero; + break; + } + case GPU_BLEND_INVERT: { + src_rgb = MTLBlendFactorOneMinusDestinationColor; + dst_rgb = MTLBlendFactorZero; + src_alpha = MTLBlendFactorZero; + dst_alpha = MTLBlendFactorOne; + break; + } + case GPU_BLEND_OIT: { + src_rgb = MTLBlendFactorOne; + dst_rgb = MTLBlendFactorOne; + src_alpha = MTLBlendFactorZero; + dst_alpha = MTLBlendFactorOneMinusSourceAlpha; + break; + } + case GPU_BLEND_BACKGROUND: { + src_rgb = MTLBlendFactorOneMinusDestinationAlpha; + dst_rgb = MTLBlendFactorSourceAlpha; + src_alpha = MTLBlendFactorZero; + dst_alpha = MTLBlendFactorSourceAlpha; + break; + } + case GPU_BLEND_ALPHA_UNDER_PREMUL: { + src_rgb = MTLBlendFactorOneMinusDestinationAlpha; + dst_rgb = MTLBlendFactorOne; + src_alpha = MTLBlendFactorOneMinusDestinationAlpha; + dst_alpha = MTLBlendFactorOne; + break; + } + case GPU_BLEND_CUSTOM: { + src_rgb = MTLBlendFactorOne; + dst_rgb = MTLBlendFactorSource1Color; + src_alpha = MTLBlendFactorOne; + dst_alpha = MTLBlendFactorSource1Alpha; + break; + } + } + + /* Check Current Context. */ + BLI_assert(this->context_); + MTLContextGlobalShaderPipelineState &pipeline_state = this->context_->pipeline_state; + + if (value == GPU_BLEND_SUBTRACT) { + pipeline_state.rgb_blend_op = MTLBlendOperationReverseSubtract; + pipeline_state.alpha_blend_op = MTLBlendOperationReverseSubtract; + } + else { + pipeline_state.rgb_blend_op = MTLBlendOperationAdd; + pipeline_state.alpha_blend_op = MTLBlendOperationAdd; + } + + /* Apply State. */ + pipeline_state.blending_enabled = (value != GPU_BLEND_NONE); + pipeline_state.src_rgb_blend_factor = src_rgb; + pipeline_state.dest_rgb_blend_factor = dst_rgb; + pipeline_state.src_alpha_blend_factor = src_alpha; + pipeline_state.dest_alpha_blend_factor = dst_alpha; + + /* Mark Dirty - Ensure context updates PSOs between draws. */ + pipeline_state.dirty_flags |= MTL_PIPELINE_STATE_PSO_FLAG; + pipeline_state.dirty = true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Memory barrier + * \{ */ + +/* Note(Metal): Granular option for specifying before/after stages for a barrier + * Would be a useful feature. */ +/*void MTLStateManager::issue_barrier(eGPUBarrier barrier_bits, + eGPUStageBarrierBits before_stages, + eGPUStageBarrierBits after_stages) */ +void MTLStateManager::issue_barrier(eGPUBarrier barrier_bits) +{ + /* Note(Metal): The Metal API implictly tracks dependencies between resources. + * Memory barriers and execution barriers (Fences/Events) can be used to coordinate + * this explicitly, however, in most cases, the driver will be able to + * resolve these dependencies automatically. + * For untracked resources, such as MTLHeap's, explicit barriers are necessary. */ + eGPUStageBarrierBits before_stages = GPU_BARRIER_STAGE_ANY; + eGPUStageBarrierBits after_stages = GPU_BARRIER_STAGE_ANY; + + MTLContext *ctx = reinterpret_cast(GPU_context_active_get()); + BLI_assert(ctx); + if (ctx->is_render_pass_active()) { + + /* Apple Silicon does not support memory barriers. + * We do not currently need these due to implicit API guarantees. + * Note(Metal): MTLFence/MTLEvent may be required to synchronize work if + * untracked resources are ever used. */ + if ([ctx->device hasUnifiedMemory]) { + return; + } + + /* Issue barrier. */ + /* TODO(Metal): To be completed pending implementation of RenderCommandEncoder management. */ + id rec = nil; // ctx->get_active_render_command_encoder(); + BLI_assert(rec); + + /* Only supporting Metal on 10.15 onwards anyway - Check required for warnings. */ + if (@available(macOS 10.14, *)) { + MTLBarrierScope scope = 0; + if (barrier_bits & GPU_BARRIER_SHADER_IMAGE_ACCESS || + barrier_bits & GPU_BARRIER_TEXTURE_FETCH) { + scope = scope | MTLBarrierScopeTextures | MTLBarrierScopeRenderTargets; + } + if (barrier_bits & GPU_BARRIER_SHADER_STORAGE || + barrier_bits & GPU_BARRIER_VERTEX_ATTRIB_ARRAY || + barrier_bits & GPU_BARRIER_ELEMENT_ARRAY) { + scope = scope | MTLBarrierScopeBuffers; + } + + MTLRenderStages before_stage_flags = 0; + MTLRenderStages after_stage_flags = 0; + if (before_stages & GPU_BARRIER_STAGE_VERTEX && + !(before_stages & GPU_BARRIER_STAGE_FRAGMENT)) { + before_stage_flags = before_stage_flags | MTLRenderStageVertex; + } + if (before_stages & GPU_BARRIER_STAGE_FRAGMENT) { + before_stage_flags = before_stage_flags | MTLRenderStageFragment; + } + if (after_stages & GPU_BARRIER_STAGE_VERTEX) { + after_stage_flags = after_stage_flags | MTLRenderStageVertex; + } + if (after_stages & GPU_BARRIER_STAGE_FRAGMENT) { + after_stage_flags = MTLRenderStageFragment; + } + + if (scope != 0) { + [rec memoryBarrierWithScope:scope + afterStages:after_stage_flags + beforeStages:before_stage_flags]; + } + } + } +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Texture State Management + * \{ */ + +void MTLStateManager::texture_unpack_row_length_set(uint len) +{ + /* Set source image row data stride when uploading image data to the GPU. */ + MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); + ctx->pipeline_state.unpack_row_length = len; +} + +void MTLStateManager::texture_bind(Texture *tex_, eGPUSamplerState sampler_type, int unit) +{ + BLI_assert(tex_); + gpu::MTLTexture *mtl_tex = static_cast(tex_); + BLI_assert(mtl_tex); + + MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); + if (unit >= 0) { + ctx->texture_bind(mtl_tex, unit); + + /* Fetching textures default sampler configuration and applying + * eGPUSampler State on top. This path exists to support + * Any of the sampler state which is associated with the + * texture itself such as min/max mip levels. */ + MTLSamplerState sampler = mtl_tex->get_sampler_state(); + sampler.state = sampler_type; + + ctx->sampler_bind(sampler, unit); + } +} + +void MTLStateManager::texture_unbind(Texture *tex_) +{ + BLI_assert(tex_); + gpu::MTLTexture *mtl_tex = static_cast(tex_); + BLI_assert(mtl_tex); + MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); + ctx->texture_unbind(mtl_tex); +} + +void MTLStateManager::texture_unbind_all(void) +{ + MTLContext *ctx = static_cast(unwrap(GPU_context_active_get())); + BLI_assert(ctx); + ctx->texture_unbind_all(); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Image Binding (from image load store) + * \{ */ + +void MTLStateManager::image_bind(Texture *tex_, int unit) +{ + this->texture_bind(tex_, GPU_SAMPLER_DEFAULT, unit); +} + +void MTLStateManager::image_unbind(Texture *tex_) +{ + this->texture_unbind(tex_); +} + +void MTLStateManager::image_unbind_all(void) +{ + this->texture_unbind_all(); +} + +/** \} */ + +} // blender::gpu diff --git a/source/blender/gpu/metal/mtl_texture.hh b/source/blender/gpu/metal/mtl_texture.hh index 47f03a5777b..b820256ec36 100644 --- a/source/blender/gpu/metal/mtl_texture.hh +++ b/source/blender/gpu/metal/mtl_texture.hh @@ -47,16 +47,13 @@ struct TextureUpdateRoutineSpecialisation { (component_count_input == other.component_count_input) && (component_count_output == other.component_count_output)); } -}; -template<> struct blender::DefaultHash { - inline uint64_t operator()(const TextureUpdateRoutineSpecialisation &key) const + inline uint64_t hash() const { - - DefaultHash string_hasher; + blender::DefaultHash string_hasher; return (uint64_t)string_hasher( - key.input_data_type + key.output_data_type + - std::to_string((key.component_count_input << 8) + key.component_count_output)); + this->input_data_type + this->output_data_type + + std::to_string((this->component_count_input << 8) + this->component_count_output)); } }; @@ -78,12 +75,10 @@ struct DepthTextureUpdateRoutineSpecialisation { { return ((data_mode == other.data_mode)); } -}; -template<> struct blender::DefaultHash { - inline uint64_t operator()(const DepthTextureUpdateRoutineSpecialisation &key) const + inline uint64_t hash() const { - return (uint64_t)(key.data_mode); + return (uint64_t)(this->data_mode); } }; @@ -109,17 +104,14 @@ struct TextureReadRoutineSpecialisation { (component_count_output == other.component_count_output) && (depth_format_mode == other.depth_format_mode)); } -}; -template<> struct blender::DefaultHash { - inline uint64_t operator()(const TextureReadRoutineSpecialisation &key) const + inline uint64_t hash() const { - - DefaultHash string_hasher; - return (uint64_t)string_hasher(key.input_data_type + key.output_data_type + - std::to_string((key.component_count_input << 8) + - key.component_count_output + - (key.depth_format_mode << 28))); + blender::DefaultHash string_hasher; + return (uint64_t)string_hasher(this->input_data_type + this->output_data_type + + std::to_string((this->component_count_input << 8) + + this->component_count_output + + (this->depth_format_mode << 28))); } }; @@ -158,21 +150,6 @@ typedef struct MTLSamplerState { const MTLSamplerState DEFAULT_SAMPLER_STATE = {GPU_SAMPLER_DEFAULT /*, 0, 9999*/}; -} // namespace blender::gpu - -template<> struct blender::DefaultHash { - inline uint64_t operator()(const blender::gpu::MTLSamplerState &key) const - { - const DefaultHash uint_hasher; - uint64_t main_hash = (uint64_t)uint_hasher((unsigned int)(key.state)); - - /* Hash other parameters as needed. */ - return main_hash; - } -}; - -namespace blender::gpu { - class MTLTexture : public Texture { friend class MTLContext; friend class MTLStateManager; -- cgit v1.2.3