
The memory manager includes both a GPUContext-local manager which allocates per-context resources such as Circular Scratch Buffers for temporary data such as uniform updates and resource staging, and a GPUContext-global memory manager which features a pooled memory allocator for efficient re-use of resources, to reduce CPU-overhead of frequent memory allocations. These Memory Managers act as a simple interface for use by other Metal backend modules and to coordinate the lifetime of buffers, to ensure that GPU-resident resources are correctly tracked and freed when no longer in use. Note: This also contains dependent DIFF changes from D15027, though these will be removed once D15027 lands. Authored by Apple: Michael Parkin-White Ref T96261 Reviewed By: fclem Maniphest Tasks: T96261 Differential Revision: https://developer.blender.org/D15277
1898 lines
64 KiB
C++
1898 lines
64 KiB
C++
/** \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
|