From e13a260b8ca8b240efb5e70cc24045390740b477 Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Fri, 6 Nov 2026 14:53:59 +0100 Subject: [PATCH] Metal: framebuffer fetch again, through rslc's subpass input The vendored rslc reads a subpassInput and prints it to MSL as the colour attachment's [[color(N)]] value, so the pixel header defines INPUT_ATTACHMENT_BINDING and FB_FETCH_VALUE again and the backend reports framebuffer fetch on Apple GPUs and on the Intel GPUs whose probe draws a pixel that reads back doubled. --- .../libretro-common/gfx/shaders/rslc_emit.c | 42 +++++- .../libretro-common/gfx/shaders/rslc_front.c | 31 ++++- .../libretro-common/gfx/shaders/rslc_ir.c | 41 ++++++ .../libretro-common/gfx/shaders/rslc_share.h | 16 ++- Source/Core/VideoBackends/Metal/MTLUtil.mm | 121 +++++++++++++++++- Source/Core/VideoCommon/PixelShaderGen.cpp | 3 +- Source/Core/VideoCommon/UberShaderPixel.cpp | 3 +- 7 files changed, 240 insertions(+), 17 deletions(-) diff --git a/Externals/libretro-common/gfx/shaders/rslc_emit.c b/Externals/libretro-common/gfx/shaders/rslc_emit.c index f9f55e9a5d..f1e8b18c32 100644 --- a/Externals/libretro-common/gfx/shaders/rslc_emit.c +++ b/Externals/libretro-common/gfx/shaders/rslc_emit.c @@ -341,6 +341,11 @@ static void pr_type(struct rslc_pr *p, const struct rslc_type *t) pr_s(p, t->kind == RSLC_TY_SAMPLER2DARRAY ? "sampler2DArray" : t->kind == RSLC_TY_SAMPLER2DMSARRAY ? "sampler2DMSArray" : "samplerBuffer"); break; + case RSLC_TY_SUBPASSINPUT: + /* MSL alone prints one: the attachment's value, read as a + * fragment input */ + pr_s(p, "float4"); + break; case RSLC_TY_IMAGE2D: case RSLC_TY_IMAGE2DARRAY: if (p->hlsl) @@ -436,8 +441,10 @@ static const char *pr_block_instance(struct rslc_pr *p, unsigned block) { const struct rslc_blockdecl *b = &p->c->prog.blocks[block]; const char *forced = NULL; - /* the frontend's names are for its two blocks; further ones keep theirs */ - if (p->names && block < 2) + /* the frontend's names are for its two blocks; further ones keep + * theirs. Without RSLC_EXTENDED every block is one of those two, + * so the index test folds away. */ + if (p->names && (RSLC_MAX_BLOCKS == 2 || block < 2)) forced = b->push ? p->names->push_instance : p->names->ubo_instance; if (forced) return forced; @@ -1903,6 +1910,9 @@ static void pr_msl_builtin(struct rslc_pr *p, const struct rslc_node *n) } return; } + case RSLC_BFN_SUBPASSLOAD: + pr_expr(p, rslc_list_item(p->c, list, 0), PR_PREC_POSTFIX); + return; case RSLC_BFN_IMAGESIZE: { const struct rslc_type *it = pr_arg_type(p, list, 0); @@ -2481,7 +2491,7 @@ static void pr_msl_env(struct rslc_pr *p, const struct rslc_func *f, bool decl, continue; } #ifdef RSLC_EXTENDED - if (v->storage == RSLC_ST_IMAGE) + if (v->storage == RSLC_ST_IMAGE || v->storage == RSLC_ST_SUBPASS) { if (decl) { @@ -2618,7 +2628,7 @@ static void pr_msl_env_mark(struct rslc_pr *p, uint8_t *env, uint32_t node, bool case RSLC_ST_IN: case RSLC_ST_OUT: case RSLC_ST_BUILTIN: case RSLC_ST_GLOBAL: case RSLC_ST_SAMPLER: #ifdef RSLC_EXTENDED - case RSLC_ST_IMAGE: case RSLC_ST_SHARED: + case RSLC_ST_IMAGE: case RSLC_ST_SHARED: case RSLC_ST_SUBPASS: #endif slot = n->a; break; @@ -6356,6 +6366,21 @@ static bool pr_msl(struct rslc_pr *p, struct rslc_result *result) pr_u(p, pr_msl_index(v->binding)); pr_s(p, ")]]"); } + /* an input attachment is the colour attachment's value on entry */ + for (i = 1; i < pr->nvars; i++) + { + const struct rslc_var *v = pr->vars[i]; + if (v->storage != RSLC_ST_SUBPASS || !menv[i]) + continue; + if (!first) + pr_s(p, ", "); + first = false; + pr_s(p, "float4 "); + pr_var_name(p, v); + pr_s(p, " [[color("); + pr_u(p, v->member); + pr_s(p, ")]]"); + } #endif for (i = 1; i < pr->nvars; i++) { @@ -6636,6 +6661,15 @@ bool rslc_emit_text(struct rslc_ctx *c, struct rslc_result *result) rslc_err(c, p->hlsl ? "hlsl: no geometry stage yet" : "msl: no geometry stage", NULL, 0); return false; } + /* an input attachment has a form in MSL alone */ + if (!p->msl) + for (k = 1; k < pr->nvars; k++) + if (pr->vars[k]->storage == RSLC_ST_SUBPASS) + { + rslc_err(c, p->hlsl ? "hlsl: no form of subpassInput" : "glsl: no form of subpassInput", + NULL, pr->vars[k]->line); + return false; + } if (p->hlsl && c->stage == RSLC_STAGE_COMPUTE) { uint32_t k; diff --git a/Externals/libretro-common/gfx/shaders/rslc_front.c b/Externals/libretro-common/gfx/shaders/rslc_front.c index 39ff26e38e..3f1df12745 100644 --- a/Externals/libretro-common/gfx/shaders/rslc_front.c +++ b/Externals/libretro-common/gfx/shaders/rslc_front.c @@ -1698,6 +1698,7 @@ struct rslc_quals bool early_fragment_tests; uint8_t gs_prim; /* RSLC_GS_* named in the layout */ uint32_t index; + uint32_t input_att; /* input_attachment_index; RSLC_UNSET if absent */ uint32_t gs_invocations; uint32_t gs_max_vertices; #endif @@ -2958,6 +2959,7 @@ static bool rslc_p_eval(struct rslc_parser *p, uint32_t e, struct rslc_cval *out * A N Z sampler2DArray, sampler2DMSArray, samplerBuffer * J K image2D, image2DArray; g the gvec4 of the image's base, * matched exactly + * P subpassInput * (RSLC_EXTENDED; their overloads are absent otherwise) * M any matrix; Q square matrix; T transposed M result * X any scalar or vector, same type as other X @@ -3075,7 +3077,8 @@ static const struct rslc_bsig rslc_bsigs[] = { { RSLC_BFN_GROUPMEMORYBARRIER, "groupMemoryBarrier", ":v" }, { RSLC_BFN_IMAGESTORE, "imageStore", "J i2 g:v;K i3 g:v" }, { RSLC_BFN_IMAGELOAD, "imageLoad", "J i2:4;K i3:4" }, - { RSLC_BFN_IMAGESIZE, "imageSize", "J:i2;K:i3" } + { RSLC_BFN_IMAGESIZE, "imageSize", "J:i2;K:i3" }, + { RSLC_BFN_SUBPASSLOAD, "subpassLoad", "P:4" } #endif }; @@ -3165,6 +3168,8 @@ static bool rslc_bsig_match_tok(struct rslc_parser *p, const char *tok, unsigned return true; } #ifdef RSLC_EXTENDED + else if (len == 1 && tok[0] == 'P') + return t->kind == RSLC_TY_SUBPASSINPUT; else if (len == 1 && (tok[0] == 'J' || tok[0] == 'K')) { if (t->kind != (tok[0] == 'J' ? RSLC_TY_IMAGE2D : RSLC_TY_IMAGE2DARRAY)) @@ -3261,7 +3266,7 @@ static const struct rslc_type *rslc_bsig_param_type(struct rslc_parser *p, if (len == 1 && (tok[0] == 'S' || tok[0] == 'A' || tok[0] == 'N' || tok[0] == 'Z')) return actual; #ifdef RSLC_EXTENDED - if (len == 1 && (tok[0] == 'J' || tok[0] == 'K')) + if (len == 1 && (tok[0] == 'J' || tok[0] == 'K' || tok[0] == 'P')) return actual; if (len == 1 && tok[0] == 'g') return actual; /* matched exactly */ @@ -4525,6 +4530,7 @@ static bool rslc_p_layout(struct rslc_parser *p, struct rslc_quals *q) else if (len == 12 && !memcmp(s, "local_size_x", 12)) slot = &q->cs_local[0]; else if (len == 12 && !memcmp(s, "local_size_y", 12)) slot = &q->cs_local[1]; else if (len == 12 && !memcmp(s, "local_size_z", 12)) slot = &q->cs_local[2]; + else if (len == 22 && !memcmp(s, "input_attachment_index", 22)) slot = &q->input_att; else if (rslc_p_image_format(s, len, &q->img_fmt)) { } #endif else if (len == 7 && !memcmp(s, "index", 5)) { } @@ -4554,6 +4560,9 @@ static bool rslc_p_quals(struct rslc_parser *p, struct rslc_quals *q) { memset(q, 0, sizeof(*q)); q->location = q->binding = q->set = q->offset = RSLC_UNSET; +#ifdef RSLC_EXTENDED + q->input_att = RSLC_UNSET; +#endif for (;;) { const struct rslc_tok *t = P_TOK(p); @@ -4657,6 +4666,8 @@ static const struct rslc_type *rslc_p_type_spec(struct rslc_parser *p, ty = rslc_type_sampler2d(p->c, RSLC_BASE_INT); else if (k == RSLC_KW_USAMPLER2D) ty = rslc_type_sampler2d(p->c, RSLC_BASE_UINT); + else if (k == RSLC_KW_SUBPASSINPUT) + ty = rslc_type_basic(p->c, RSLC_TY_SUBPASSINPUT, RSLC_BASE_FLOAT, 1, 1); else if (k >= RSLC_KW_IMAGE2D && k <= RSLC_KW_UIMAGE2DARRAY) { /* the format is the declaration's layout; it is applied there */ @@ -5251,6 +5262,11 @@ static bool rslc_p_params(struct rslc_parser *p, struct rslc_func *f) ty = rslc_p_array_suffix(p, ty); if (!ty) return false; +#ifdef RSLC_EXTENDED + /* the printers hand it over as the value it reads */ + if (ty->kind == RSLC_TY_SUBPASSINPUT) + return rslc_p_fail(p, "a subpassInput cannot be a parameter", NULL); +#endif v = rslc_p_new_var(p, name, ty, line); if (!v) return false; @@ -5930,6 +5946,17 @@ static uint32_t rslc_p_declaration(struct rslc_parser *p, bool global) if (q.writeonly) v->quals |= RSLC_VQ_WRITEONLY; } + else if (vt->kind == RSLC_TY_SUBPASSINPUT) + { + /* an input attachment: the fragment stage's framebuffer read */ + if (!global || !q.is_uniform || p->c->stage != RSLC_STAGE_FRAGMENT) + return rslc_p_fail(p, "a subpassInput must be a fragment stage\'s global uniform", NULL), 0; + if (q.input_att == RSLC_UNSET) + return rslc_p_fail(p, "a subpassInput needs its input_attachment_index", NULL), 0; + v->storage = RSLC_ST_SUBPASS; + v->member = q.input_att; + v->precision = 0; + } else if (q.is_shared) { if (!global || p->c->stage != RSLC_STAGE_COMPUTE) diff --git a/Externals/libretro-common/gfx/shaders/rslc_ir.c b/Externals/libretro-common/gfx/shaders/rslc_ir.c index a8cce34ac1..72797d69af 100644 --- a/Externals/libretro-common/gfx/shaders/rslc_ir.c +++ b/Externals/libretro-common/gfx/shaders/rslc_ir.c @@ -417,6 +417,7 @@ static const char *const rslc_keywords[] = { "sampler2DMSArray", "isampler2DMSArray", "usampler2DMSArray", "samplerBuffer", "isamplerBuffer", "usamplerBuffer", "image2D", "iimage2D", "uimage2D", "image2DArray", "iimage2DArray", "uimage2DArray", + "subpassInput", "buffer", "readonly", "writeonly", "coherent", "restrict", "volatile", "shared" #endif }; @@ -999,6 +1000,7 @@ enum spv_section #define SPV_CAP_CLIPDISTANCE 32 #define SPV_CAP_CULLDISTANCE 33 #define SPV_CAP_GEOMETRY 2 +#define SPV_CAP_INPUTATTACHMENT 40 #define SPV_MODEL_VERTEX 0 #define SPV_MODEL_FRAGMENT 4 #define SPV_MODEL_GEOMETRY 3 @@ -1039,6 +1041,7 @@ enum spv_section #define SPV_DEC_INDEX 32 #define SPV_DEC_BINDING 33 #define SPV_DEC_DESCRIPTORSET 34 +#define SPV_DEC_INPUTATTACHMENTINDEX 43 #define SPV_DEC_OFFSET 35 #define SPV_BI_POSITION 0 @@ -1207,6 +1210,7 @@ struct spv_emit uint32_t cur_label; /* current block's label, 0 = terminated */ uint32_t entry_label; bool need_imagequery; + bool need_inputattachment; bool need_sampledbuffer; bool need_samplerateshading; bool need_clipdistance; @@ -1577,6 +1581,20 @@ static uint32_t spv_type(struct spv_emit *e, const struct rslc_type *t) ti->img = id; break; } + case RSLC_TY_SUBPASSINPUT: + { + /* Dim SubpassData, Sampled 2, format Unknown */ + uint32_t s = spv_type(e, rslc_type_basic(e->c, RSLC_TY_SCALAR, RSLC_BASE_FLOAT, 1, 1)); + if (!s) + return 0; + ti = &e->tinfo[t->id]; + w[0] = id; w[1] = s; w[2] = 6; w[3] = 0; w[4] = 0; w[5] = 0; w[6] = 2; w[7] = 0; + if (!spv_inst(e, SPV_SEC_TYPES, SpvOpTypeImage, w, 8)) + return 0; + ti->img = id; + e->need_inputattachment = true; + break; + } #endif case RSLC_TY_ARRAY: { @@ -3260,6 +3278,16 @@ static uint32_t spv_builtin_node(struct spv_emit *e, const struct rslc_node *n) case RSLC_BFN_IMAGESIZE: e->need_imagequery = true; return spv_r1(e, SpvOpImageQuerySize, tid, args[0], prec); + case RSLC_BFN_SUBPASSLOAD: + { + /* the fragment's own texel: coordinate (0, 0) */ + static const uint32_t zero[2] = { 0, 0 }; + uint32_t used, coord = spv_const_composite(e, + rslc_type_basic(e->c, RSLC_TY_VEC, RSLC_BASE_INT, 2, 1), zero, &used); + if (!coord) + return 0; + return spv_r2(e, SpvOpImageRead, tid, args[0], coord, prec); + } case RSLC_BFN_EMITVERTEX: case RSLC_BFN_ENDPRIMITIVE: /* no result: the void type's id stands for the expression */ @@ -4468,6 +4496,7 @@ static bool spv_global_var(struct spv_emit *e, const struct rslc_var *v) case RSLC_ST_GLOBAL: sc = SPV_SC_PRIVATE; break; #ifdef RSLC_EXTENDED case RSLC_ST_IMAGE: sc = SPV_SC_UNIFORMCONSTANT; break; + case RSLC_ST_SUBPASS: sc = SPV_SC_UNIFORMCONSTANT; break; case RSLC_ST_SHARED: sc = SPV_SC_WORKGROUP; break; #endif case RSLC_ST_CONST: @@ -4511,6 +4540,13 @@ static bool spv_global_var(struct spv_emit *e, const struct rslc_var *v) if ((v->quals & RSLC_VQ_READONLY) && !spv_decorate(e, id, SPV_DEC_NONWRITABLE)) return false; } + if (v->storage == RSLC_ST_SUBPASS) + { + if (!spv_decorate1(e, id, SPV_DEC_DESCRIPTORSET, v->set == RSLC_UNSET ? 0 : v->set) + || !spv_decorate1(e, id, SPV_DEC_BINDING, v->binding == RSLC_UNSET ? 0 : v->binding) + || !spv_decorate1(e, id, SPV_DEC_INPUTATTACHMENTINDEX, v->member)) + return false; + } #endif if (v->storage == RSLC_ST_SAMPLER) { @@ -5003,6 +5039,11 @@ static bool rslc_emit_spirv(struct rslc_ctx *c, struct rslc_result *result) return false; if (e->need_imagequery && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_IMAGEQUERY)) return false; +#ifdef RSLC_EXTENDED + if (e->need_inputattachment + && !spv_inst1(e, SPV_SEC_CAPS, SpvOpCapability, SPV_CAP_INPUTATTACHMENT)) + return false; +#endif if (!spv_inst_str(e, SPV_SEC_CAPS, SpvOpExtInstImport, &e->ext_id, 1, "GLSL.std.450", NULL, 0)) return false; if (!spv_inst2(e, SPV_SEC_CAPS, SpvOpMemoryModel, 0 /* Logical */, 1 /* GLSL450 */)) diff --git a/Externals/libretro-common/gfx/shaders/rslc_share.h b/Externals/libretro-common/gfx/shaders/rslc_share.h index 57dc47f90b..1d9b0953f5 100644 --- a/Externals/libretro-common/gfx/shaders/rslc_share.h +++ b/Externals/libretro-common/gfx/shaders/rslc_share.h @@ -219,6 +219,8 @@ enum rslc_kw /* RSLC_EXTENDED image types: float, int, uint of each */ RSLC_KW_IMAGE2D, RSLC_KW_IIMAGE2D, RSLC_KW_UIMAGE2D, RSLC_KW_IMAGE2DARRAY, RSLC_KW_IIMAGE2DARRAY, RSLC_KW_UIMAGE2DARRAY, + /* RSLC_EXTENDED: a fragment stage's input attachment */ + RSLC_KW_SUBPASSINPUT, /* RSLC_EXTENDED qualifiers */ RSLC_KW_BUFFER, RSLC_KW_READONLY, RSLC_KW_WRITEONLY, RSLC_KW_COHERENT, RSLC_KW_RESTRICT, RSLC_KW_VOLATILE, RSLC_KW_SHARED, @@ -249,7 +251,9 @@ enum rslc_tkind RSLC_TY_SAMPLERBUFFER, /* RSLC_EXTENDED storage images; cols is the RSLC_IMGFMT_* */ RSLC_TY_IMAGE2D, - RSLC_TY_IMAGE2DARRAY + RSLC_TY_IMAGE2DARRAY, + /* RSLC_EXTENDED: a float input attachment */ + RSLC_TY_SUBPASSINPUT }; #define RSLC_TY_IS_IMAGE(k) ((k) == RSLC_TY_IMAGE2D || (k) == RSLC_TY_IMAGE2DARRAY) @@ -274,7 +278,7 @@ enum rslc_tkind #define RSLC_IMGFMT_R32UI 33 /* The last keyword that names a type. */ -#define RSLC_KW_LAST_TYPE RSLC_KW_UIMAGE2DARRAY +#define RSLC_KW_LAST_TYPE RSLC_KW_SUBPASSINPUT /* Every sampler kind: sampler2D, and the RSLC_EXTENDED ones. */ #define RSLC_TY_IS_SAMPLER(k) ((k) == RSLC_TY_SAMPLER2D \ @@ -326,7 +330,9 @@ enum rslc_storage RSLC_ST_MEMBER, /* a member of an unnamed block, visible bare */ RSLC_ST_BUILTIN, /* gl_Position and friends */ RSLC_ST_SHARED, /* RSLC_EXTENDED: compute workgroup memory */ - RSLC_ST_IMAGE /* RSLC_EXTENDED: uniform image2D and the like */ + RSLC_ST_IMAGE, /* RSLC_EXTENDED: uniform image2D and the like */ + RSLC_ST_SUBPASS /* RSLC_EXTENDED: uniform subpassInput; member is + * its input_attachment_index */ }; #define RSLC_DIR_IN 0 @@ -364,7 +370,8 @@ struct rslc_var uint32_t binding; uint32_t set; uint32_t block; /* RSLC_ST_MEMBER / BLOCK: block index */ - uint32_t member; /* RSLC_ST_MEMBER: field index */ + uint32_t member; /* RSLC_ST_MEMBER: field index; SUBPASS: + * input_attachment_index */ uint8_t storage; /* enum rslc_storage */ uint8_t dir; /* RSLC_DIR_* for params */ uint8_t interp; @@ -583,6 +590,7 @@ enum rslc_bfn RSLC_BFN_BARRIER, RSLC_BFN_MEMORYBARRIER, RSLC_BFN_MEMORYBARRIERSHARED, RSLC_BFN_MEMORYBARRIERBUFFER, RSLC_BFN_MEMORYBARRIERIMAGE, RSLC_BFN_GROUPMEMORYBARRIER, RSLC_BFN_IMAGESTORE, RSLC_BFN_IMAGELOAD, RSLC_BFN_IMAGESIZE, + RSLC_BFN_SUBPASSLOAD, /* RSLC_EXTENDED, fragment */ RSLC_BFN_COUNT }; diff --git a/Source/Core/VideoBackends/Metal/MTLUtil.mm b/Source/Core/VideoBackends/Metal/MTLUtil.mm index 59aa0ec739..6ca23fd80e 100644 --- a/Source/Core/VideoBackends/Metal/MTLUtil.mm +++ b/Source/Core/VideoBackends/Metal/MTLUtil.mm @@ -94,6 +94,115 @@ void Metal::Util::PopulateBackendInfoAdapters(BackendInfo* backend_info, } } +#if TARGET_OS_OSX +/* Some Intel GPUs fetch from the framebuffer though no feature set says + * so, and others build the pipeline but leave the fetch out: draw one + * pixel whose shader doubles what it reads, and check it doubled. */ +static bool DetectIntelGPUFBFetch(id dev) +{ + static const char shader[] = + "vertex float4 fs_triangle(uint vid [[vertex_id]]) {\n" + " return float4(vid & 1 ? 3 : -1, vid & 2 ? 3 : -1, 0, 1);\n" + "}\n" + "fragment float4 fbfetch_test(float4 in [[color(0), raster_order_group(0)]]) {\n" + " return in * 2;\n" + "}\n"; + const u32 px_in = 0x11223344; + u32 px_out = 0; + bool ok = false; + id lib; + id vs; + id fs; + MTLRenderPipelineDescriptor* pdesc; + id pipe = nil; + id buf = nil; + MTLTextureDescriptor* tdesc; + id tex = nil; + id q = nil; + id cmdbuf; + id blit; + MTLRenderPassDescriptor* rpdesc; + id renc; + + lib = [dev newLibraryWithSource:[NSString stringWithUTF8String:shader] options:nil error:nil]; + if (!lib) + return false; + vs = [lib newFunctionWithName:@"fs_triangle"]; + fs = [lib newFunctionWithName:@"fbfetch_test"]; + pdesc = [MTLRenderPipelineDescriptor new]; + [pdesc setVertexFunction:vs]; + [pdesc setFragmentFunction:fs]; + [[pdesc colorAttachments][0] setPixelFormat:MTLPixelFormatRGBA8Unorm]; + if (vs && fs) + pipe = [dev newRenderPipelineStateWithDescriptor:pdesc error:nil]; + if (pipe) + { + buf = [dev newBufferWithLength:4 options:MTLResourceStorageModeShared]; + memcpy([buf contents], &px_in, sizeof(px_in)); + tdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm + width:1 + height:1 + mipmapped:false]; + [tdesc setUsage:MTLTextureUsageRenderTarget]; + tex = [dev newTextureWithDescriptor:tdesc]; + q = [dev newCommandQueue]; + cmdbuf = [q commandBuffer]; + + blit = [cmdbuf blitCommandEncoder]; + [blit copyFromBuffer:buf + sourceOffset:0 + sourceBytesPerRow:4 + sourceBytesPerImage:4 + sourceSize:MTLSizeMake(1, 1, 1) + toTexture:tex + destinationSlice:0 + destinationLevel:0 + destinationOrigin:MTLOriginMake(0, 0, 0)]; + [blit endEncoding]; + + rpdesc = [MTLRenderPassDescriptor new]; + [[rpdesc colorAttachments][0] setTexture:tex]; + [[rpdesc colorAttachments][0] setLoadAction:MTLLoadActionLoad]; + [[rpdesc colorAttachments][0] setStoreAction:MTLStoreActionStore]; + renc = [cmdbuf renderCommandEncoderWithDescriptor:rpdesc]; + [renc setRenderPipelineState:pipe]; + [renc drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; + [renc endEncoding]; + [rpdesc release]; + + blit = [cmdbuf blitCommandEncoder]; + [blit copyFromTexture:tex + sourceSlice:0 + sourceLevel:0 + sourceOrigin:MTLOriginMake(0, 0, 0) + sourceSize:MTLSizeMake(1, 1, 1) + toBuffer:buf + destinationOffset:0 + destinationBytesPerRow:4 + destinationBytesPerImage:4]; + [blit endEncoding]; + + [cmdbuf commit]; + [cmdbuf waitUntilCompleted]; + + memcpy(&px_out, [buf contents], sizeof(px_out)); + ok = [cmdbuf status] == MTLCommandBufferStatusCompleted; + } + + [q release]; + [tex release]; + [buf release]; + [pipe release]; + [pdesc release]; + [fs release]; + [vs release]; + [lib release]; + + /* Skylake and later double it; Broadwell leaves it, Haswell blackens it */ + return ok && px_out == 0x22446688; +} +#endif + void Metal::Util::PopulateBackendInfoFeatures(const VideoConfig& config, BackendInfo* backend_info, id device) { @@ -152,9 +261,11 @@ void Metal::Util::PopulateBackendInfoFeatures(const VideoConfig& config, Backend break; } - /* rslc has no form of a subpass input: blending is not emulated - * through framebuffer fetch */ - backend_info->bSupportsFramebufferFetch = false; + backend_info->bSupportsFramebufferFetch = [device supportsFamily:MTLGPUFamilyApple1]; +#if TARGET_OS_OSX + if (vendor == DriverDetails::VENDOR_INTEL) + backend_info->bSupportsFramebufferFetch |= DetectIntelGPUFBFetch(device); +#endif if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DYNAMIC_SAMPLER_INDEXING)) backend_info->bSupportsDynamicSamplerIndexing = false; @@ -268,6 +379,10 @@ static const std::string_view PIXEL_SHADER_HEADER = R"( #define FORCE_EARLY_Z layout(early_fragment_tests) in #define UBO_BINDING(packing, x) layout(packing, binding = ((x) - 1)) #define SSBO_BINDING(x) layout(std430, binding = ((x) + 2)) +#define INPUT_ATTACHMENT_BINDING(x, y, z) layout(set = x, binding = y, input_attachment_index = z) + +// Metal framebuffer fetch helpers. +#define FB_FETCH_VALUE subpassLoad(in_ocol0) // hlsl to glsl function translation #define API_METAL 1 diff --git a/Source/Core/VideoCommon/PixelShaderGen.cpp b/Source/Core/VideoCommon/PixelShaderGen.cpp index 4f4bfff8f1..7401869a7f 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.cpp +++ b/Source/Core/VideoCommon/PixelShaderGen.cpp @@ -831,8 +831,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos if (use_framebuffer_fetch) { - // A subpass input for the framebuffer read; no backend reports framebuffer fetch - // here while rslc has no form of one. + // rslc prints a subpass input as Metal's framebuffer fetch. out.Write("INPUT_ATTACHMENT_BINDING(0, 0, 0) uniform subpassInput in_ocol0;\n"); } } diff --git a/Source/Core/VideoCommon/UberShaderPixel.cpp b/Source/Core/VideoCommon/UberShaderPixel.cpp index b7e7278bb3..d2f49b718b 100644 --- a/Source/Core/VideoCommon/UberShaderPixel.cpp +++ b/Source/Core/VideoCommon/UberShaderPixel.cpp @@ -98,8 +98,7 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, if (use_framebuffer_fetch) { - // A subpass input for the framebuffer read; no backend reports framebuffer fetch - // here while rslc has no form of one. + // rslc prints a subpass input as Metal's framebuffer fetch. out.Write("INPUT_ATTACHMENT_BINDING(0, 0, 0) uniform subpassInput in_ocol0;\n"); } }