/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup gpu */ #include "BKE_global.h" #include "mtl_context.hh" #include "mtl_debug.hh" #include "mtl_framebuffer.hh" #include "mtl_texture.hh" #import namespace blender::gpu { /* -------------------------------------------------------------------- */ /** \name Creation & Deletion * \{ */ MTLFrameBuffer::MTLFrameBuffer(MTLContext *ctx, const char *name) : FrameBuffer(name) { context_ = ctx; is_dirty_ = true; is_loadstore_dirty_ = true; dirty_state_ctx_ = nullptr; has_pending_clear_ = false; colour_attachment_count_ = 0; srgb_enabled_ = false; is_srgb_ = false; for (int i = 0; i < GPU_FB_MAX_COLOR_ATTACHMENT; i++) { mtl_color_attachments_[i].used = false; } mtl_depth_attachment_.used = false; mtl_stencil_attachment_.used = false; for (int i = 0; i < MTL_FB_CONFIG_MAX; i++) { framebuffer_descriptor_[i] = [[MTLRenderPassDescriptor alloc] init]; descriptor_dirty_[i] = true; } for (int i = 0; i < GPU_FB_MAX_COLOR_ATTACHMENT; i++) { colour_attachment_descriptors_[i] = [[MTLRenderPassColorAttachmentDescriptor alloc] init]; } /* Initial state. */ this->size_set(0, 0); this->viewport_reset(); this->scissor_reset(); } MTLFrameBuffer::~MTLFrameBuffer() { /* If FrameBuffer is associated with a currently open RenderPass, end. */ if (context_->main_command_buffer.get_active_framebuffer() == this) { context_->main_command_buffer.end_active_command_encoder(); } /* Restore default frame-buffer if this frame-buffer was bound. */ if (context_->active_fb == this && context_->back_left != this) { /* If this assert triggers it means the frame-buffer is being freed while in use by another * context which, by the way, is TOTALLY UNSAFE!!! (Copy from GL behavior). */ BLI_assert(context_ == static_cast(unwrap(GPU_context_active_get()))); GPU_framebuffer_restore(); } /* Free Render Pass Descriptors. */ for (int config = 0; config < MTL_FB_CONFIG_MAX; config++) { if (framebuffer_descriptor_[config] != nil) { [framebuffer_descriptor_[config] release]; framebuffer_descriptor_[config] = nil; } } /* Free colour attachment descriptors. */ for (int i = 0; i < GPU_FB_MAX_COLOR_ATTACHMENT; i++) { if (colour_attachment_descriptors_[i] != nil) { [colour_attachment_descriptors_[i] release]; colour_attachment_descriptors_[i] = nil; } } /* Remove attachments - release FB texture references. */ this->remove_all_attachments(); if (context_ == nullptr) { return; } } void MTLFrameBuffer::bind(bool enabled_srgb) { /* Verify Context is valid. */ if (context_ != static_cast(unwrap(GPU_context_active_get()))) { BLI_assert(false && "Trying to use the same frame-buffer in multiple context's."); return; } /* Ensure SRGB state is up-to-date and valid. */ bool srgb_state_changed = srgb_enabled_ != enabled_srgb; if (context_->active_fb != this || srgb_state_changed) { if (srgb_state_changed) { this->mark_dirty(); } srgb_enabled_ = enabled_srgb; GPU_shader_set_framebuffer_srgb_target(srgb_enabled_ && is_srgb_); } /* Ensure local MTLAttachment data is up to date. */ this->update_attachments(true); /* Reset clear state on bind -- Clears and load/store ops are set after binding. */ this->reset_clear_state(); /* Bind to active context. */ MTLContext *mtl_context = reinterpret_cast(GPU_context_active_get()); if (mtl_context) { mtl_context->framebuffer_bind(this); dirty_state_ = true; } else { MTL_LOG_WARNING("Attempting to bind FrameBuffer, but no context is active\n"); } } bool MTLFrameBuffer::check(char err_out[256]) { /* Ensure local MTLAttachment data is up to date. */ this->update_attachments(true); /* Ensure there is at least one attachment. */ bool valid = (this->get_attachment_count() > 0 || this->has_depth_attachment() | this->has_stencil_attachment()); if (!valid) { const char *format = "Framebuffer %s does not have any attachments.\n"; if (err_out) { BLI_snprintf(err_out, 256, format, name_); } else { MTL_LOG_ERROR(format, name_); } return false; } /* Ensure all attachments have identical dimensions. */ /* Ensure all attachments are render-targets. */ bool first = true; uint dim_x = 0; uint dim_y = 0; for (int col_att = 0; col_att < this->get_attachment_count(); col_att++) { MTLAttachment att = this->get_color_attachment(col_att); if (att.used) { if (att.texture->gpu_image_usage_flags_ & GPU_TEXTURE_USAGE_ATTACHMENT) { if (first) { dim_x = att.texture->width_get(); dim_y = att.texture->height_get(); first = false; } else { if (dim_x != att.texture->width_get() || dim_y != att.texture->height_get()) { const char *format = "Framebuffer %s: Color attachment dimensions do not match those of previous " "attachment\n"; if (err_out) { BLI_snprintf(err_out, 256, format, name_); } else { fprintf(stderr, format, name_); MTL_LOG_ERROR(format, name_); } return false; } } } else { const char *format = "Framebuffer %s: Color attachment texture does not have usage flag " "'GPU_TEXTURE_USAGE_ATTACHMENT'\n"; if (err_out) { BLI_snprintf(err_out, 256, format, name_); } else { fprintf(stderr, format, name_); MTL_LOG_ERROR(format, name_); } return false; } } } MTLAttachment depth_att = this->get_depth_attachment(); MTLAttachment stencil_att = this->get_stencil_attachment(); if (depth_att.used) { if (first) { dim_x = depth_att.texture->width_get(); dim_y = depth_att.texture->height_get(); first = false; valid = (depth_att.texture->gpu_image_usage_flags_ & GPU_TEXTURE_USAGE_ATTACHMENT); if (!valid) { const char *format = "Framebuffer %n: Depth attachment does not have usage " "'GPU_TEXTURE_USAGE_ATTACHMENT'\n"; if (err_out) { BLI_snprintf(err_out, 256, format, name_); } else { fprintf(stderr, format, name_); MTL_LOG_ERROR(format, name_); } return false; } } else { if (dim_x != depth_att.texture->width_get() || dim_y != depth_att.texture->height_get()) { const char *format = "Framebuffer %n: Depth attachment dimensions do not match that of previous " "attachment\n"; if (err_out) { BLI_snprintf(err_out, 256, format, name_); } else { fprintf(stderr, format, name_); MTL_LOG_ERROR(format, name_); } return false; } } } if (stencil_att.used) { if (first) { dim_x = stencil_att.texture->width_get(); dim_y = stencil_att.texture->height_get(); first = false; valid = (stencil_att.texture->gpu_image_usage_flags_ & GPU_TEXTURE_USAGE_ATTACHMENT); if (!valid) { const char *format = "Framebuffer %s: Stencil attachment does not have usage " "'GPU_TEXTURE_USAGE_ATTACHMENT'\n"; if (err_out) { BLI_snprintf(err_out, 256, format, name_); } else { fprintf(stderr, format, name_); MTL_LOG_ERROR(format, name_); } return false; } } else { if (dim_x != stencil_att.texture->width_get() || dim_y != stencil_att.texture->height_get()) { const char *format = "Framebuffer %s: Stencil attachment dimensions do not match that of previous " "attachment"; if (err_out) { BLI_snprintf(err_out, 256, format, name_); } else { fprintf(stderr, format, name_); MTL_LOG_ERROR(format, name_); } return false; } } } BLI_assert(valid); return valid; } void MTLFrameBuffer::force_clear() { /* Perform clear by ending current and starting a new render pass. */ MTLContext *mtl_context = static_cast(unwrap(GPU_context_active_get())); MTLFrameBuffer *current_framebuffer = mtl_context->get_current_framebuffer(); if (current_framebuffer) { BLI_assert(current_framebuffer == this); /* End current render-pass. */ if (mtl_context->main_command_buffer.is_inside_render_pass()) { mtl_context->main_command_buffer.end_active_command_encoder(); } mtl_context->ensure_begin_render_pass(); BLI_assert(has_pending_clear_ == false); } } void MTLFrameBuffer::clear(eGPUFrameBufferBits buffers, const float clear_col[4], float clear_depth, uint clear_stencil) { BLI_assert(unwrap(GPU_context_active_get()) == context_); BLI_assert(context_->active_fb == this); /* Ensure attachments are up to date. */ this->update_attachments(true); /* If we had no previous clear pending, reset clear state. */ if (!has_pending_clear_) { this->reset_clear_state(); } /* Ensure we only clear if attachments exist for given buffer bits. */ bool do_clear = false; if (buffers & GPU_COLOR_BIT) { for (int i = 0; i < colour_attachment_count_; i++) { this->set_color_attachment_clear_color(i, clear_col); do_clear = true; } } if (buffers & GPU_DEPTH_BIT) { this->set_depth_attachment_clear_value(clear_depth); do_clear = do_clear || this->has_depth_attachment(); } if (buffers & GPU_STENCIL_BIT) { this->set_stencil_attachment_clear_value(clear_stencil); do_clear = do_clear || this->has_stencil_attachment(); } if (do_clear) { has_pending_clear_ = true; /* Apply state before clear. */ this->apply_state(); /* TODO(Metal): Optimize - Currently force-clear always used. Consider moving clear state to * MTLTexture instead. */ /* Force clear if RP is not yet active -- not the most efficient, but there is no distinction * between clears where no draws occur. Can optimize at the high-level by using explicit * load-store flags. */ this->force_clear(); } } void MTLFrameBuffer::clear_multi(const float (*clear_cols)[4]) { /* If we had no previous clear pending, reset clear state. */ if (!has_pending_clear_) { this->reset_clear_state(); } bool do_clear = false; for (int i = 0; i < this->get_attachment_limit(); i++) { if (this->has_attachment_at_slot(i)) { this->set_color_attachment_clear_color(i, clear_cols[i]); do_clear = true; } } if (do_clear) { has_pending_clear_ = true; /* Apply state before clear. */ this->apply_state(); /* TODO(Metal): Optimize - Currently force-clear always used. Consider moving clear state to * MTLTexture instead. */ /* Force clear if RP is not yet active -- not the most efficient, but there is no distinction * between clears where no draws occur. Can optimize at the high-level by using explicit * load-store flags. */ this->force_clear(); } } void MTLFrameBuffer::clear_attachment(GPUAttachmentType type, eGPUDataFormat data_format, const void *clear_value) { BLI_assert(static_cast(unwrap(GPU_context_active_get())) == context_); BLI_assert(context_->active_fb == this); /* If we had no previous clear pending, reset clear state. */ if (!has_pending_clear_) { this->reset_clear_state(); } bool do_clear = false; if (type == GPU_FB_DEPTH_STENCIL_ATTACHMENT) { if (this->has_depth_attachment() || this->has_stencil_attachment()) { BLI_assert(data_format == GPU_DATA_UINT_24_8); float depth = ((*(uint32_t *)clear_value) & 0x00FFFFFFu) / (float)0x00FFFFFFu; int stencil = ((*(uint32_t *)clear_value) >> 24); this->set_depth_attachment_clear_value(depth); this->set_stencil_attachment_clear_value(stencil); do_clear = true; } } else if (type == GPU_FB_DEPTH_ATTACHMENT) { if (this->has_depth_attachment()) { if (data_format == GPU_DATA_FLOAT) { this->set_depth_attachment_clear_value(*(float *)clear_value); } else { float depth = *(uint32_t *)clear_value / (float)0xFFFFFFFFu; this->set_depth_attachment_clear_value(depth); } do_clear = true; } } else { int slot = type - GPU_FB_COLOR_ATTACHMENT0; if (this->has_attachment_at_slot(slot)) { float col_clear_val[4] = {0.0}; switch (data_format) { case GPU_DATA_FLOAT: { const float *vals = (float *)clear_value; col_clear_val[0] = vals[0]; col_clear_val[1] = vals[1]; col_clear_val[2] = vals[2]; col_clear_val[3] = vals[3]; } break; case GPU_DATA_UINT: { const uint *vals = (uint *)clear_value; col_clear_val[0] = (float)(vals[0]); col_clear_val[1] = (float)(vals[1]); col_clear_val[2] = (float)(vals[2]); col_clear_val[3] = (float)(vals[3]); } break; case GPU_DATA_INT: { const int *vals = (int *)clear_value; col_clear_val[0] = (float)(vals[0]); col_clear_val[1] = (float)(vals[1]); col_clear_val[2] = (float)(vals[2]); col_clear_val[3] = (float)(vals[3]); } break; default: BLI_assert_msg(0, "Unhandled data format"); break; } this->set_color_attachment_clear_color(slot, col_clear_val); do_clear = true; } } if (do_clear) { has_pending_clear_ = true; /* Apply state before clear. */ this->apply_state(); /* TODO(Metal): Optimize - Currently force-clear always used. Consider moving clear state to * MTLTexture instead. */ /* Force clear if RP is not yet active -- not the most efficient, but there is no distinction * between clears where no draws occur. Can optimize at the high-level by using explicit * load-store flags. */ this->force_clear(); } } void MTLFrameBuffer::read(eGPUFrameBufferBits planes, eGPUDataFormat format, const int area[4], int channel_len, int slot, void *r_data) { BLI_assert((planes & GPU_STENCIL_BIT) == 0); BLI_assert(area[2] > 0); BLI_assert(area[3] > 0); switch (planes) { case GPU_DEPTH_BIT: { if (this->has_depth_attachment()) { MTLAttachment depth = this->get_depth_attachment(); gpu::MTLTexture *tex = depth.texture; if (tex) { size_t sample_len = area[2] * area[3]; size_t sample_size = to_bytesize(tex->format_, format); int debug_data_size = sample_len * sample_size; tex->read_internal(0, area[0], area[1], 0, area[2], area[3], 1, format, channel_len, debug_data_size, r_data); } } else { MTL_LOG_ERROR( "Attempting to read depth from a framebuffer which does not have a depth " "attachment!\n"); } } return; case GPU_COLOR_BIT: { if (this->has_attachment_at_slot(slot)) { MTLAttachment color = this->get_color_attachment(slot); gpu::MTLTexture *tex = color.texture; if (tex) { size_t sample_len = area[2] * area[3]; size_t sample_size = to_bytesize(tex->format_, format); int debug_data_size = sample_len * sample_size * channel_len; tex->read_internal(0, area[0], area[1], 0, area[2], area[3], 1, format, channel_len, debug_data_size, r_data); } } } return; case GPU_STENCIL_BIT: MTL_LOG_ERROR("GPUFramebuffer: Error: Trying to read stencil bit. Unsupported.\n"); return; } } void MTLFrameBuffer::blit_to(eGPUFrameBufferBits planes, int src_slot, FrameBuffer *dst, int dst_slot, int dst_offset_x, int dst_offset_y) { this->update_attachments(true); static_cast(dst)->update_attachments(true); BLI_assert(planes != 0); MTLFrameBuffer *metal_fb_write = static_cast(dst); BLI_assert(this); BLI_assert(metal_fb_write); /* Get width/height from attachment. */ MTLAttachment src_attachment; const bool do_color = (planes & GPU_COLOR_BIT); const bool do_depth = (planes & GPU_DEPTH_BIT); const bool do_stencil = (planes & GPU_STENCIL_BIT); if (do_color) { BLI_assert(!do_depth && !do_stencil); src_attachment = this->get_color_attachment(src_slot); } else if (do_depth) { BLI_assert(!do_color && !do_stencil); src_attachment = this->get_depth_attachment(); } else if (do_stencil) { BLI_assert(!do_color && !do_depth); src_attachment = this->get_stencil_attachment(); } BLI_assert(src_attachment.used); this->blit(src_slot, 0, 0, metal_fb_write, dst_slot, dst_offset_x, dst_offset_y, src_attachment.texture->width_get(), src_attachment.texture->height_get(), planes); } /** \} */ /* -------------------------------------------------------------------- */ /** \ Private METAL implementation functions * \{ */ void MTLFrameBuffer::mark_dirty() { is_dirty_ = true; is_loadstore_dirty_ = true; } void MTLFrameBuffer::mark_loadstore_dirty() { is_loadstore_dirty_ = true; } void MTLFrameBuffer::mark_cleared() { has_pending_clear_ = false; } void MTLFrameBuffer::mark_do_clear() { has_pending_clear_ = true; } void MTLFrameBuffer::update_attachments(bool update_viewport) { if (!dirty_attachments_) { return; } /* Cache viewport and scissor (If we have existing attachments). */ int t_viewport[4], t_scissor[4]; update_viewport = update_viewport && (this->get_attachment_count() > 0 && this->has_depth_attachment() && this->has_stencil_attachment()); if (update_viewport) { this->viewport_get(t_viewport); this->scissor_get(t_scissor); } /* Clear current attachments state. */ this->remove_all_attachments(); /* Reset framebuffer options. */ use_multilayered_rendering_ = false; /* Track first attachment for SRGB property extraction. */ GPUAttachmentType first_attachment = GPU_FB_MAX_ATTACHMENT; MTLAttachment first_attachment_mtl; /* Scan through changes to attachments and populate local structures. */ bool depth_added = false; for (GPUAttachmentType type = GPU_FB_MAX_ATTACHMENT - 1; type >= 0; --type) { GPUAttachment &attach = attachments_[type]; switch (type) { case GPU_FB_DEPTH_ATTACHMENT: case GPU_FB_DEPTH_STENCIL_ATTACHMENT: { /* If one of the DEPTH types has added a texture, we avoid running this again, as it would * only remove the target. */ if (depth_added) { break; } if (attach.tex) { /* If we already had a depth attachment, preserve load/clear-state parameters, * but remove existing and add new attachment. */ if (this->has_depth_attachment()) { MTLAttachment depth_attachment_prev = this->get_depth_attachment(); this->remove_depth_attachment(); this->add_depth_attachment( static_cast(unwrap(attach.tex)), attach.mip, attach.layer); this->set_depth_attachment_clear_value(depth_attachment_prev.clear_value.depth); this->set_depth_loadstore_op(depth_attachment_prev.load_action, depth_attachment_prev.store_action); } else { this->add_depth_attachment( static_cast(unwrap(attach.tex)), attach.mip, attach.layer); } /* Check stencil component -- if supplied texture format supports stencil. */ eGPUTextureFormat format = GPU_texture_format(attach.tex); bool use_stencil = (type == GPU_FB_DEPTH_STENCIL_ATTACHMENT) && (format == GPU_DEPTH32F_STENCIL8 || format == GPU_DEPTH24_STENCIL8); if (use_stencil) { if (this->has_stencil_attachment()) { MTLAttachment stencil_attachment_prev = this->get_stencil_attachment(); this->remove_stencil_attachment(); this->add_stencil_attachment( static_cast(unwrap(attach.tex)), attach.mip, attach.layer); this->set_stencil_attachment_clear_value( stencil_attachment_prev.clear_value.stencil); this->set_stencil_loadstore_op(stencil_attachment_prev.load_action, stencil_attachment_prev.store_action); } else { this->add_stencil_attachment( static_cast(unwrap(attach.tex)), attach.mip, attach.layer); } } /* Flag depth as added -- mirrors the behavior in gl_framebuffer.cc to exit the for-loop * after GPU_FB_DEPTH_STENCIL_ATTACHMENT has executed. */ depth_added = true; if (first_attachment == GPU_FB_MAX_ATTACHMENT) { /* Only use depth texture to get information if there is no color attachment. */ first_attachment = type; first_attachment_mtl = this->get_depth_attachment(); } } else { this->remove_depth_attachment(); if (type == GPU_FB_DEPTH_STENCIL_ATTACHMENT && this->has_stencil_attachment()) { this->remove_stencil_attachment(); } } } break; case GPU_FB_COLOR_ATTACHMENT0: case GPU_FB_COLOR_ATTACHMENT1: case GPU_FB_COLOR_ATTACHMENT2: case GPU_FB_COLOR_ATTACHMENT3: case GPU_FB_COLOR_ATTACHMENT4: case GPU_FB_COLOR_ATTACHMENT5: { int color_slot_ind = type - GPU_FB_COLOR_ATTACHMENT0; if (attach.tex) { /* If we already had a colour attachment, preserve load/clear-state parameters, * but remove existing and add new attachment. */ if (this->has_attachment_at_slot(color_slot_ind)) { MTLAttachment color_attachment_prev = this->get_color_attachment(color_slot_ind); this->remove_color_attachment(color_slot_ind); this->add_color_attachment(static_cast(unwrap(attach.tex)), color_slot_ind, attach.mip, attach.layer); this->set_color_attachment_clear_color(color_slot_ind, color_attachment_prev.clear_value.color); this->set_color_loadstore_op(color_slot_ind, color_attachment_prev.load_action, color_attachment_prev.store_action); } else { this->add_color_attachment(static_cast(unwrap(attach.tex)), color_slot_ind, attach.mip, attach.layer); } first_attachment = type; first_attachment_mtl = this->get_color_attachment(color_slot_ind); } else { this->remove_color_attachment(color_slot_ind); } } break; default: /* Non-attachment parameters. */ break; } } /* Check whether the first attachment is SRGB. */ if (first_attachment != GPU_FB_MAX_ATTACHMENT) { is_srgb_ = (first_attachment_mtl.texture->format_get() == GPU_SRGB8_A8); } /* Reset viewport and Scissor (If viewport is smaller or equal to the framebuffer size). */ if (update_viewport && t_viewport[2] <= width_ && t_viewport[3] <= height_) { this->viewport_set(t_viewport); this->scissor_set(t_viewport); } else { this->viewport_reset(); this->scissor_reset(); } /* We have now updated our internal structures. */ dirty_attachments_ = false; } void MTLFrameBuffer::apply_state() { MTLContext *mtl_ctx = static_cast(unwrap(GPU_context_active_get())); BLI_assert(mtl_ctx); if (mtl_ctx->active_fb == this) { if (dirty_state_ == false && dirty_state_ctx_ == mtl_ctx) { return; } /* Ensure viewport has been set. NOTE: This should no longer happen, but kept for safety to * track bugs. */ if (viewport_[2] == 0 || viewport_[3] == 0) { MTL_LOG_WARNING( "Viewport had width and height of (0,0) -- Updating -- DEBUG Safety check\n"); viewport_reset(); } /* Update Context State. */ mtl_ctx->set_viewport(viewport_[0], viewport_[1], viewport_[2], viewport_[3]); mtl_ctx->set_scissor(scissor_[0], scissor_[1], scissor_[2], scissor_[3]); mtl_ctx->set_scissor_enabled(scissor_test_); dirty_state_ = false; dirty_state_ctx_ = mtl_ctx; } else { MTL_LOG_ERROR( "Attempting to set FrameBuffer State (VIEWPORT, SCISSOR), But FrameBuffer is not bound to " "current Context.\n"); } } /** \} */ /* -------------------------------------------------------------------- */ /** \ Adding and Removing attachments * \{ */ bool MTLFrameBuffer::add_color_attachment(gpu::MTLTexture *texture, uint slot, int miplevel, int layer) { BLI_assert(this); BLI_assert(slot >= 0 && slot < this->get_attachment_limit()); if (texture) { if (miplevel < 0 || miplevel >= MTL_MAX_MIPMAP_COUNT) { MTL_LOG_WARNING("Attachment specified with invalid mip level %u\n", miplevel); miplevel = 0; } /* Check if slot is in-use. */ /* Assume attachment load by default. */ colour_attachment_count_ += (!mtl_color_attachments_[slot].used) ? 1 : 0; mtl_color_attachments_[slot].used = true; mtl_color_attachments_[slot].texture = texture; mtl_color_attachments_[slot].mip = miplevel; mtl_color_attachments_[slot].load_action = GPU_LOADACTION_LOAD; mtl_color_attachments_[slot].store_action = GPU_STOREACTION_STORE; mtl_color_attachments_[slot].render_target_array_length = 0; /* Determine whether array slice or depth plane based on texture type. */ switch (texture->type_) { case GPU_TEXTURE_1D: case GPU_TEXTURE_2D: BLI_assert(layer <= 0); mtl_color_attachments_[slot].slice = 0; mtl_color_attachments_[slot].depth_plane = 0; break; case GPU_TEXTURE_1D_ARRAY: if (layer < 0) { layer = 0; MTL_LOG_WARNING("TODO: Support layered rendering for 1D array textures, if needed.\n"); } BLI_assert(layer < texture->h_); mtl_color_attachments_[slot].slice = layer; mtl_color_attachments_[slot].depth_plane = 0; break; case GPU_TEXTURE_2D_ARRAY: BLI_assert(layer < texture->d_); mtl_color_attachments_[slot].slice = layer; mtl_color_attachments_[slot].depth_plane = 0; if (layer == -1) { mtl_color_attachments_[slot].slice = 0; mtl_color_attachments_[slot].render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_3D: BLI_assert(layer < texture->d_); mtl_color_attachments_[slot].slice = 0; mtl_color_attachments_[slot].depth_plane = layer; if (layer == -1) { mtl_color_attachments_[slot].depth_plane = 0; mtl_color_attachments_[slot].render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_CUBE: BLI_assert(layer < 6); mtl_color_attachments_[slot].slice = layer; mtl_color_attachments_[slot].depth_plane = 0; if (layer == -1) { mtl_color_attachments_[slot].slice = 0; mtl_color_attachments_[slot].depth_plane = 0; mtl_color_attachments_[slot].render_target_array_length = 6; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_CUBE_ARRAY: BLI_assert(layer < 6 * texture->d_); /* TODO(Metal): Verify multilayered rendering for Cube arrays. */ mtl_color_attachments_[slot].slice = layer; mtl_color_attachments_[slot].depth_plane = 0; if (layer == -1) { mtl_color_attachments_[slot].slice = 0; mtl_color_attachments_[slot].depth_plane = 0; mtl_color_attachments_[slot].render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_BUFFER: mtl_color_attachments_[slot].slice = 0; mtl_color_attachments_[slot].depth_plane = 0; break; default: MTL_LOG_ERROR("MTLFrameBuffer::add_color_attachment Unrecognized texture type %u\n", texture->type_); break; } /* Update Frame-buffer Resolution. */ int width_of_miplayer, height_of_miplayer; if (miplevel <= 0) { width_of_miplayer = texture->width_get(); height_of_miplayer = texture->height_get(); } else { width_of_miplayer = max_ii(texture->width_get() >> miplevel, 1); height_of_miplayer = max_ii(texture->height_get() >> miplevel, 1); } if (width_ == 0 || height_ == 0) { this->size_set(width_of_miplayer, height_of_miplayer); this->scissor_reset(); this->viewport_reset(); BLI_assert(width_ > 0); BLI_assert(height_ > 0); } else { BLI_assert(width_ == width_of_miplayer); BLI_assert(height_ == height_of_miplayer); } /* Flag as dirty. */ this->mark_dirty(); } else { MTL_LOG_ERROR( "Passing in null texture to MTLFrameBuffer::addColourAttachment (This could be due to not " "all texture types being supported).\n"); } return true; } bool MTLFrameBuffer::add_depth_attachment(gpu::MTLTexture *texture, int miplevel, int layer) { BLI_assert(this); if (texture) { if (miplevel < 0 || miplevel >= MTL_MAX_MIPMAP_COUNT) { MTL_LOG_WARNING("Attachment specified with invalid mip level %u\n", miplevel); miplevel = 0; } /* Assume attachment load by default. */ mtl_depth_attachment_.used = true; mtl_depth_attachment_.texture = texture; mtl_depth_attachment_.mip = miplevel; mtl_depth_attachment_.load_action = GPU_LOADACTION_LOAD; mtl_depth_attachment_.store_action = GPU_STOREACTION_STORE; mtl_depth_attachment_.render_target_array_length = 0; /* Determine whether array slice or depth plane based on texture type. */ switch (texture->type_) { case GPU_TEXTURE_1D: case GPU_TEXTURE_2D: BLI_assert(layer <= 0); mtl_depth_attachment_.slice = 0; mtl_depth_attachment_.depth_plane = 0; break; case GPU_TEXTURE_1D_ARRAY: if (layer < 0) { layer = 0; MTL_LOG_WARNING("TODO: Support layered rendering for 1D array textures, if needed\n"); } BLI_assert(layer < texture->h_); mtl_depth_attachment_.slice = layer; mtl_depth_attachment_.depth_plane = 0; break; case GPU_TEXTURE_2D_ARRAY: BLI_assert(layer < texture->d_); mtl_depth_attachment_.slice = layer; mtl_depth_attachment_.depth_plane = 0; if (layer == -1) { mtl_depth_attachment_.slice = 0; mtl_depth_attachment_.render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_3D: BLI_assert(layer < texture->d_); mtl_depth_attachment_.slice = 0; mtl_depth_attachment_.depth_plane = layer; if (layer == -1) { mtl_depth_attachment_.depth_plane = 0; mtl_depth_attachment_.render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_CUBE: BLI_assert(layer < 6); mtl_depth_attachment_.slice = layer; mtl_depth_attachment_.depth_plane = 0; if (layer == -1) { mtl_depth_attachment_.slice = 0; mtl_depth_attachment_.depth_plane = 0; mtl_depth_attachment_.render_target_array_length = 1; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_CUBE_ARRAY: /* TODO(Metal): Verify multilayered rendering for Cube arrays. */ BLI_assert(layer < 6 * texture->d_); mtl_depth_attachment_.slice = layer; mtl_depth_attachment_.depth_plane = 0; if (layer == -1) { mtl_depth_attachment_.slice = 0; mtl_depth_attachment_.depth_plane = 0; mtl_depth_attachment_.render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_BUFFER: mtl_depth_attachment_.slice = 0; mtl_depth_attachment_.depth_plane = 0; break; default: BLI_assert(false && "Unrecognized texture type"); break; } /* Update Frame-buffer Resolution. */ int width_of_miplayer, height_of_miplayer; if (miplevel <= 0) { width_of_miplayer = texture->width_get(); height_of_miplayer = texture->height_get(); } else { width_of_miplayer = max_ii(texture->width_get() >> miplevel, 1); height_of_miplayer = max_ii(texture->height_get() >> miplevel, 1); } /* Update Frame-buffer Resolution. */ if (width_ == 0 || height_ == 0) { this->size_set(width_of_miplayer, height_of_miplayer); this->scissor_reset(); this->viewport_reset(); BLI_assert(width_ > 0); BLI_assert(height_ > 0); } else { BLI_assert(width_ == texture->width_get()); BLI_assert(height_ == texture->height_get()); } /* Flag as dirty after attachments changed. */ this->mark_dirty(); } else { MTL_LOG_ERROR( "Passing in null texture to MTLFrameBuffer::addDepthAttachment (This could be due to not " "all texture types being supported)."); } return true; } bool MTLFrameBuffer::add_stencil_attachment(gpu::MTLTexture *texture, int miplevel, int layer) { BLI_assert(this); if (texture) { if (miplevel < 0 || miplevel >= MTL_MAX_MIPMAP_COUNT) { MTL_LOG_WARNING("Attachment specified with invalid mip level %u\n", miplevel); miplevel = 0; } /* Assume attachment load by default. */ mtl_stencil_attachment_.used = true; mtl_stencil_attachment_.texture = texture; mtl_stencil_attachment_.mip = miplevel; mtl_stencil_attachment_.load_action = GPU_LOADACTION_LOAD; mtl_stencil_attachment_.store_action = GPU_STOREACTION_STORE; mtl_stencil_attachment_.render_target_array_length = 0; /* Determine whether array slice or depth plane based on texture type. */ switch (texture->type_) { case GPU_TEXTURE_1D: case GPU_TEXTURE_2D: BLI_assert(layer <= 0); mtl_stencil_attachment_.slice = 0; mtl_stencil_attachment_.depth_plane = 0; break; case GPU_TEXTURE_1D_ARRAY: if (layer < 0) { layer = 0; MTL_LOG_WARNING("TODO: Support layered rendering for 1D array textures, if needed\n"); } BLI_assert(layer < texture->h_); mtl_stencil_attachment_.slice = layer; mtl_stencil_attachment_.depth_plane = 0; break; case GPU_TEXTURE_2D_ARRAY: BLI_assert(layer < texture->d_); mtl_stencil_attachment_.slice = layer; mtl_stencil_attachment_.depth_plane = 0; if (layer == -1) { mtl_stencil_attachment_.slice = 0; mtl_stencil_attachment_.render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_3D: BLI_assert(layer < texture->d_); mtl_stencil_attachment_.slice = 0; mtl_stencil_attachment_.depth_plane = layer; if (layer == -1) { mtl_stencil_attachment_.depth_plane = 0; mtl_stencil_attachment_.render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_CUBE: BLI_assert(layer < 6); mtl_stencil_attachment_.slice = layer; mtl_stencil_attachment_.depth_plane = 0; if (layer == -1) { mtl_stencil_attachment_.slice = 0; mtl_stencil_attachment_.depth_plane = 0; mtl_stencil_attachment_.render_target_array_length = 1; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_CUBE_ARRAY: /* TODO(Metal): Verify multilayered rendering for Cube arrays. */ BLI_assert(layer < 6 * texture->d_); mtl_stencil_attachment_.slice = layer; mtl_stencil_attachment_.depth_plane = 0; if (layer == -1) { mtl_stencil_attachment_.slice = 0; mtl_stencil_attachment_.depth_plane = 0; mtl_stencil_attachment_.render_target_array_length = texture->d_; use_multilayered_rendering_ = true; } break; case GPU_TEXTURE_BUFFER: mtl_stencil_attachment_.slice = 0; mtl_stencil_attachment_.depth_plane = 0; break; default: BLI_assert(false && "Unrecognized texture type"); break; } /* Update Frame-buffer Resolution. */ int width_of_miplayer, height_of_miplayer; if (miplevel <= 0) { width_of_miplayer = texture->width_get(); height_of_miplayer = texture->height_get(); } else { width_of_miplayer = max_ii(texture->width_get() >> miplevel, 1); height_of_miplayer = max_ii(texture->height_get() >> miplevel, 1); } /* Update Frame-buffer Resolution. */ if (width_ == 0 || height_ == 0) { this->size_set(width_of_miplayer, height_of_miplayer); this->scissor_reset(); this->viewport_reset(); BLI_assert(width_ > 0); BLI_assert(height_ > 0); } else { BLI_assert(width_ == texture->width_get()); BLI_assert(height_ == texture->height_get()); } /* Flag as dirty after attachments changed. */ this->mark_dirty(); } else { MTL_LOG_ERROR( "Passing in null texture to MTLFrameBuffer::addStencilAttachment (This could be due to " "not all texture types being supported)."); } return true; } bool MTLFrameBuffer::remove_color_attachment(uint slot) { BLI_assert(this); BLI_assert(slot >= 0 && slot < this->get_attachment_limit()); if (this->has_attachment_at_slot(slot)) { colour_attachment_count_ -= (mtl_color_attachments_[slot].used) ? 1 : 0; mtl_color_attachments_[slot].used = false; this->ensure_render_target_size(); this->mark_dirty(); return true; } return false; } bool MTLFrameBuffer::remove_depth_attachment() { BLI_assert(this); mtl_depth_attachment_.used = false; mtl_depth_attachment_.texture = nullptr; this->ensure_render_target_size(); this->mark_dirty(); return true; } bool MTLFrameBuffer::remove_stencil_attachment() { BLI_assert(this); mtl_stencil_attachment_.used = false; mtl_stencil_attachment_.texture = nullptr; this->ensure_render_target_size(); this->mark_dirty(); return true; } void MTLFrameBuffer::remove_all_attachments() { BLI_assert(this); for (int attachment = 0; attachment < GPU_FB_MAX_COLOR_ATTACHMENT; attachment++) { this->remove_color_attachment(attachment); } this->remove_depth_attachment(); this->remove_stencil_attachment(); colour_attachment_count_ = 0; this->mark_dirty(); /* Verify height. */ this->ensure_render_target_size(); /* Flag attachments as no longer being dirty. */ dirty_attachments_ = false; } void MTLFrameBuffer::ensure_render_target_size() { /* If we have no attachments, reset width and height to zero. */ if (colour_attachment_count_ == 0 && !this->has_depth_attachment() && !this->has_stencil_attachment()) { /* Reset Viewport and Scissor for NULL framebuffer. */ this->size_set(0, 0); this->scissor_reset(); this->viewport_reset(); } } /** \} */ /* -------------------------------------------------------------------- */ /** \ Clear values and Load-store actions * \{ */ void MTLFrameBuffer::attachment_set_loadstore_op(GPUAttachmentType type, eGPULoadOp load_action, eGPUStoreOp store_action) { if (type >= GPU_FB_COLOR_ATTACHMENT0) { int slot = type - GPU_FB_COLOR_ATTACHMENT0; this->set_color_loadstore_op(slot, load_action, store_action); } else if (type == GPU_FB_DEPTH_STENCIL_ATTACHMENT) { this->set_depth_loadstore_op(load_action, store_action); this->set_stencil_loadstore_op(load_action, store_action); } else if (type == GPU_FB_DEPTH_ATTACHMENT) { this->set_depth_loadstore_op(load_action, store_action); } } bool MTLFrameBuffer::set_color_attachment_clear_color(uint slot, const float clear_color[4]) { BLI_assert(this); BLI_assert(slot >= 0 && slot < this->get_attachment_limit()); /* Only mark as dirty if values have changed. */ bool changed = mtl_color_attachments_[slot].load_action != GPU_LOADACTION_CLEAR; changed = changed || (memcmp(mtl_color_attachments_[slot].clear_value.color, clear_color, sizeof(float) * 4) != 0); if (changed) { memcpy(mtl_color_attachments_[slot].clear_value.color, clear_color, sizeof(float) * 4); } mtl_color_attachments_[slot].load_action = GPU_LOADACTION_CLEAR; if (changed) { this->mark_loadstore_dirty(); } return true; } bool MTLFrameBuffer::set_depth_attachment_clear_value(float depth_clear) { BLI_assert(this); if (mtl_depth_attachment_.clear_value.depth != depth_clear || mtl_depth_attachment_.load_action != GPU_LOADACTION_CLEAR) { mtl_depth_attachment_.clear_value.depth = depth_clear; mtl_depth_attachment_.load_action = GPU_LOADACTION_CLEAR; this->mark_loadstore_dirty(); } return true; } bool MTLFrameBuffer::set_stencil_attachment_clear_value(uint stencil_clear) { BLI_assert(this); if (mtl_stencil_attachment_.clear_value.stencil != stencil_clear || mtl_stencil_attachment_.load_action != GPU_LOADACTION_CLEAR) { mtl_stencil_attachment_.clear_value.stencil = stencil_clear; mtl_stencil_attachment_.load_action = GPU_LOADACTION_CLEAR; this->mark_loadstore_dirty(); } return true; } bool MTLFrameBuffer::set_color_loadstore_op(uint slot, eGPULoadOp load_action, eGPUStoreOp store_action) { BLI_assert(this); eGPULoadOp prev_load_action = mtl_color_attachments_[slot].load_action; eGPUStoreOp prev_store_action = mtl_color_attachments_[slot].store_action; mtl_color_attachments_[slot].load_action = load_action; mtl_color_attachments_[slot].store_action = store_action; bool changed = (mtl_color_attachments_[slot].load_action != prev_load_action || mtl_color_attachments_[slot].store_action != prev_store_action); if (changed) { this->mark_loadstore_dirty(); } return changed; } bool MTLFrameBuffer::set_depth_loadstore_op(eGPULoadOp load_action, eGPUStoreOp store_action) { BLI_assert(this); eGPULoadOp prev_load_action = mtl_depth_attachment_.load_action; eGPUStoreOp prev_store_action = mtl_depth_attachment_.store_action; mtl_depth_attachment_.load_action = load_action; mtl_depth_attachment_.store_action = store_action; bool changed = (mtl_depth_attachment_.load_action != prev_load_action || mtl_depth_attachment_.store_action != prev_store_action); if (changed) { this->mark_loadstore_dirty(); } return changed; } bool MTLFrameBuffer::set_stencil_loadstore_op(eGPULoadOp load_action, eGPUStoreOp store_action) { BLI_assert(this); eGPULoadOp prev_load_action = mtl_stencil_attachment_.load_action; eGPUStoreOp prev_store_action = mtl_stencil_attachment_.store_action; mtl_stencil_attachment_.load_action = load_action; mtl_stencil_attachment_.store_action = store_action; bool changed = (mtl_stencil_attachment_.load_action != prev_load_action || mtl_stencil_attachment_.store_action != prev_store_action); if (changed) { this->mark_loadstore_dirty(); } return changed; } bool MTLFrameBuffer::reset_clear_state() { for (int slot = 0; slot < colour_attachment_count_; slot++) { this->set_color_loadstore_op(slot, GPU_LOADACTION_LOAD, GPU_STOREACTION_STORE); } this->set_depth_loadstore_op(GPU_LOADACTION_LOAD, GPU_STOREACTION_STORE); this->set_stencil_loadstore_op(GPU_LOADACTION_LOAD, GPU_STOREACTION_STORE); return true; } /** \} */ /* -------------------------------------------------------------------- */ /** \ Fetch values and Frame-buffer status * \{ */ bool MTLFrameBuffer::has_attachment_at_slot(uint slot) { BLI_assert(this); if (slot >= 0 && slot < this->get_attachment_limit()) { return mtl_color_attachments_[slot].used; } return false; } bool MTLFrameBuffer::has_color_attachment_with_texture(gpu::MTLTexture *texture) { BLI_assert(this); for (int attachment = 0; attachment < this->get_attachment_limit(); attachment++) { if (mtl_color_attachments_[attachment].used && mtl_color_attachments_[attachment].texture == texture) { return true; } } return false; } bool MTLFrameBuffer::has_depth_attachment() { BLI_assert(this); return mtl_depth_attachment_.used; } bool MTLFrameBuffer::has_stencil_attachment() { BLI_assert(this); return mtl_stencil_attachment_.used; } int MTLFrameBuffer::get_color_attachment_slot_from_texture(gpu::MTLTexture *texture) { BLI_assert(this); BLI_assert(texture); for (int attachment = 0; attachment < this->get_attachment_limit(); attachment++) { if (mtl_color_attachments_[attachment].used && (mtl_color_attachments_[attachment].texture == texture)) { return attachment; } } return -1; } uint MTLFrameBuffer::get_attachment_count() { BLI_assert(this); return colour_attachment_count_; } MTLAttachment MTLFrameBuffer::get_color_attachment(uint slot) { BLI_assert(this); if (slot >= 0 && slot < GPU_FB_MAX_COLOR_ATTACHMENT) { return mtl_color_attachments_[slot]; } MTLAttachment null_attachment; null_attachment.used = false; return null_attachment; } MTLAttachment MTLFrameBuffer::get_depth_attachment() { BLI_assert(this); return mtl_depth_attachment_; } MTLAttachment MTLFrameBuffer::get_stencil_attachment() { BLI_assert(this); return mtl_stencil_attachment_; } /** \} */ /* -------------------------------------------------------------------- */ /** \ METAL API Resources and Validation * \{ */ bool MTLFrameBuffer::validate_render_pass() { BLI_assert(this); /* First update attachments if dirty. */ this->update_attachments(true); /* Verify attachment count. */ int used_attachments = 0; for (int attachment = 0; attachment < GPU_FB_MAX_COLOR_ATTACHMENT; attachment++) { if (mtl_color_attachments_[attachment].used) { used_attachments++; } } used_attachments += (mtl_depth_attachment_.used) ? 1 : 0; used_attachments += (mtl_stencil_attachment_.used) ? 1 : 0; return (used_attachments > 0); } MTLLoadAction mtl_load_action_from_gpu(eGPULoadOp action) { return (action == GPU_LOADACTION_LOAD) ? MTLLoadActionLoad : ((action == GPU_LOADACTION_CLEAR) ? MTLLoadActionClear : MTLLoadActionDontCare); } MTLStoreAction mtl_store_action_from_gpu(eGPUStoreOp action) { return (action == GPU_STOREACTION_STORE) ? MTLStoreActionStore : MTLStoreActionDontCare; } MTLRenderPassDescriptor *MTLFrameBuffer::bake_render_pass_descriptor(bool load_contents) { BLI_assert(this); if (load_contents) { /* Only force-load contents if there is no clear pending. */ BLI_assert(!has_pending_clear_); } /* Ensure we are inside a frame boundary. */ MTLContext *metal_ctx = static_cast(unwrap(GPU_context_active_get())); BLI_assert(metal_ctx && metal_ctx->get_inside_frame()); UNUSED_VARS_NDEBUG(metal_ctx); /* If Frame-buffer has been modified, regenerate descriptor. */ if (is_dirty_) { /* Clear all configs. */ for (int config = 0; config < 3; config++) { descriptor_dirty_[config] = true; } } else if (is_loadstore_dirty_) { /* Load config always has load ops, so we only need to re-generate custom and clear state. */ descriptor_dirty_[MTL_FB_CONFIG_CLEAR] = true; descriptor_dirty_[MTL_FB_CONFIG_CUSTOM] = true; } /* If we need to populate descriptor" */ /* Select config based on FrameBuffer state: * [0] {MTL_FB_CONFIG_CLEAR} = Clear config -- we have a pending clear so should perform our * configured clear. * [1] {MTL_FB_CONFIG_LOAD} = Load config -- We need to re-load ALL attachments, * used for re-binding/pass-breaks. * [2] {MTL_FB_CONFIG_CUSTOM} = Custom config -- Use this when a custom binding config is * specified. */ uint descriptor_config = (load_contents) ? MTL_FB_CONFIG_LOAD : ((this->get_pending_clear()) ? MTL_FB_CONFIG_CLEAR : MTL_FB_CONFIG_CUSTOM); if (descriptor_dirty_[descriptor_config] || framebuffer_descriptor_[descriptor_config] == nil) { /* Create descriptor if it does not exist. */ if (framebuffer_descriptor_[descriptor_config] == nil) { framebuffer_descriptor_[descriptor_config] = [[MTLRenderPassDescriptor alloc] init]; } #if defined(MAC_OS_X_VERSION_11_0) && __MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_11_0 if (@available(macOS 11.00, *)) { /* Optimization: Use smaller tile size on Apple Silicon if exceeding a certain bpp limit. */ bool is_tile_based_gpu = [metal_ctx->device hasUnifiedMemory]; if (is_tile_based_gpu) { uint framebuffer_bpp = this->get_bits_per_pixel(); bool use_small_tiles = (framebuffer_bpp > 64); if (use_small_tiles) { framebuffer_descriptor_[descriptor_config].tileWidth = 16; framebuffer_descriptor_[descriptor_config].tileHeight = 16; } } } #endif /* Configure multilayered rendering. */ if (use_multilayered_rendering_) { /* Ensure all targets have the same length. */ int len = 0; bool valid = true; for (int attachment_ind = 0; attachment_ind < GPU_FB_MAX_COLOR_ATTACHMENT; attachment_ind++) { if (mtl_color_attachments_[attachment_ind].used) { if (len == 0) { len = mtl_color_attachments_[attachment_ind].render_target_array_length; } else { valid = valid && (len == mtl_color_attachments_[attachment_ind].render_target_array_length); } } } if (mtl_depth_attachment_.used) { if (len == 0) { len = mtl_depth_attachment_.render_target_array_length; } else { valid = valid && (len == mtl_depth_attachment_.render_target_array_length); } } if (mtl_stencil_attachment_.used) { if (len == 0) { len = mtl_stencil_attachment_.render_target_array_length; } else { valid = valid && (len == mtl_stencil_attachment_.render_target_array_length); } } BLI_assert(len > 0); BLI_assert(valid); framebuffer_descriptor_[descriptor_config].renderTargetArrayLength = len; } else { framebuffer_descriptor_[descriptor_config].renderTargetArrayLength = 0; } /* Color attachments. */ int colour_attachments = 0; for (int attachment_ind = 0; attachment_ind < GPU_FB_MAX_COLOR_ATTACHMENT; attachment_ind++) { if (mtl_color_attachments_[attachment_ind].used) { /* Create attachment descriptor. */ MTLRenderPassColorAttachmentDescriptor *attachment = colour_attachment_descriptors_[attachment_ind]; BLI_assert(attachment != nil); id texture = mtl_color_attachments_[attachment_ind].texture->get_metal_handle_base(); if (texture == nil) { MTL_LOG_ERROR("Attempting to assign invalid texture as attachment\n"); } /* IF SRGB is enabled, but we are rendering with SRGB disabled, sample texture view. */ /* TODO(Metal): Consider caching SRGB texture view. */ id source_color_texture = texture; if (this->get_is_srgb() && !this->get_srgb_enabled()) { source_color_texture = [texture newTextureViewWithPixelFormat:MTLPixelFormatRGBA8Unorm]; } /* Resolve appropriate load action -- IF force load, perform load. * If clear but framebuffer has no pending clear, also load. */ eGPULoadOp load_action = mtl_color_attachments_[attachment_ind].load_action; if (descriptor_config == MTL_FB_CONFIG_LOAD) { /* MTL_FB_CONFIG_LOAD must always load. */ load_action = GPU_LOADACTION_LOAD; } else if (descriptor_config == MTL_FB_CONFIG_CUSTOM && load_action == GPU_LOADACTION_CLEAR) { /* Custom config should be LOAD or DONT_CARE only. */ load_action = GPU_LOADACTION_LOAD; } attachment.texture = source_color_texture; attachment.loadAction = mtl_load_action_from_gpu(load_action); attachment.clearColor = (load_action == GPU_LOADACTION_CLEAR) ? MTLClearColorMake(mtl_color_attachments_[attachment_ind].clear_value.color[0], mtl_color_attachments_[attachment_ind].clear_value.color[1], mtl_color_attachments_[attachment_ind].clear_value.color[2], mtl_color_attachments_[attachment_ind].clear_value.color[3]) : MTLClearColorMake(0.0, 0.0, 0.0, 0.0); attachment.storeAction = mtl_store_action_from_gpu( mtl_color_attachments_[attachment_ind].store_action); attachment.level = mtl_color_attachments_[attachment_ind].mip; attachment.slice = mtl_color_attachments_[attachment_ind].slice; attachment.depthPlane = mtl_color_attachments_[attachment_ind].depth_plane; colour_attachments++; /* Copy attachment info back in. */ [framebuffer_descriptor_[descriptor_config].colorAttachments setObject:attachment atIndexedSubscript:attachment_ind]; } else { /* Disable colour attachment. */ [framebuffer_descriptor_[descriptor_config].colorAttachments setObject:nil atIndexedSubscript:attachment_ind]; } } BLI_assert(colour_attachments == colour_attachment_count_); /* Depth attachment. */ if (mtl_depth_attachment_.used) { framebuffer_descriptor_[descriptor_config].depthAttachment.texture = (id)mtl_depth_attachment_.texture->get_metal_handle_base(); /* Resolve appropriate load action -- IF force load, perform load. * If clear but framebuffer has no pending clear, also load. */ eGPULoadOp load_action = mtl_depth_attachment_.load_action; if (descriptor_config == MTL_FB_CONFIG_LOAD) { /* MTL_FB_CONFIG_LOAD must always load. */ load_action = GPU_LOADACTION_LOAD; } else if (descriptor_config == MTL_FB_CONFIG_CUSTOM && load_action == GPU_LOADACTION_CLEAR) { /* Custom config should be LOAD or DONT_CARE only. */ load_action = GPU_LOADACTION_LOAD; } framebuffer_descriptor_[descriptor_config].depthAttachment.loadAction = mtl_load_action_from_gpu(load_action); framebuffer_descriptor_[descriptor_config].depthAttachment.clearDepth = (load_action == GPU_LOADACTION_CLEAR) ? mtl_depth_attachment_.clear_value.depth : 0; framebuffer_descriptor_[descriptor_config].depthAttachment.storeAction = mtl_store_action_from_gpu(mtl_depth_attachment_.store_action); framebuffer_descriptor_[descriptor_config].depthAttachment.level = mtl_depth_attachment_.mip; framebuffer_descriptor_[descriptor_config].depthAttachment.slice = mtl_depth_attachment_.slice; framebuffer_descriptor_[descriptor_config].depthAttachment.depthPlane = mtl_depth_attachment_.depth_plane; } else { framebuffer_descriptor_[descriptor_config].depthAttachment.texture = nil; } /* Stencil attachment. */ if (mtl_stencil_attachment_.used) { framebuffer_descriptor_[descriptor_config].stencilAttachment.texture = (id)mtl_stencil_attachment_.texture->get_metal_handle_base(); /* Resolve appropriate load action -- IF force load, perform load. * If clear but framebuffer has no pending clear, also load. */ eGPULoadOp load_action = mtl_stencil_attachment_.load_action; if (descriptor_config == MTL_FB_CONFIG_LOAD) { /* MTL_FB_CONFIG_LOAD must always load. */ load_action = GPU_LOADACTION_LOAD; } else if (descriptor_config == MTL_FB_CONFIG_CUSTOM && load_action == GPU_LOADACTION_CLEAR) { /* Custom config should be LOAD or DONT_CARE only. */ load_action = GPU_LOADACTION_LOAD; } framebuffer_descriptor_[descriptor_config].stencilAttachment.loadAction = mtl_load_action_from_gpu(load_action); framebuffer_descriptor_[descriptor_config].stencilAttachment.clearStencil = (load_action == GPU_LOADACTION_CLEAR) ? mtl_stencil_attachment_.clear_value.stencil : 0; framebuffer_descriptor_[descriptor_config].stencilAttachment.storeAction = mtl_store_action_from_gpu(mtl_stencil_attachment_.store_action); framebuffer_descriptor_[descriptor_config].stencilAttachment.level = mtl_stencil_attachment_.mip; framebuffer_descriptor_[descriptor_config].stencilAttachment.slice = mtl_stencil_attachment_.slice; framebuffer_descriptor_[descriptor_config].stencilAttachment.depthPlane = mtl_stencil_attachment_.depth_plane; } else { framebuffer_descriptor_[descriptor_config].stencilAttachment.texture = nil; } descriptor_dirty_[descriptor_config] = false; } is_dirty_ = false; is_loadstore_dirty_ = false; return framebuffer_descriptor_[descriptor_config]; } /** \} */ /* -------------------------------------------------------------------- */ /** \ Blitting * \{ */ void MTLFrameBuffer::blit(uint read_slot, uint src_x_offset, uint src_y_offset, MTLFrameBuffer *metal_fb_write, uint write_slot, uint dst_x_offset, uint dst_y_offset, uint width, uint height, eGPUFrameBufferBits blit_buffers) { BLI_assert(this); BLI_assert(metal_fb_write); if (!(this && metal_fb_write)) { return; } MTLContext *mtl_context = reinterpret_cast(GPU_context_active_get()); const bool do_color = (blit_buffers & GPU_COLOR_BIT); const bool do_depth = (blit_buffers & GPU_DEPTH_BIT); const bool do_stencil = (blit_buffers & GPU_STENCIL_BIT); /* Early exit if there is no blit to do. */ if (!(do_color || do_depth || do_stencil)) { MTL_LOG_WARNING( " MTLFrameBuffer: requested blit but no color, depth or stencil flag was set\n"); return; } id blit_encoder = nil; /* If the color format is not the same, we cannot use the BlitCommandEncoder, and instead use * a Graphics-based blit. */ if (do_color && (this->get_color_attachment(read_slot).texture->format_get() != metal_fb_write->get_color_attachment(read_slot).texture->format_get())) { MTLAttachment src_attachment = this->get_color_attachment(read_slot); MTLAttachment dst_attachment = metal_fb_write->get_color_attachment(write_slot); assert(src_attachment.slice == 0 && "currently only supporting slice 0 for graphics framebuffer blit"); src_attachment.texture->blit(dst_attachment.texture, src_x_offset, src_y_offset, dst_x_offset, dst_y_offset, src_attachment.mip, dst_attachment.mip, dst_attachment.slice, width, height); } else { /* Setup blit encoder. */ blit_encoder = mtl_context->main_command_buffer.ensure_begin_blit_encoder(); if (do_color) { MTLAttachment src_attachment = this->get_color_attachment(read_slot); MTLAttachment dst_attachment = metal_fb_write->get_color_attachment(write_slot); if (src_attachment.used && dst_attachment.used) { /* TODO(Metal): Support depth(z) offset in blit if needed. */ src_attachment.texture->blit(blit_encoder, src_x_offset, src_y_offset, 0, src_attachment.slice, src_attachment.mip, dst_attachment.texture, dst_x_offset, dst_y_offset, 0, dst_attachment.slice, dst_attachment.mip, width, height, 1); } else { MTL_LOG_ERROR("Failed performing colour blit\n"); } } } if ((do_depth || do_stencil) && blit_encoder == nil) { blit_encoder = mtl_context->main_command_buffer.ensure_begin_blit_encoder(); } if (do_depth) { MTLAttachment src_attachment = this->get_depth_attachment(); MTLAttachment dst_attachment = metal_fb_write->get_depth_attachment(); if (src_attachment.used && dst_attachment.used) { /* TODO(Metal): Support depth(z) offset in blit if needed. */ src_attachment.texture->blit(blit_encoder, src_x_offset, src_y_offset, 0, src_attachment.slice, src_attachment.mip, dst_attachment.texture, dst_x_offset, dst_y_offset, 0, dst_attachment.slice, dst_attachment.mip, width, height, 1); } else { MTL_LOG_ERROR("Failed performing depth blit\n"); } } /* Stencil attachment blit. */ if (do_stencil) { MTLAttachment src_attachment = this->get_stencil_attachment(); MTLAttachment dst_attachment = metal_fb_write->get_stencil_attachment(); if (src_attachment.used && dst_attachment.used) { /* TODO(Metal): Support depth(z) offset in blit if needed. */ src_attachment.texture->blit(blit_encoder, src_x_offset, src_y_offset, 0, src_attachment.slice, src_attachment.mip, dst_attachment.texture, dst_x_offset, dst_y_offset, 0, dst_attachment.slice, dst_attachment.mip, width, height, 1); } else { MTL_LOG_ERROR("Failed performing Stencil blit\n"); } } } int MTLFrameBuffer::get_width() { return width_; } int MTLFrameBuffer::get_height() { return height_; } } // blender::gpu