Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Fielder <jason_apple>2022-05-12 21:49:09 +0300
committerClément Foucault <foucault.clem@gmail.com>2022-05-12 21:50:11 +0300
commit073139e32943b863828474ca70bbdfc258520769 (patch)
tree9851acf5b0adc25113ca247d09bf8d2451b90979 /source/blender/gpu/metal
parent992ae3f282cb9f9c3c9ce64e1c45a4c75066c31b (diff)
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
Diffstat (limited to 'source/blender/gpu/metal')
-rw-r--r--source/blender/gpu/metal/mtl_context.hh304
-rw-r--r--source/blender/gpu/metal/mtl_context.mm236
-rw-r--r--source/blender/gpu/metal/mtl_state.hh73
-rw-r--r--source/blender/gpu/metal/mtl_state.mm675
-rw-r--r--source/blender/gpu/metal/mtl_texture.hh47
5 files changed, 1287 insertions, 48 deletions
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 <Cocoa/Cocoa.h>
@@ -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<MTLSamplerState> 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<MTLSamplerState> sampler_state_cache_[GPU_SAMPLER_MAX] = {0};
+ id<MTLSamplerState> 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<MTLSamplerArray, gpu::MTLBuffer *> cached_sampler_buffers_;
+
public:
- /* METAL API Resource Handles. */
+ /* Shaders and Pipeline state. */
+ MTLContextGlobalShaderPipelineState pipeline_state;
+
+ /* Metal API Resource Handles. */
id<MTLCommandQueue> queue = nil;
id<MTLDevice> 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<MTLCommandBuffer> 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<MTLSamplerState> get_sampler_from_state(MTLSamplerState state);
+ id<MTLSamplerState> generate_sampler_from_state(MTLSamplerState state);
+ id<MTLSamplerState> 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<MTLSamplerState> 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<MTLSamplerState> MTLContext::generate_sampler_from_state(MTLSamplerState sampler_state)
+{
+ /* Check if samper already exists for given state. */
+ id<MTLSamplerState> 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<MTLSamplerState> state = [this->device newSamplerStateWithDescriptor:descriptor];
+ this->sampler_state_cache_[(unsigned int)sampler_state] = state;
+
+ BLI_assert(state != nil);
+ [descriptor autorelease];
+ return state;
+ }
+}
+
+id<MTLSamplerState> 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<MTLFrameBuffer *>(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<MTLContext *>(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<MTLRenderCommandEncoder> 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<MTLContext *>(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<gpu::MTLTexture *>(tex_);
+ BLI_assert(mtl_tex);
+
+ MTLContext *ctx = static_cast<MTLContext *>(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<gpu::MTLTexture *>(tex_);
+ BLI_assert(mtl_tex);
+ MTLContext *ctx = static_cast<MTLContext *>(unwrap(GPU_context_active_get()));
+ ctx->texture_unbind(mtl_tex);
+}
+
+void MTLStateManager::texture_unbind_all(void)
+{
+ MTLContext *ctx = static_cast<MTLContext *>(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<TextureUpdateRoutineSpecialisation> {
- inline uint64_t operator()(const TextureUpdateRoutineSpecialisation &key) const
+ inline uint64_t hash() const
{
-
- DefaultHash<std::string> string_hasher;
+ blender::DefaultHash<std::string> 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<DepthTextureUpdateRoutineSpecialisation> {
- 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<TextureReadRoutineSpecialisation> {
- inline uint64_t operator()(const TextureReadRoutineSpecialisation &key) const
+ inline uint64_t hash() const
{
-
- DefaultHash<std::string> 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<std::string> 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<blender::gpu::MTLSamplerState> {
- inline uint64_t operator()(const blender::gpu::MTLSamplerState &key) const
- {
- const DefaultHash<unsigned int> 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;