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:
Diffstat (limited to 'source/blender/gpu/metal/mtl_immediate.mm')
-rw-r--r--source/blender/gpu/metal/mtl_immediate.mm397
1 files changed, 397 insertions, 0 deletions
diff --git a/source/blender/gpu/metal/mtl_immediate.mm b/source/blender/gpu/metal/mtl_immediate.mm
new file mode 100644
index 00000000000..41632e39092
--- /dev/null
+++ b/source/blender/gpu/metal/mtl_immediate.mm
@@ -0,0 +1,397 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup gpu
+ *
+ * Mimics old style opengl immediate mode drawing.
+ */
+
+#include "BKE_global.h"
+
+#include "GPU_vertex_format.h"
+#include "gpu_context_private.hh"
+#include "gpu_shader_private.hh"
+#include "gpu_vertex_format_private.h"
+
+#include "mtl_context.hh"
+#include "mtl_debug.hh"
+#include "mtl_immediate.hh"
+#include "mtl_primitive.hh"
+#include "mtl_shader.hh"
+
+namespace blender::gpu {
+
+MTLImmediate::MTLImmediate(MTLContext *ctx)
+{
+ context_ = ctx;
+}
+
+MTLImmediate::~MTLImmediate()
+{
+}
+
+uchar *MTLImmediate::begin()
+{
+ BLI_assert(!has_begun_);
+
+ /* Determine primitive type. */
+ metal_primitive_type_ = gpu_prim_type_to_metal(this->prim_type);
+ metal_primitive_mode_ = mtl_prim_type_to_topology_class(metal_primitive_type_);
+ has_begun_ = true;
+
+ /* Allocate a range of data and return host-accessible pointer. */
+ const size_t bytes_needed = vertex_buffer_size(&vertex_format, vertex_len);
+ current_allocation_ = context_->get_scratchbuffer_manager()
+ .scratch_buffer_allocate_range_aligned(bytes_needed, 256);
+ [current_allocation_.metal_buffer retain];
+ return reinterpret_cast<uchar *>(current_allocation_.data);
+}
+
+void MTLImmediate::end()
+{
+ /* Ensure we're between a imm::begin/imm:end pair. */
+ BLI_assert(has_begun_);
+ BLI_assert(prim_type != GPU_PRIM_NONE);
+
+ /* Verify context is valid, vertex data is written and a valid shader is bound. */
+ if (context_ && this->vertex_idx > 0 && this->shader) {
+
+ MTLShader *active_mtl_shader = static_cast<MTLShader *>(unwrap(shader));
+
+ /* Skip draw if Metal shader is not valid. */
+ if (active_mtl_shader == nullptr || !active_mtl_shader->is_valid() ||
+ active_mtl_shader->get_interface() == nullptr) {
+
+ const char *ptr = (active_mtl_shader) ? active_mtl_shader->name_get() : nullptr;
+ MTL_LOG_WARNING(
+ "MTLImmediate::end -- cannot perform draw as active shader is NULL or invalid (likely "
+ "unimplemented) (shader %p '%s')\n",
+ active_mtl_shader,
+ ptr);
+ return;
+ }
+
+ /* Ensure we are inside a render pass and fetch active RenderCommandEncoder. */
+ id<MTLRenderCommandEncoder> rec = context_->ensure_begin_render_pass();
+ BLI_assert(rec != nil);
+
+ /* Fetch active render pipeline state. */
+ MTLRenderPassState &rps = context_->main_command_buffer.get_render_pass_state();
+
+ /* Bind Shader. */
+ GPU_shader_bind(this->shader);
+
+ /* Debug markers for frame-capture and detailed error messages. */
+ if (G.debug & G_DEBUG_GPU) {
+ [rec pushDebugGroup:[NSString
+ stringWithFormat:@"immEnd(verts: %d, shader: %s)",
+ this->vertex_idx,
+ active_mtl_shader->get_interface()->get_name()]];
+ [rec insertDebugSignpost:[NSString stringWithFormat:@"immEnd(verts: %d, shader: %s)",
+ this->vertex_idx,
+ active_mtl_shader->get_interface()
+ ->get_name()]];
+ }
+
+ /* Populate pipeline state vertex descriptor. */
+ MTLStateManager *state_manager = static_cast<MTLStateManager *>(
+ MTLContext::get()->state_manager);
+ MTLRenderPipelineStateDescriptor &desc = state_manager->get_pipeline_descriptor();
+ const MTLShaderInterface *interface = active_mtl_shader->get_interface();
+
+ desc.vertex_descriptor.num_attributes = interface->get_total_attributes();
+ desc.vertex_descriptor.num_vert_buffers = 1;
+
+ for (int i = 0; i < desc.vertex_descriptor.num_attributes; i++) {
+ desc.vertex_descriptor.attributes[i].format = MTLVertexFormatInvalid;
+ }
+ desc.vertex_descriptor.uses_ssbo_vertex_fetch =
+ active_mtl_shader->get_uses_ssbo_vertex_fetch();
+ desc.vertex_descriptor.num_ssbo_attributes = 0;
+
+ /* SSBO Vertex Fetch -- Verify Attributes. */
+ if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
+ active_mtl_shader->ssbo_vertex_fetch_bind_attributes_begin();
+
+ /* Disable Indexed rendering in SSBO vertex fetch. */
+ int uniform_ssbo_use_indexed = active_mtl_shader->uni_ssbo_uses_indexed_rendering;
+ BLI_assert_msg(uniform_ssbo_use_indexed != -1, "Expected valid uniform location for ssbo_uses_indexed_rendering.");
+ int uses_indexed_rendering = 0;
+ active_mtl_shader->uniform_int(uniform_ssbo_use_indexed, 1, 1, &uses_indexed_rendering);
+ }
+
+ /* Populate Vertex descriptor and verify attributes.
+ * TODO(Metal): Cache this vertex state based on Vertex format and shaders. */
+ for (int i = 0; i < interface->get_total_attributes(); i++) {
+
+ /* Note: Attribute in VERTEX FORMAT does not necessarily share the same array index as
+ * attributes in shader interface. */
+ GPUVertAttr *attr = nullptr;
+ const MTLShaderInputAttribute &mtl_shader_attribute = interface->get_attribute(i);
+
+ /* Scan through vertex_format attributes until one with a name matching the shader interface
+ * is found. */
+ for (uint32_t a_idx = 0; a_idx < this->vertex_format.attr_len && attr == nullptr; a_idx++) {
+ GPUVertAttr *check_attribute = &this->vertex_format.attrs[a_idx];
+
+ /* Attributes can have multiple name aliases associated with them. */
+ for (uint32_t n_idx = 0; n_idx < check_attribute->name_len; n_idx++) {
+ const char *name = GPU_vertformat_attr_name_get(
+ &this->vertex_format, check_attribute, n_idx);
+
+ if (strcmp(name, interface->get_name_at_offset(mtl_shader_attribute.name_offset)) == 0) {
+ attr = check_attribute;
+ break;
+ }
+ }
+ }
+
+ BLI_assert_msg(attr != nullptr,
+ "Could not find expected attribute in immediate mode vertex format.");
+ if (attr == nullptr) {
+ MTL_LOG_ERROR(
+ "MTLImmediate::end Could not find matching attribute '%s' from Shader Interface in "
+ "Vertex Format! - TODO: Bind Dummy attribute\n",
+ interface->get_name_at_offset(mtl_shader_attribute.name_offset));
+ return;
+ }
+
+ /* Determine whether implicit type conversion between input vertex format
+ * and shader interface vertex format is supported. */
+ MTLVertexFormat convertedFormat;
+ bool can_use_implicit_conversion = mtl_convert_vertex_format(
+ mtl_shader_attribute.format,
+ (GPUVertCompType)attr->comp_type,
+ attr->comp_len,
+ (GPUVertFetchMode)attr->fetch_mode,
+ &convertedFormat);
+
+ if (can_use_implicit_conversion) {
+ /* Metal API can implicitly convert some formats during vertex assembly:
+ * - Converting from a normalized short2 format to float2
+ * - Type truncation e.g. Float4 to Float2.
+ * - Type expansion from Float3 to Float4.
+ * - Note: extra components are filled with the corresponding components of (0,0,0,1).
+ * (See
+ * https://developer.apple.com/documentation/metal/mtlvertexattributedescriptor/1516081-format)
+ */
+ bool is_floating_point_format = (attr->comp_type == GPU_COMP_F32);
+ desc.vertex_descriptor.attributes[i].format = convertedFormat;
+ desc.vertex_descriptor.attributes[i].format_conversion_mode =
+ (is_floating_point_format) ? (GPUVertFetchMode)GPU_FETCH_FLOAT :
+ (GPUVertFetchMode)GPU_FETCH_INT;
+ BLI_assert(convertedFormat != MTLVertexFormatInvalid);
+ }
+ else {
+ /* Some conversions are NOT valid, e.g. Int4 to Float4
+ * - In this case, we need to implement a conversion routine inside the shader.
+ * - This is handled using the format_conversion_mode flag
+ * - This flag is passed into the PSO as a function specialisation,
+ * and will generate an appropriate conversion function when reading the vertex attribute
+ * value into local shader storage.
+ * (If no explicit conversion is needed, the function specialize to a pass-through). */
+ MTLVertexFormat converted_format;
+ bool can_convert = mtl_vertex_format_resize(
+ mtl_shader_attribute.format, attr->comp_len, &converted_format);
+ desc.vertex_descriptor.attributes[i].format = (can_convert) ? converted_format :
+ mtl_shader_attribute.format;
+ desc.vertex_descriptor.attributes[i].format_conversion_mode = (GPUVertFetchMode)
+ attr->fetch_mode;
+ BLI_assert(desc.vertex_descriptor.attributes[i].format != MTLVertexFormatInvalid);
+ }
+ /* Using attribute offset in vertex format, as this will be correct */
+ desc.vertex_descriptor.attributes[i].offset = attr->offset;
+ desc.vertex_descriptor.attributes[i].buffer_index = mtl_shader_attribute.buffer_index;
+
+ /* SSBO Vertex Fetch Attribute bind. */
+ if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
+ BLI_assert_msg(mtl_shader_attribute.buffer_index == 0,
+ "All attributes should be in buffer index zero");
+ MTLSSBOAttribute ssbo_attr(
+ mtl_shader_attribute.index,
+ mtl_shader_attribute.buffer_index,
+ attr->offset,
+ this->vertex_format.stride,
+ MTLShader::ssbo_vertex_type_to_attr_type(desc.vertex_descriptor.attributes[i].format),
+ false);
+ desc.vertex_descriptor.ssbo_attributes[desc.vertex_descriptor.num_ssbo_attributes] =
+ ssbo_attr;
+ desc.vertex_descriptor.num_ssbo_attributes++;
+ active_mtl_shader->ssbo_vertex_fetch_bind_attribute(ssbo_attr);
+ }
+ }
+
+ /* Buffer bindings for singular vertex buffer. */
+ desc.vertex_descriptor.buffer_layouts[0].step_function = MTLVertexStepFunctionPerVertex;
+ desc.vertex_descriptor.buffer_layouts[0].step_rate = 1;
+ desc.vertex_descriptor.buffer_layouts[0].stride = this->vertex_format.stride;
+ BLI_assert(this->vertex_format.stride > 0);
+
+ /* SSBO Vertex Fetch -- Verify Attributes. */
+ if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
+ active_mtl_shader->ssbo_vertex_fetch_bind_attributes_end(rec);
+
+ /* Set Status uniforms. */
+ BLI_assert_msg(active_mtl_shader->uni_ssbo_input_prim_type_loc != -1,
+ "ssbo_input_prim_type uniform location invalid!");
+ BLI_assert_msg(active_mtl_shader->uni_ssbo_input_vert_count_loc != -1,
+ "ssbo_input_vert_count uniform location invalid!");
+ GPU_shader_uniform_vector_int(reinterpret_cast<GPUShader *>(wrap(active_mtl_shader)),
+ active_mtl_shader->uni_ssbo_input_prim_type_loc,
+ 1,
+ 1,
+ (const int *)(&this->prim_type));
+ GPU_shader_uniform_vector_int(reinterpret_cast<GPUShader *>(wrap(active_mtl_shader)),
+ active_mtl_shader->uni_ssbo_input_vert_count_loc,
+ 1,
+ 1,
+ (const int *)(&this->vertex_idx));
+ }
+
+ MTLPrimitiveType mtl_prim_type = gpu_prim_type_to_metal(this->prim_type);
+ if (context_->ensure_render_pipeline_state(mtl_prim_type)) {
+
+ /* Issue draw call. */
+ BLI_assert(this->vertex_idx > 0);
+
+ /* Metal API does not support triangle fan, so we can emulate this
+ * input data by generating an index buffer to re-map indices to
+ * a TriangleList.
+ *
+ * NOTE(Metal): Consider caching generated triangle fan index buffers.
+ * For immediate mode, generating these is currently very cheap, as we use
+ * fast scratch buffer allocations. Though we may benefit from caching of
+ * frequently used buffer sizes. */
+ if (mtl_needs_topology_emulation(this->prim_type)) {
+
+ /* Debug safety check for SSBO FETCH MODE. */
+ if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
+ BLI_assert(false && "Topology emulation not supported with SSBO Vertex Fetch mode");
+ }
+
+ /* Emulate Tri-fan. */
+ if (this->prim_type == GPU_PRIM_TRI_FAN) {
+ /* Prepare Triangle-Fan emulation index buffer on CPU based on number of input
+ * vertices. */
+ uint32_t base_vert_count = this->vertex_idx;
+ uint32_t num_triangles = max_ii(base_vert_count - 2, 0);
+ uint32_t fan_index_count = num_triangles * 3;
+ BLI_assert(num_triangles > 0);
+
+ uint32_t alloc_size = sizeof(uint32_t) * fan_index_count;
+ uint32_t *index_buffer = nullptr;
+
+ MTLTemporaryBuffer allocation =
+ context_->get_scratchbuffer_manager().scratch_buffer_allocate_range_aligned(
+ alloc_size, 128);
+ index_buffer = (uint32_t *)allocation.data;
+
+ int a = 0;
+ for (int i = 0; i < num_triangles; i++) {
+ index_buffer[a++] = 0;
+ index_buffer[a++] = i + 1;
+ index_buffer[a++] = i + 2;
+ }
+
+ @autoreleasepool {
+
+ id<MTLBuffer> index_buffer_mtl = nil;
+ uint32_t index_buffer_offset = 0;
+
+ /* Region of scratch buffer used for topology emulation element data.
+ * NOTE(Metal): We do not need to manually flush as the entire scratch
+ * buffer for current command buffer is flushed upon submission. */
+ index_buffer_mtl = allocation.metal_buffer;
+ index_buffer_offset = allocation.buffer_offset;
+
+ /* Set depth stencil state (requires knowledge of primitive type). */
+ context_->ensure_depth_stencil_state(MTLPrimitiveTypeTriangle);
+
+ /* Bind Vertex Buffer. */
+ rps.bind_vertex_buffer(
+ current_allocation_.metal_buffer, current_allocation_.buffer_offset, 0);
+
+ /* Draw. */
+ [rec drawIndexedPrimitives:MTLPrimitiveTypeTriangle
+ indexCount:fan_index_count
+ indexType:MTLIndexTypeUInt32
+ indexBuffer:index_buffer_mtl
+ indexBufferOffset:index_buffer_offset];
+ }
+ }
+ else {
+ /* TODO(Metal): Topology emulation for line loop.
+ * NOTE(Metal): This is currently not used anywhere and modified at the high
+ * level for efficiency in such cases. */
+ BLI_assert_msg(false, "LineLoop requires emulation support in immediate mode.");
+ }
+ }
+ else {
+ MTLPrimitiveType primitive_type = metal_primitive_type_;
+ int vertex_count = this->vertex_idx;
+
+ /* Bind Vertex Buffer. */
+ rps.bind_vertex_buffer(
+ current_allocation_.metal_buffer, current_allocation_.buffer_offset, 0);
+
+ /* Set depth stencil state (requires knowledge of primitive type). */
+ context_->ensure_depth_stencil_state(primitive_type);
+
+ if (active_mtl_shader->get_uses_ssbo_vertex_fetch()) {
+
+ /* Bind Null Buffers for empty/missing bind slots. */
+ id<MTLBuffer> null_buffer = context_->get_null_buffer();
+ BLI_assert(null_buffer != nil);
+ for (int i = 1; i < MTL_SSBO_VERTEX_FETCH_MAX_VBOS; i++) {
+
+ /* We only need to ensure a buffer is bound to the context, its contents do not matter
+ * as it will not be used. */
+ if (rps.cached_vertex_buffer_bindings[i].metal_buffer == nil) {
+ rps.bind_vertex_buffer(null_buffer, 0, i);
+ }
+ }
+
+ /* SSBO vertex fetch - Nullify elements buffer. */
+ if (rps.cached_vertex_buffer_bindings[MTL_SSBO_VERTEX_FETCH_IBO_INDEX].metal_buffer ==
+ nil) {
+ rps.bind_vertex_buffer(null_buffer, 0, MTL_SSBO_VERTEX_FETCH_IBO_INDEX);
+ }
+
+ /* Submit draw call with modified vertex count, which reflects vertices per primitive
+ * defined in the USE_SSBO_VERTEX_FETCH pragma. */
+ int num_input_primitives = gpu_get_prim_count_from_type(vertex_count, this->prim_type);
+ int output_num_verts = num_input_primitives *
+ active_mtl_shader->get_ssbo_vertex_fetch_output_num_verts();
+#ifndef NDEBUG
+ BLI_assert(
+ mtl_vertex_count_fits_primitive_type(
+ output_num_verts, active_mtl_shader->get_ssbo_vertex_fetch_output_prim_type()) &&
+ "Output Vertex count is not compatible with the requested output vertex primitive "
+ "type");
+#endif
+ [rec drawPrimitives:active_mtl_shader->get_ssbo_vertex_fetch_output_prim_type()
+ vertexStart:0
+ vertexCount:output_num_verts];
+ context_->main_command_buffer.register_draw_counters(output_num_verts);
+ }
+ else {
+ /* Regular draw. */
+ [rec drawPrimitives:primitive_type vertexStart:0 vertexCount:vertex_count];
+ context_->main_command_buffer.register_draw_counters(vertex_count);
+ }
+ }
+ }
+ if (G.debug & G_DEBUG_GPU) {
+ [rec popDebugGroup];
+ }
+ }
+
+ /* Reset allocation after draw submission. */
+ has_begun_ = false;
+ if (current_allocation_.metal_buffer) {
+ [current_allocation_.metal_buffer release];
+ current_allocation_.metal_buffer = nil;
+ }
+}
+
+} // blender::gpu