mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 02:09:05 +02:00
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
835 lines
33 KiB
Plaintext
835 lines
33 KiB
Plaintext
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/Profiler/Profiler.h"
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Core/Config.h"
|
|
#include "GPU/GPUState.h"
|
|
#include "GPU/GPUCommon.h"
|
|
#include "GPU/ge_constants.h"
|
|
#include "GPU/Common/SoftwareTransformCommon.h"
|
|
#include "GPU/Common/VertexDecoderCommon.h"
|
|
#include "GPU/Common/FramebufferManagerCommon.h"
|
|
#include "GPU/Metal/DrawEngineMetal.h"
|
|
#include "GPU/Metal/ShaderManagerMetal.h"
|
|
#include "GPU/Metal/TextureCacheMetal.h"
|
|
#include "GPU/Metal/FramebufferManagerMetal.h"
|
|
|
|
static const MTLBlendFactor blendFactorToMetal[(size_t)BlendFactor::COUNT] = {
|
|
MTLBlendFactorZero,
|
|
MTLBlendFactorOne,
|
|
MTLBlendFactorSourceColor,
|
|
MTLBlendFactorOneMinusSourceColor,
|
|
MTLBlendFactorDestinationColor,
|
|
MTLBlendFactorOneMinusDestinationColor,
|
|
MTLBlendFactorSourceAlpha,
|
|
MTLBlendFactorOneMinusSourceAlpha,
|
|
MTLBlendFactorDestinationAlpha,
|
|
MTLBlendFactorOneMinusDestinationAlpha,
|
|
MTLBlendFactorBlendColor,
|
|
MTLBlendFactorOneMinusBlendColor,
|
|
MTLBlendFactorBlendAlpha,
|
|
MTLBlendFactorOneMinusBlendAlpha,
|
|
MTLBlendFactorSource1Color,
|
|
MTLBlendFactorOneMinusSource1Color,
|
|
MTLBlendFactorSource1Alpha,
|
|
MTLBlendFactorOneMinusSource1Alpha,
|
|
MTLBlendFactorZero, // INVALID
|
|
};
|
|
|
|
static const MTLBlendOperation blendEqToMetal[(size_t)BlendEq::COUNT] = {
|
|
MTLBlendOperationAdd,
|
|
MTLBlendOperationSubtract,
|
|
MTLBlendOperationReverseSubtract,
|
|
MTLBlendOperationMin,
|
|
MTLBlendOperationMax,
|
|
};
|
|
|
|
// In GE order.
|
|
static const MTLCompareFunction compareToMetal[8] = {
|
|
MTLCompareFunctionNever,
|
|
MTLCompareFunctionAlways,
|
|
MTLCompareFunctionEqual,
|
|
MTLCompareFunctionNotEqual,
|
|
MTLCompareFunctionLess,
|
|
MTLCompareFunctionLessEqual,
|
|
MTLCompareFunctionGreater,
|
|
MTLCompareFunctionGreaterEqual,
|
|
};
|
|
|
|
static const MTLStencilOperation stencilOpToMetal[8] = {
|
|
MTLStencilOperationKeep,
|
|
MTLStencilOperationZero,
|
|
MTLStencilOperationReplace,
|
|
MTLStencilOperationInvert,
|
|
MTLStencilOperationIncrementClamp,
|
|
MTLStencilOperationDecrementClamp,
|
|
MTLStencilOperationKeep,
|
|
MTLStencilOperationKeep,
|
|
};
|
|
|
|
// Not a valid decoded format ID (those can be 0).
|
|
static constexpr uint32_t SOFTWARE_TRANSFORM_VTXFMT = 0xFFFFFFFF;
|
|
|
|
static MTLVertexFormat DecFmtToMetal(int fmt) {
|
|
switch (fmt) {
|
|
case DEC_FLOAT_1: return MTLVertexFormatFloat;
|
|
case DEC_FLOAT_2: return MTLVertexFormatFloat2;
|
|
case DEC_FLOAT_3: return MTLVertexFormatFloat3;
|
|
case DEC_FLOAT_4: return MTLVertexFormatFloat4;
|
|
case DEC_S8_3: return MTLVertexFormatChar4Normalized;
|
|
case DEC_S16_3: return MTLVertexFormatShort4Normalized;
|
|
case DEC_U8_1:
|
|
case DEC_U8_2:
|
|
case DEC_U8_3:
|
|
case DEC_U8_4: return MTLVertexFormatUChar4Normalized;
|
|
case DEC_U16_1:
|
|
case DEC_U16_2: return MTLVertexFormatUShort2Normalized;
|
|
case DEC_U16_3:
|
|
case DEC_U16_4: return MTLVertexFormatUShort4Normalized;
|
|
default: return MTLVertexFormatInvalid;
|
|
}
|
|
}
|
|
|
|
static void SetAttribute(MTLVertexDescriptor *vd, PspAttributeLocation location, int fmt, int offset) {
|
|
MTLVertexAttributeDescriptor *attr = vd.attributes[(int)location];
|
|
attr.format = DecFmtToMetal(fmt);
|
|
attr.offset = offset;
|
|
attr.bufferIndex = METAL_VERTEX_BUFFER_INDEX;
|
|
}
|
|
|
|
static MTLVertexDescriptor *CreateVertexDescriptor(const DecVtxFormat *decFmt) {
|
|
MTLVertexDescriptor *vd = [MTLVertexDescriptor vertexDescriptor];
|
|
if (decFmt) {
|
|
if (decFmt->w0fmt != 0) SetAttribute(vd, PspAttributeLocation::W1, decFmt->w0fmt, decFmt->w0off);
|
|
if (decFmt->w1fmt != 0) SetAttribute(vd, PspAttributeLocation::W2, decFmt->w1fmt, decFmt->w1off);
|
|
if (decFmt->uvfmt != 0) SetAttribute(vd, PspAttributeLocation::TEXCOORD, decFmt->uvfmt, decFmt->uvoff);
|
|
if (decFmt->c0fmt != 0) SetAttribute(vd, PspAttributeLocation::COLOR0, decFmt->c0fmt, decFmt->c0off);
|
|
if (decFmt->c1fmt != 0) SetAttribute(vd, PspAttributeLocation::COLOR1, decFmt->c1fmt, decFmt->c1off);
|
|
if (decFmt->nrmfmt != 0) SetAttribute(vd, PspAttributeLocation::NORMAL, decFmt->nrmfmt, decFmt->nrmoff);
|
|
SetAttribute(vd, PspAttributeLocation::POSITION, DecVtxFormat::PosFmt(), decFmt->posoff);
|
|
vd.layouts[METAL_VERTEX_BUFFER_INDEX].stride = decFmt->stride;
|
|
} else {
|
|
// Software transformed. Unused attributes don't hurt.
|
|
SetAttribute(vd, PspAttributeLocation::POSITION, DEC_FLOAT_4, offsetof(TransformedVertex, pos));
|
|
SetAttribute(vd, PspAttributeLocation::TEXCOORD, DEC_FLOAT_3, offsetof(TransformedVertex, uv));
|
|
SetAttribute(vd, PspAttributeLocation::COLOR0, DEC_U8_4, offsetof(TransformedVertex, color0_32));
|
|
SetAttribute(vd, PspAttributeLocation::COLOR1, DEC_U8_4, offsetof(TransformedVertex, color1_32));
|
|
SetAttribute(vd, PspAttributeLocation::NORMAL, DEC_FLOAT_1, offsetof(TransformedVertex, fog));
|
|
vd.layouts[METAL_VERTEX_BUFFER_INDEX].stride = sizeof(TransformedVertex);
|
|
}
|
|
vd.layouts[METAL_VERTEX_BUFFER_INDEX].stepFunction = MTLVertexStepFunctionPerVertex;
|
|
return vd;
|
|
}
|
|
|
|
DrawEngineMetal::DrawEngineMetal(Draw::DrawContext *draw) : draw_(draw), pipelines_(256) {
|
|
decOptions_.expand8BitNormalsToFloat = false;
|
|
InitDeviceObjects();
|
|
}
|
|
|
|
DrawEngineMetal::~DrawEngineMetal() {
|
|
ClearPipelines();
|
|
if (draw_) {
|
|
draw_->SetInvalidationCallback(InvalidationCallback());
|
|
}
|
|
}
|
|
|
|
void DrawEngineMetal::InitDeviceObjects() {
|
|
rm_ = (MetalRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
|
id<MTLDevice> device = rm_->Device();
|
|
MTLSamplerDescriptor *sd = [MTLSamplerDescriptor new];
|
|
sd.sAddressMode = MTLSamplerAddressModeClampToEdge;
|
|
sd.tAddressMode = MTLSamplerAddressModeClampToEdge;
|
|
sd.rAddressMode = MTLSamplerAddressModeClampToEdge;
|
|
samplerNearest_ = [device newSamplerStateWithDescriptor:sd];
|
|
sd.minFilter = MTLSamplerMinMagFilterLinear;
|
|
sd.magFilter = MTLSamplerMinMagFilterLinear;
|
|
samplerLinear_ = [device newSamplerStateWithDescriptor:sd];
|
|
nullTexture_ = (__bridge id<MTLTexture>)(void *)draw_->GetNativeObject(Draw::NativeObject::NULL_IMAGEVIEW);
|
|
}
|
|
|
|
void DrawEngineMetal::DeviceLost() {
|
|
ClearPipelines();
|
|
depthStencilCache_.clear();
|
|
vertexDescriptors_.clear();
|
|
if (draw_) {
|
|
draw_->SetInvalidationCallback(InvalidationCallback());
|
|
invalidationCallbackInstalled_ = false;
|
|
}
|
|
boundSecondary_ = nil;
|
|
boundDepal_ = nil;
|
|
draw_ = nullptr;
|
|
rm_ = nullptr;
|
|
}
|
|
|
|
void DrawEngineMetal::DeviceRestore(Draw::DrawContext *draw) {
|
|
draw_ = draw;
|
|
InitDeviceObjects();
|
|
}
|
|
|
|
void DrawEngineMetal::ClearPipelines() {
|
|
pipelines_.Iterate([&](const MetalPipelineKey &, Pipeline *pipeline) {
|
|
if (rm_) {
|
|
rm_->DeferRelease(pipeline->state);
|
|
}
|
|
delete pipeline;
|
|
});
|
|
pipelines_.Clear();
|
|
lastPipeline_ = nullptr;
|
|
}
|
|
|
|
void DrawEngineMetal::BeginFrame() {
|
|
DrawEngineCommon::BeginFrame();
|
|
if (!invalidationCallbackInstalled_) {
|
|
draw_->SetInvalidationCallback(std::bind(&DrawEngineMetal::Invalidate, this, std::placeholders::_1));
|
|
invalidationCallbackInstalled_ = true;
|
|
}
|
|
lastPipeline_ = nullptr;
|
|
boundDepal_ = nil;
|
|
boundSecondary_ = nil;
|
|
baseUBO_ = MetalPushAlloc{};
|
|
lightUBO_ = MetalPushAlloc{};
|
|
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
|
|
AssertEmpty();
|
|
}
|
|
|
|
void DrawEngineMetal::Invalidate(InvalidationCallbackFlags flags) {
|
|
if (flags & InvalidationCallbackFlags::RENDER_PASS_STATE) {
|
|
// The render target might have a different format. The encoder state itself is tracked by the render manager.
|
|
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_BLEND_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
|
|
lastPipeline_ = nullptr;
|
|
}
|
|
}
|
|
|
|
id<MTLDepthStencilState> DrawEngineMetal::GetDepthStencilState(const MetalDepthStencilKey &key) {
|
|
auto iter = depthStencilCache_.find(key.value);
|
|
if (iter != depthStencilCache_.end()) {
|
|
return iter->second;
|
|
}
|
|
MTLDepthStencilDescriptor *dd = [MTLDepthStencilDescriptor new];
|
|
if (key.depthTestEnable) {
|
|
dd.depthCompareFunction = compareToMetal[key.depthCompare];
|
|
dd.depthWriteEnabled = key.depthWriteEnable;
|
|
} else {
|
|
dd.depthCompareFunction = MTLCompareFunctionAlways;
|
|
dd.depthWriteEnabled = NO;
|
|
}
|
|
if (key.stencilTestEnable) {
|
|
MTLStencilDescriptor *sd = [MTLStencilDescriptor new];
|
|
sd.stencilCompareFunction = compareToMetal[key.stencilCompare];
|
|
sd.stencilFailureOperation = stencilOpToMetal[key.stencilFail];
|
|
sd.depthFailureOperation = stencilOpToMetal[key.stencilDepthFail];
|
|
sd.depthStencilPassOperation = stencilOpToMetal[key.stencilPass];
|
|
sd.readMask = (uint32_t)key.compareMask;
|
|
sd.writeMask = (uint32_t)key.writeMask;
|
|
dd.frontFaceStencil = sd;
|
|
dd.backFaceStencil = sd;
|
|
}
|
|
id<MTLDepthStencilState> dss = [rm_->Device() newDepthStencilStateWithDescriptor:dd];
|
|
depthStencilCache_[key.value] = dss;
|
|
return dss;
|
|
}
|
|
|
|
DrawEngineMetal::Pipeline *DrawEngineMetal::GetPipeline(const VShaderID &vsid, const FShaderID &fsid, bool useHWTransform) {
|
|
MetalPipelineKey key{};
|
|
key.vsid = vsid;
|
|
key.fsid = fsid;
|
|
key.vtxFmtId = useHWTransform ? dec_->decFmt.id : SOFTWARE_TRANSFORM_VTXFMT;
|
|
key.blend = blendKey_.value;
|
|
key.targetFormat = rm_->CurTargetFormat().Key();
|
|
return GetOrCreatePipeline(key, useHWTransform ? &dec_->decFmt : nullptr, rm_->CurTargetFormat());
|
|
}
|
|
|
|
DrawEngineMetal::Pipeline *DrawEngineMetal::GetOrCreatePipeline(const MetalPipelineKey &key, const DecVtxFormat *decFmt, const MetalRenderTargetFormat &format) {
|
|
Pipeline *pipeline = pipelines_.GetOrNull(key);
|
|
if (pipeline) {
|
|
return pipeline;
|
|
}
|
|
|
|
MetalVertexShader *vs = shaderManager_->GetVertexShaderFromID(key.vsid);
|
|
MetalFragmentShader *fs = shaderManager_->GetFragmentShaderFromID(key.fsid);
|
|
pipeline = new Pipeline();
|
|
pipeline->fragmentArrayTextureMask = fs->info.arrayTextureMask;
|
|
pipeline->fragmentTextureMask = fs->info.textureMask;
|
|
pipeline->state = [MetalPipelineState new];
|
|
pipeline->format = format;
|
|
pipelines_.Insert(key, pipeline);
|
|
if (vs->Failed() || fs->Failed()) {
|
|
[pipeline->state setPipeline:nil];
|
|
return pipeline;
|
|
}
|
|
|
|
MTLVertexDescriptor *vd;
|
|
auto iter = vertexDescriptors_.find(key.vtxFmtId);
|
|
if (iter != vertexDescriptors_.end()) {
|
|
vd = iter->second;
|
|
} else {
|
|
vd = CreateVertexDescriptor(decFmt);
|
|
vertexDescriptors_[key.vtxFmtId] = vd;
|
|
}
|
|
|
|
MetalBlendKey blend;
|
|
blend.value = key.blend;
|
|
MTLRenderPipelineDescriptor *desc = [MTLRenderPipelineDescriptor new];
|
|
desc.vertexDescriptor = vd;
|
|
desc.rasterSampleCount = format.sampleCount;
|
|
desc.depthAttachmentPixelFormat = format.depth;
|
|
desc.stencilAttachmentPixelFormat = format.stencil;
|
|
if (format.color != MTLPixelFormatInvalid) {
|
|
MTLRenderPipelineColorAttachmentDescriptor *att = desc.colorAttachments[0];
|
|
att.pixelFormat = format.color;
|
|
MTLColorWriteMask mask = MTLColorWriteMaskNone;
|
|
if (blend.colorWriteMask & 1) mask |= MTLColorWriteMaskRed;
|
|
if (blend.colorWriteMask & 2) mask |= MTLColorWriteMaskGreen;
|
|
if (blend.colorWriteMask & 4) mask |= MTLColorWriteMaskBlue;
|
|
if (blend.colorWriteMask & 8) mask |= MTLColorWriteMaskAlpha;
|
|
att.writeMask = mask;
|
|
att.blendingEnabled = blend.blendEnable;
|
|
if (blend.blendEnable) {
|
|
att.rgbBlendOperation = blendEqToMetal[blend.eqColor];
|
|
att.alphaBlendOperation = blendEqToMetal[blend.eqAlpha];
|
|
att.sourceRGBBlendFactor = blendFactorToMetal[blend.srcColor];
|
|
att.destinationRGBBlendFactor = blendFactorToMetal[blend.dstColor];
|
|
att.sourceAlphaBlendFactor = blendFactorToMetal[blend.srcAlpha];
|
|
att.destinationAlphaBlendFactor = blendFactorToMetal[blend.dstAlpha];
|
|
}
|
|
}
|
|
|
|
// The shader functions compile on first use (around 100ms each when not in Metal's cache), and then the
|
|
// pipeline, so all of it goes to a compile thread. Draws record the pipeline state, and the render thread
|
|
// waits for it if it gets there first.
|
|
id<MTLDevice> device = rm_->Device();
|
|
MetalPipelineState *state = pipeline->state;
|
|
MetalShaderFunction *vsFunction = vs->function;
|
|
MetalShaderFunction *fsFunction = fs->function;
|
|
std::string description = vs->desc + " / " + fs->desc;
|
|
RunOnCompileThread([=]() {
|
|
desc.vertexFunction = [vsFunction function];
|
|
desc.fragmentFunction = [fsFunction function];
|
|
id<MTLRenderPipelineState> pso;
|
|
if (desc.vertexFunction && desc.fragmentFunction) {
|
|
NSError *error = nil;
|
|
pso = [device newRenderPipelineStateWithDescriptor:desc error:&error];
|
|
if (!pso) {
|
|
ERROR_LOG(Log::G3D, "Failed to create pipeline (%s): %s", description.c_str(), error ? [[error localizedDescription] UTF8String] : "?");
|
|
}
|
|
}
|
|
[state setPipeline:pso];
|
|
});
|
|
return pipeline;
|
|
}
|
|
|
|
struct StoredMetalPipeline {
|
|
MetalPipelineKey key;
|
|
uint32_t color;
|
|
uint32_t depth;
|
|
uint32_t stencil;
|
|
uint32_t sampleCount;
|
|
};
|
|
|
|
uint32_t DrawEngineMetal::PipelineRecordSize() {
|
|
return sizeof(StoredMetalPipeline);
|
|
}
|
|
|
|
bool DrawEngineMetal::SavePipelines(FILE *f) {
|
|
bool ok = true;
|
|
pipelines_.Iterate([&](const MetalPipelineKey &key, Pipeline *pipeline) {
|
|
StoredMetalPipeline stored{};
|
|
stored.key = key;
|
|
stored.color = (uint32_t)pipeline->format.color;
|
|
stored.depth = (uint32_t)pipeline->format.depth;
|
|
stored.stencil = (uint32_t)pipeline->format.stencil;
|
|
stored.sampleCount = (uint32_t)pipeline->format.sampleCount;
|
|
ok = ok && fwrite(&stored, sizeof(stored), 1, f) == 1;
|
|
});
|
|
return ok;
|
|
}
|
|
|
|
bool DrawEngineMetal::LoadPipelines(FILE *f, int count, bool waitForCompiles) {
|
|
std::vector<MetalPipelineState *> states;
|
|
bool ok = true;
|
|
for (int i = 0; i < count; i++) {
|
|
StoredMetalPipeline stored;
|
|
if (fread(&stored, sizeof(stored), 1, f) != 1) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
MetalRenderTargetFormat format;
|
|
format.color = (MTLPixelFormat)stored.color;
|
|
format.depth = (MTLPixelFormat)stored.depth;
|
|
format.stencil = (MTLPixelFormat)stored.stencil;
|
|
format.sampleCount = (int)stored.sampleCount;
|
|
DecVtxFormat decFmt;
|
|
const bool useHWTransform = stored.key.vtxFmtId != SOFTWARE_TRANSFORM_VTXFMT;
|
|
if (useHWTransform) {
|
|
decFmt.InitializeFromID(stored.key.vtxFmtId);
|
|
}
|
|
Pipeline *pipeline = GetOrCreatePipeline(stored.key, useHWTransform ? &decFmt : nullptr, format);
|
|
states.push_back(pipeline->state);
|
|
}
|
|
if (waitForCompiles) {
|
|
for (MetalPipelineState *state : states) {
|
|
[state wait];
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
void DrawEngineMetal::ConvertStateToKeys(int prim) {
|
|
bool useBufferedRendering = framebufferManager_->UseBufferedRendering();
|
|
|
|
if (gstate_c.IsDirty(DIRTY_BLEND_STATE)) {
|
|
if (gstate.isModeClear()) {
|
|
blendKey_.blendEnable = false;
|
|
blendKey_.eqColor = (uint32_t)BlendEq::ADD;
|
|
blendKey_.eqAlpha = (uint32_t)BlendEq::ADD;
|
|
blendKey_.srcColor = (uint32_t)BlendFactor::ONE;
|
|
blendKey_.srcAlpha = (uint32_t)BlendFactor::ONE;
|
|
blendKey_.dstColor = (uint32_t)BlendFactor::ZERO;
|
|
blendKey_.dstAlpha = (uint32_t)BlendFactor::ZERO;
|
|
blendKey_.colorWriteMask = ClearModeChannelMask();
|
|
dynState_.useBlendColor = false;
|
|
} else {
|
|
pipelineState_.Convert(draw_->GetShaderLanguageDesc().bitwiseOps, gstate_c.Use(GPU_USE_SHADER_BLENDING));
|
|
GenericMaskState &maskState = pipelineState_.maskState;
|
|
GenericBlendState &blendState = pipelineState_.blendState;
|
|
|
|
if (pipelineState_.FramebufferRead() && useBufferedRendering) {
|
|
ApplyFramebufferRead(&fboTexBindState_);
|
|
// The shader takes over the responsibility for blending, so recompute.
|
|
ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState);
|
|
dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE;
|
|
gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE);
|
|
} else if (fboTexBound_) {
|
|
boundSecondary_ = nil;
|
|
fboTexBound_ = false;
|
|
dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE;
|
|
gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE);
|
|
}
|
|
|
|
if (blendState.blendEnabled) {
|
|
blendKey_.blendEnable = true;
|
|
blendKey_.eqColor = (uint32_t)blendState.eqColor;
|
|
blendKey_.eqAlpha = (uint32_t)blendState.eqAlpha;
|
|
blendKey_.srcColor = (uint32_t)blendState.srcColor;
|
|
blendKey_.srcAlpha = (uint32_t)blendState.srcAlpha;
|
|
blendKey_.dstColor = (uint32_t)blendState.dstColor;
|
|
blendKey_.dstAlpha = (uint32_t)blendState.dstAlpha;
|
|
if (blendState.dirtyShaderBlendFixValues) {
|
|
dirtyRequiresRecheck_ |= DIRTY_SHADERBLEND;
|
|
gstate_c.Dirty(DIRTY_SHADERBLEND);
|
|
}
|
|
dynState_.useBlendColor = blendState.useBlendColor;
|
|
if (blendState.useBlendColor) {
|
|
blendState.blendColorToFloat4(dynState_.blendColor);
|
|
}
|
|
} else {
|
|
blendKey_.blendEnable = false;
|
|
blendKey_.eqColor = (uint32_t)BlendEq::ADD;
|
|
blendKey_.eqAlpha = (uint32_t)BlendEq::ADD;
|
|
blendKey_.srcColor = (uint32_t)BlendFactor::ONE;
|
|
blendKey_.srcAlpha = (uint32_t)BlendFactor::ONE;
|
|
blendKey_.dstColor = (uint32_t)BlendFactor::ZERO;
|
|
blendKey_.dstAlpha = (uint32_t)BlendFactor::ZERO;
|
|
dynState_.useBlendColor = false;
|
|
}
|
|
blendKey_.colorWriteMask = maskState.channelMask;
|
|
}
|
|
}
|
|
|
|
if (gstate_c.IsDirty(DIRTY_RASTER_STATE)) {
|
|
bool wantCull = !gstate.isModeClear() && prim != GE_PRIM_RECTANGLES && prim > GE_PRIM_LINE_STRIP && gstate.isCullEnabled();
|
|
dynState_.cull = wantCull ? (gstate.getCullMode() ? MTLCullModeFront : MTLCullModeBack) : MTLCullModeNone;
|
|
if (gstate.isModeClear() || gstate.isModeThrough()) {
|
|
dynState_.depthClamp = false;
|
|
} else if (gstate.getDepthRangeMin() == 0 || gstate.getDepthRangeMax() == 65535) {
|
|
dynState_.depthClamp = gstate.isDepthClipEnabled() && gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
|
} else {
|
|
dynState_.depthClamp = false;
|
|
}
|
|
}
|
|
|
|
if (gstate_c.IsDirty(DIRTY_DEPTHSTENCIL_STATE)) {
|
|
GenericStencilFuncState stencilState;
|
|
ConvertStencilFuncState(stencilState);
|
|
MetalDepthStencilKey &key = depthStencilKey_;
|
|
key.value = 0;
|
|
|
|
if (gstate.isModeClear()) {
|
|
key.depthTestEnable = true;
|
|
key.depthCompare = GE_COMP_ALWAYS;
|
|
key.depthWriteEnable = gstate.isClearModeDepthMask();
|
|
if (gstate.isClearModeAlphaMask()) {
|
|
key.stencilTestEnable = true;
|
|
key.stencilCompare = GE_COMP_ALWAYS;
|
|
key.stencilPass = GE_STENCILOP_REPLACE;
|
|
key.stencilFail = GE_STENCILOP_REPLACE;
|
|
key.stencilDepthFail = GE_STENCILOP_REPLACE;
|
|
key.writeMask = stencilState.writeMask;
|
|
key.compareMask = 0xFF;
|
|
// Overridden from software transform for clear rectangles.
|
|
dynState_.stencilRef = 0xFF;
|
|
}
|
|
} else {
|
|
if (!IsDepthTestEffectivelyDisabled()) {
|
|
key.depthTestEnable = true;
|
|
key.depthCompare = gstate.getDepthTestFunction();
|
|
key.depthWriteEnable = gstate.isDepthWriteEnabled();
|
|
UpdateEverUsedEqualDepth(gstate.getDepthTestFunction());
|
|
}
|
|
if (stencilState.enabled) {
|
|
key.stencilTestEnable = true;
|
|
key.stencilCompare = stencilState.testFunc;
|
|
key.stencilPass = stencilState.zPass;
|
|
key.stencilFail = stencilState.sFail;
|
|
key.stencilDepthFail = stencilState.zFail;
|
|
key.writeMask = stencilState.writeMask;
|
|
key.compareMask = stencilState.testMask;
|
|
dynState_.stencilRef = stencilState.testRef;
|
|
|
|
// Nasty special case for Spongebob and similar, see StateMappingVulkan.cpp.
|
|
if (SpongebobDepthInverseConditions(stencilState)) {
|
|
blendKey_.blendEnable = true;
|
|
blendKey_.eqAlpha = (uint32_t)BlendEq::ADD;
|
|
blendKey_.eqColor = (uint32_t)BlendEq::ADD;
|
|
blendKey_.srcColor = (uint32_t)BlendFactor::ZERO;
|
|
blendKey_.dstColor = (uint32_t)BlendFactor::ZERO;
|
|
blendKey_.srcAlpha = (uint32_t)BlendFactor::ZERO;
|
|
blendKey_.dstAlpha = (uint32_t)BlendFactor::ZERO;
|
|
blendKey_.colorWriteMask = 8;
|
|
key.depthCompare = GE_COMP_LESS; // Inverse of GREATER_EQUAL
|
|
key.stencilCompare = GE_COMP_ALWAYS;
|
|
key.stencilPass = GE_STENCILOP_ZERO;
|
|
key.stencilFail = GE_STENCILOP_ZERO;
|
|
key.stencilDepthFail = GE_STENCILOP_KEEP;
|
|
dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE;
|
|
gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) {
|
|
ViewportAndScissor vpAndScissor;
|
|
ConvertViewportAndScissor(
|
|
framebufferManager_->GetDisplayLayoutConfigCopy(),
|
|
useBufferedRendering,
|
|
framebufferManager_->GetRenderWidth(), framebufferManager_->GetRenderHeight(),
|
|
framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(),
|
|
vpAndScissor);
|
|
dynState_.viewport = MTLViewport{ vpAndScissor.viewportX, vpAndScissor.viewportY, vpAndScissor.viewportW, vpAndScissor.viewportH, 0.0, 1.0 };
|
|
dynState_.scissor[0] = vpAndScissor.scissorX;
|
|
dynState_.scissor[1] = vpAndScissor.scissorY;
|
|
dynState_.scissor[2] = std::max(0, vpAndScissor.scissorW);
|
|
dynState_.scissor[3] = std::max(0, vpAndScissor.scissorH);
|
|
}
|
|
}
|
|
|
|
void DrawEngineMetal::BindShaderBlendTex() {
|
|
if (!gstate.isModeClear() && fboTexBindState_ == FBO_TEX_COPY_BIND_TEX) {
|
|
VirtualFramebuffer *curRenderVfb = framebufferManager_->GetCurrentRenderVFB();
|
|
bool bindResult = framebufferManager_->BindFramebufferAsColorTexture(1, curRenderVfb, BINDFBCOLOR_MAY_COPY | BINDFBCOLOR_UNCACHED, Draw::ALL_LAYERS);
|
|
_dbg_assert_(bindResult);
|
|
boundSecondary_ = (__bridge id<MTLTexture>)(void *)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE1_IMAGEVIEW);
|
|
fboTexBound_ = true;
|
|
fboTexBindState_ = FBO_TEX_NONE;
|
|
// Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects.
|
|
dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE;
|
|
} else if (gstate.isModeClear() || !fboTexBound_) {
|
|
boundSecondary_ = nil;
|
|
}
|
|
}
|
|
|
|
bool DrawEngineMetal::ApplyState(const Pipeline *pipeline, bool overrideStencilRef, uint8_t stencilRef) {
|
|
if (!rm_->EnsureRenderEncoder()) {
|
|
return false;
|
|
}
|
|
rm_->SetPipeline(pipeline->state);
|
|
rm_->SetDepthStencilState(GetDepthStencilState(depthStencilKey_), depthStencilKey_.depthTestEnable || depthStencilKey_.stencilTestEnable);
|
|
if (depthStencilKey_.stencilTestEnable) {
|
|
rm_->SetStencilRef(overrideStencilRef ? stencilRef : dynState_.stencilRef);
|
|
}
|
|
if (dynState_.useBlendColor) {
|
|
rm_->SetBlendColor(dynState_.blendColor[0], dynState_.blendColor[1], dynState_.blendColor[2], dynState_.blendColor[3]);
|
|
}
|
|
rm_->SetCullMode(dynState_.cull);
|
|
rm_->SetFrontFacing(MTLWindingCounterClockwise);
|
|
rm_->SetDepthClip(dynState_.depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip);
|
|
rm_->SetViewport(dynState_.viewport);
|
|
return rm_->SetScissor(dynState_.scissor[0], dynState_.scissor[1], dynState_.scissor[2], dynState_.scissor[3]);
|
|
}
|
|
|
|
static inline void BindTexture(MetalRenderManager *rm, __unsafe_unretained id<MTLTexture> tex, __unsafe_unretained id<MTLSamplerState> sampler, int index, uint32_t arrayMask) {
|
|
if (arrayMask & (1 << index)) {
|
|
tex = rm->ArrayView(tex);
|
|
}
|
|
rm->SetFragmentTexture(tex, index);
|
|
rm->SetFragmentSampler(sampler, index);
|
|
}
|
|
|
|
void DrawEngineMetal::BindResources(const Pipeline *pipeline) {
|
|
uint64_t dirty = shaderManager_->UpdateUniforms(framebufferManager_->UseBufferedRendering(), false);
|
|
if ((dirty & DIRTY_BASE_UNIFORMS) || !baseUBO_.buffer || uboFrame_ != rm_->FrameNumber()) {
|
|
baseUBO_ = rm_->Push().Push(&shaderManager_->BaseUniforms(), sizeof(UB_VS_FS_Base), 256);
|
|
}
|
|
if ((dirty & DIRTY_LIGHT_UNIFORMS) || !lightUBO_.buffer || uboFrame_ != rm_->FrameNumber()) {
|
|
lightUBO_ = rm_->Push().Push(&shaderManager_->LightUniforms(), sizeof(UB_VS_Lights), 256);
|
|
}
|
|
uboFrame_ = rm_->FrameNumber();
|
|
rm_->SetVertexBuffer(baseUBO_.buffer, baseUBO_.offset, METAL_DRAW_BINDING_DYNUBO_BASE);
|
|
rm_->SetFragmentBuffer(baseUBO_.buffer, baseUBO_.offset, METAL_DRAW_BINDING_DYNUBO_BASE);
|
|
rm_->SetVertexBuffer(lightUBO_.buffer, lightUBO_.offset, METAL_DRAW_BINDING_DYNUBO_LIGHT);
|
|
|
|
// Only the slots the fragment shader actually uses.
|
|
const uint32_t used = pipeline->fragmentTextureMask;
|
|
const uint32_t arrayMask = pipeline->fragmentArrayTextureMask;
|
|
if (used & (1 << METAL_DRAW_BINDING_TEXTURE)) {
|
|
__unsafe_unretained id<MTLTexture> tex = textureCache_->curTexture;
|
|
__unsafe_unretained id<MTLSamplerState> sampler = textureCache_->curSampler;
|
|
BindTexture(rm_, tex ? tex : nullTexture_, sampler ? sampler : samplerNearest_, METAL_DRAW_BINDING_TEXTURE, arrayMask);
|
|
}
|
|
if (used & (1 << METAL_DRAW_BINDING_2ND_TEXTURE)) {
|
|
BindTexture(rm_, boundSecondary_ ? boundSecondary_ : nullTexture_, samplerNearest_, METAL_DRAW_BINDING_2ND_TEXTURE, arrayMask);
|
|
}
|
|
if (used & (1 << METAL_DRAW_BINDING_DEPAL_TEXTURE)) {
|
|
BindTexture(rm_, boundDepal_ ? boundDepal_ : nullTexture_, boundDepalSmoothed_ ? samplerLinear_ : samplerNearest_, METAL_DRAW_BINDING_DEPAL_TEXTURE, arrayMask);
|
|
}
|
|
}
|
|
|
|
void DrawEngineMetal::DrawPrimitives(MTLPrimitiveType prim, __unsafe_unretained id<MTLBuffer> vbuf, uint32_t vbOffset, int count, const uint16_t *inds) {
|
|
rm_->SetVertexBuffer(vbuf, vbOffset, METAL_VERTEX_BUFFER_INDEX);
|
|
if (inds) {
|
|
MetalPushAlloc ialloc = rm_->Push().Push(inds, count * sizeof(uint16_t), 4);
|
|
rm_->DrawIndexed(prim, count, ialloc.buffer, ialloc.offset);
|
|
} else {
|
|
rm_->Draw(prim, 0, count);
|
|
}
|
|
}
|
|
|
|
void DrawEngineMetal::ResetAfterSkippedDraw() {
|
|
indexGen.Reset();
|
|
numDecodedVerts_ = 0;
|
|
numDrawVerts_ = 0;
|
|
numDrawInds_ = 0;
|
|
vertexCountInDrawCalls_ = 0;
|
|
expandedVertsInDrawCalls_ = 0;
|
|
numVertsToDecode_ = 0;
|
|
decodeIndsCounter_ = 0;
|
|
decodeVertsCounter_ = 0;
|
|
gstate_c.vertexFullAlpha = true;
|
|
}
|
|
|
|
void DrawEngineMetal::Flush() {
|
|
if (!numDrawVerts_) {
|
|
return;
|
|
}
|
|
PROFILE_THIS_SCOPE("Flush");
|
|
|
|
GEPrimitiveType prim = prevPrim_;
|
|
|
|
// Metal's provoking vertex is the first one, so flat shading goes through software transform.
|
|
bool provokingVertexOk = gstate.getShadeMode() != GE_SHADE_FLAT;
|
|
bool useHWTransform = CanUseHardwareTransform(prim) && provokingVertexOk;
|
|
if (clipInfoFlags_ & ClipInfoFlags::Valid) {
|
|
if (clipInfoFlags_ & ClipInfoFlags::SoftClipCull) {
|
|
useHWTransform = false;
|
|
}
|
|
}
|
|
if (clipInfoFlags_ != lastClipInfoFlags_) {
|
|
ClipInfoFlags changed = (ClipInfoFlags)((u32)clipInfoFlags_ ^ (u32)lastClipInfoFlags_);
|
|
if (changed & (ClipInfoFlags::DepthClampFragment | ClipInfoFlags::MinMaxZDiscard)) {
|
|
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
|
|
}
|
|
if (changed & ClipInfoFlags::FlatZ) {
|
|
gstate_c.Dirty(DIRTY_TEXTURE_PARAMS);
|
|
}
|
|
lastClipInfoFlags_ = clipInfoFlags_;
|
|
}
|
|
if (useHWTransform != lastUseHwTransform_) {
|
|
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
|
|
lastUseHwTransform_ = useHWTransform;
|
|
}
|
|
|
|
auto updateVertexFullAlpha = [&]() {
|
|
bool hasColor = (lastVType_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
|
|
if (gstate.isModeThrough()) {
|
|
gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (hasColor || gstate.getMaterialAmbientA() == 255);
|
|
} else {
|
|
gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && ((hasColor && (gstate.materialupdate & 1)) || gstate.getMaterialAmbientA() == 255) && (!gstate.isLightingEnabled() || gstate.getAmbientA() == 255);
|
|
}
|
|
};
|
|
|
|
auto updatePipeline = [&](bool hwTransform) -> Pipeline * {
|
|
if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE) || prim != lastPrim_) {
|
|
if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) {
|
|
ConvertStateToKeys(prim);
|
|
}
|
|
VShaderID vsid;
|
|
FShaderID fsid;
|
|
shaderManager_->GetShaderIDs(prim, dec_->VertexType(), &vsid, &fsid, pipelineState_, hwTransform, clipInfoFlags_);
|
|
// This might copy the framebuffer, which interrupts the render pass. So it goes before the pipeline lookup,
|
|
// which depends on the render target.
|
|
BindShaderBlendTex();
|
|
lastPipeline_ = GetPipeline(vsid, fsid, hwTransform);
|
|
gstate_c.Clean(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE);
|
|
gstate_c.Dirty(dirtyRequiresRecheck_);
|
|
dirtyRequiresRecheck_ = 0;
|
|
}
|
|
lastPrim_ = prim;
|
|
return lastPipeline_;
|
|
};
|
|
|
|
if (useHWTransform) {
|
|
MetalPushAlloc valloc;
|
|
if (lastVType_ & GE_VTYPE_WEIGHT_MASK) {
|
|
// If skinning, we're predecoding into "decoded". So make sure we're done, then push that content.
|
|
DecodeVerts(dec_, decoded_);
|
|
valloc = rm_->Push().Push(decoded_, numDecodedVerts_ * dec_->GetDecVtxFmt().stride, 16);
|
|
} else {
|
|
int vertsToDecode = ComputeNumVertsToDecode();
|
|
valloc = rm_->Push().Allocate(vertsToDecode * dec_->GetDecVtxFmt().stride, 16);
|
|
DecodeVerts(dec_, valloc.ptr);
|
|
}
|
|
|
|
int vertexCount;
|
|
int maxIndex;
|
|
bool useElements;
|
|
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
|
gpuStats.perFrame.numVertsDrawn += vertexCount;
|
|
updateVertexFullAlpha();
|
|
|
|
if (gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS) && !gstate.isModeClear() && gstate.isTextureMapEnabled()) {
|
|
gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
|
|
const TextureApplyResult textureResult = textureCache_->ApplyTexture(true);
|
|
textureCache_->ApplySampler(textureResult, clipInfoFlags_ & ClipInfoFlags::FlatZ, false);
|
|
} else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) {
|
|
// This catches the case of clearing a texture.
|
|
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
|
|
}
|
|
|
|
Pipeline *pipeline = updatePipeline(true);
|
|
if (!pipeline || !ApplyState(pipeline, false, 0)) {
|
|
ResetAfterSkippedDraw();
|
|
return;
|
|
}
|
|
BindResources(pipeline);
|
|
MTLPrimitiveType mtlPrim = prim == GE_PRIM_TRIANGLE_STRIP ? MTLPrimitiveTypeTriangleStrip : MTLPrimitiveTypeTriangle;
|
|
DrawPrimitives(mtlPrim, valloc.buffer, valloc.offset, vertexCount, useElements ? decIndex_ : nullptr);
|
|
if (useDepthRaster_) {
|
|
DepthRasterSubmitRaw(prim, dec_, dec_->VertexType(), vertexCount);
|
|
}
|
|
} else {
|
|
gpuStats.perFrame.numSoftTransformedDraws++;
|
|
PROFILE_THIS_SCOPE("soft");
|
|
|
|
DecodeVerts(dec_, decoded_);
|
|
int vertexCount = DecodeInds();
|
|
updateVertexFullAlpha();
|
|
|
|
gpuStats.perFrame.numVertsDrawn += vertexCount;
|
|
prim = IndexGenerator::GeneralPrim((GEPrimitiveType)drawInds_[0].prim);
|
|
|
|
if (useDepthRaster_) {
|
|
DepthRasterPredecoded(prim, decoded_, numDecodedVerts_, dec_, vertexCount);
|
|
}
|
|
|
|
u16 *inds = decIndex_;
|
|
if (gstate.getShadeMode() == GE_SHADE_FLAT) {
|
|
// Rotate the index buffer to simulate a different provoking vertex.
|
|
IndexBufferProvokingLastToFirst(prim, inds, vertexCount);
|
|
}
|
|
|
|
bool textureNeedsApply = false;
|
|
TextureApplyResult textureResult;
|
|
if (gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS) && !gstate.isModeClear() && gstate.isTextureMapEnabled()) {
|
|
gstate_c.Clean(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
|
|
gstate_c.dstSquared = false;
|
|
textureResult = textureCache_->ApplyTexture(true);
|
|
// NOTE: After this is set, we MUST call ApplySampler before returning.
|
|
textureNeedsApply = true;
|
|
} else if (gstate.getTextureAddress(0) == (gstate.getFrameBufRawAddress() | 0x04000000)) {
|
|
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
|
|
}
|
|
|
|
SoftwareTransformResult result{};
|
|
SoftwareTransformParams params{};
|
|
params.decoded = decoded_;
|
|
params.transformed = transformed_;
|
|
params.transformedExpanded = transformedExpanded_;
|
|
// Clears through the render pass don't respect the scissor, so only allow them when buffered.
|
|
params.allowClear = framebufferManager_->UseBufferedRendering();
|
|
params.allowSeparateAlphaClear = false;
|
|
params.everUsedEqualDepth = everUsedEqualDepth_;
|
|
params.clipInfoFlags = clipInfoFlags_;
|
|
params.trianglesReversed = BatchTrianglesReversed();
|
|
|
|
const SoftwareTransformAction action = RunSoftwareTransform(params, prim, dec_->VertexType(), dec_->GetDecVtxFmt(), numDecodedVerts_, VERTEX_BUFFER_MAX, vertexCount, inds, RemainingIndices(inds), &result);
|
|
if (textureNeedsApply) {
|
|
textureCache_->ApplySampler(textureResult, clipInfoFlags_ & ClipInfoFlags::FlatZ, result.pixelMapped);
|
|
if (gstate_c.dstSquared) {
|
|
gstate_c.Dirty(DIRTY_BLEND_STATE);
|
|
}
|
|
}
|
|
if (action == SW_DRAW_INDEXED) {
|
|
Pipeline *pipeline = updatePipeline(false);
|
|
if (!pipeline || !ApplyState(pipeline, result.setStencil, result.stencilValue)) {
|
|
ResetAfterSkippedDraw();
|
|
return;
|
|
}
|
|
// UpdateUniforms in BindResources would otherwise use pixelMapped = false.
|
|
uint64_t dirty = shaderManager_->UpdateUniforms(framebufferManager_->UseBufferedRendering(), result.pixelMapped);
|
|
if (dirty & DIRTY_BASE_UNIFORMS) {
|
|
baseUBO_ = MetalPushAlloc{};
|
|
}
|
|
if (dirty & DIRTY_LIGHT_UNIFORMS) {
|
|
lightUBO_ = MetalPushAlloc{};
|
|
}
|
|
BindResources(pipeline);
|
|
MetalPushAlloc valloc = rm_->Push().Push(result.drawBuffer, result.drawVertexCount * sizeof(TransformedVertex), 16);
|
|
DrawPrimitives(MTLPrimitiveTypeTriangle, valloc.buffer, valloc.offset, result.drawIndexCount, inds);
|
|
} else if (action == SW_CLEAR) {
|
|
// Note: we won't get here if the clear is alpha but not color, or color but not alpha.
|
|
bool clearColor = gstate.isClearModeColorMask();
|
|
bool clearAlpha = gstate.isClearModeAlphaMask(); // and stencil
|
|
bool clearDepth = gstate.isClearModeDepthMask();
|
|
Draw::Aspect mask = Draw::Aspect::NO_BIT;
|
|
if (clearColor || clearAlpha) mask |= Draw::Aspect::COLOR_BIT;
|
|
if (clearDepth) mask |= Draw::Aspect::DEPTH_BIT;
|
|
if (clearAlpha) mask |= Draw::Aspect::STENCIL_BIT;
|
|
// The alpha channel and the stencil channel are shared on the PSP.
|
|
draw_->Clear(mask, result.color, result.depth, result.color >> 24);
|
|
if (gstate_c.Use(GPU_USE_CLEAR_RAM_HACK) && gstate.isClearModeColorMask() && (gstate.isClearModeAlphaMask() || gstate.FrameBufFormat() == GE_FORMAT_565)) {
|
|
int scissorX1 = gstate.getScissorX1();
|
|
int scissorY1 = gstate.getScissorY1();
|
|
int scissorX2 = gstate.getScissorX2() + 1;
|
|
int scissorY2 = gstate.getScissorY2() + 1;
|
|
framebufferManager_->ApplyClearToMemory(scissorX1, scissorY1, scissorX2, scissorY2, result.color);
|
|
}
|
|
}
|
|
}
|
|
|
|
ResetAfterDrawInline();
|
|
framebufferManager_->SetColorUpdated(gstate_c.skipDrawReason);
|
|
gpuCommon_->NotifyFlush();
|
|
}
|