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_framebuffer.mm')
-rw-r--r--source/blender/gpu/metal/mtl_framebuffer.mm1899
1 files changed, 1899 insertions, 0 deletions
diff --git a/source/blender/gpu/metal/mtl_framebuffer.mm b/source/blender/gpu/metal/mtl_framebuffer.mm
new file mode 100644
index 00000000000..515dd70e5de
--- /dev/null
+++ b/source/blender/gpu/metal/mtl_framebuffer.mm
@@ -0,0 +1,1899 @@
+/* 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 <Availability.h>
+
+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<MTLContext *>(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<MTLContext *>(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<MTLContext *>(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<MTLContext *>(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<MTLContext *>(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<MTLFrameBuffer *>(dst)->update_attachments(true);
+
+ BLI_assert(planes != 0);
+
+ MTLFrameBuffer *metal_fb_write = static_cast<MTLFrameBuffer *>(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<gpu::MTLTexture *>(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<gpu::MTLTexture *>(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<gpu::MTLTexture *>(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<gpu::MTLTexture *>(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<gpu::MTLTexture *>(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<gpu::MTLTexture *>(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<MTLContext *>(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 Unrecognised texture type %u\n",
+ texture->type_);
+ break;
+ }
+
+ /* Update Framebuffer 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 && "Unrecognised texture type");
+ break;
+ }
+
+ /* Update Framebuffer 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 Framebuffer 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 && "Unrecognised texture type");
+ break;
+ }
+
+ /* Update Framebuffer 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 Framebuffer 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 Framebuffer 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<MTLContext *>(unwrap(GPU_context_active_get()));
+ BLI_assert(metal_ctx && metal_ctx->get_inside_frame());
+ UNUSED_VARS_NDEBUG(metal_ctx);
+
+ /* If Framebuffer 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<MTLTexture> 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<MTLTexture> 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<MTLTexture>)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<MTLTexture>)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<MTLContext *>(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<MTLBlitCommandEncoder> 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