// Copyright 2016 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoCommon/RenderState.h" #include #include #include "VideoCommon/BPMemory.h" #include "VideoCommon/TextureConfig.h" void RasterizationState::Generate(const BPMemory& bp, PrimitiveType primitive_type) { cullmode = bp.genMode.cullmode; primitive = primitive_type; // Back-face culling should be disabled for points/lines. if (primitive_type != PrimitiveType::Triangles && primitive_type != PrimitiveType::TriangleStrip) cullmode = CullMode::None; } void DepthState::Generate(const BPMemory& bp) { testenable = bp.zmode.testenable.Value(); updateenable = bp.zmode.updateenable.Value(); func = bp.zmode.func.Value(); } static bool IsDualSrc(SrcBlendFactor factor) { return factor == SrcBlendFactor::SrcAlpha || factor == SrcBlendFactor::InvSrcAlpha; } static bool IsDualSrc(DstBlendFactor factor) { return factor == DstBlendFactor::SrcAlpha || factor == DstBlendFactor::InvSrcAlpha; } bool BlendingState::RequiresDualSrc() const { bool requires_dual_src = false; requires_dual_src |= IsDualSrc(srcfactor) || IsDualSrc(srcfactoralpha); requires_dual_src |= IsDualSrc(dstfactor) || IsDualSrc(dstfactoralpha); requires_dual_src &= blendenable && usedualsrc; return requires_dual_src; } // If the framebuffer format has no alpha channel, it is assumed to // ONE on blending. As the backends may emulate this framebuffer // configuration with an alpha channel, we just drop all references // to the destination alpha channel. static SrcBlendFactor RemoveDstAlphaUsage(SrcBlendFactor factor) { switch (factor) { case SrcBlendFactor::DstAlpha: return SrcBlendFactor::One; case SrcBlendFactor::InvDstAlpha: return SrcBlendFactor::Zero; default: return factor; } } static DstBlendFactor RemoveDstAlphaUsage(DstBlendFactor factor) { switch (factor) { case DstBlendFactor::DstAlpha: return DstBlendFactor::One; case DstBlendFactor::InvDstAlpha: return DstBlendFactor::Zero; default: return factor; } } // We separate the blending parameter for rgb and alpha. For blending // the alpha component, CLR and ALPHA are indentical. So just always // use ALPHA as this makes it easier for the backends to use the second // alpha value of dual source blending. static DstBlendFactor RemoveSrcColorUsage(DstBlendFactor factor) { switch (factor) { case DstBlendFactor::SrcClr: return DstBlendFactor::SrcAlpha; case DstBlendFactor::InvSrcClr: return DstBlendFactor::InvSrcAlpha; default: return factor; } } // Same as RemoveSrcColorUsage, but because of the overlapping enum, // this must be written as another function. static SrcBlendFactor RemoveDstColorUsage(SrcBlendFactor factor) { switch (factor) { case SrcBlendFactor::DstClr: return SrcBlendFactor::DstAlpha; case SrcBlendFactor::InvDstClr: return SrcBlendFactor::InvDstAlpha; default: return factor; } } void BlendingState::Generate(const BPMemory& bp) { // Start with everything disabled. hex = 0; const bool target_has_alpha = bp.zcontrol.pixel_format == PixelFormat::RGBA6_Z24; const bool alpha_test_may_succeed = bp.alpha_test.TestResult() != AlphaTestResult::Fail; colorupdate = bp.blendmode.colorupdate && alpha_test_may_succeed; alphaupdate = bp.blendmode.alphaupdate && target_has_alpha && alpha_test_may_succeed; const bool dstalpha = bp.dstalpha.enable && alphaupdate; usedualsrc = true; if (bp.blendmode.blendenable) { if (bp.blendmode.subtract) { blendenable = true; subtractAlpha = subtract = true; srcfactoralpha = srcfactor = SrcBlendFactor::One; dstfactoralpha = dstfactor = DstBlendFactor::One; if (dstalpha) { subtractAlpha = false; srcfactoralpha = SrcBlendFactor::One; dstfactoralpha = DstBlendFactor::Zero; } } else { blendenable = true; srcfactor = bp.blendmode.srcfactor; dstfactor = bp.blendmode.dstfactor; if (!target_has_alpha) { // uses ONE instead of DSTALPHA srcfactor = RemoveDstAlphaUsage(srcfactor); dstfactor = RemoveDstAlphaUsage(dstfactor); } // replaces SrcClr with SrcAlpha and DstClr with DstAlpha, it is important to // use the dst function for the src factor and vice versa srcfactoralpha = RemoveDstColorUsage(srcfactor); dstfactoralpha = RemoveSrcColorUsage(dstfactor); if (dstalpha) { srcfactoralpha = SrcBlendFactor::One; dstfactoralpha = DstBlendFactor::Zero; } } } else if (bp.blendmode.logicopenable) { if (bp.blendmode.logicmode == LogicOp::NoOp) { // Fast path for Kirby's Return to Dreamland, they use it with dstAlpha. colorupdate = false; alphaupdate = alphaupdate && dstalpha; } else { logicopenable = true; logicmode = bp.blendmode.logicmode; if (dstalpha) { // TODO: Not supported by backends. } } } // If we aren't writing color or alpha, don't blend it. // Intel GPUs on D3D12 seem to have issues with dual-source blend if the second source is used in // the blend state but not actually written (i.e. the alpha src or dst factor is src alpha, but // alpha update is disabled). So, change the blending configuration to not use a dual-source // factor. Note that in theory, disabling writing should render these irrelevant. if (!colorupdate) { srcfactor = SrcBlendFactor::Zero; dstfactor = DstBlendFactor::One; } if (!alphaupdate) { srcfactoralpha = SrcBlendFactor::Zero; dstfactoralpha = DstBlendFactor::One; } } void BlendingState::ApproximateLogicOpWithBlending() { struct LogicOpApproximation { bool blendEnable; bool subtract; SrcBlendFactor srcfactor; DstBlendFactor dstfactor; }; // TODO: This previously had a warning about SRC and DST being aliased and not to mix them, // but INVSRCCLR and INVDSTCLR were also aliased and were mixed. // Thus, NOR, EQUIV, INVERT, COPY_INVERTED, and OR_INVERTED duplicate(d) other values. static constexpr std::array approximations = {{ // clang-format off {false, false, SrcBlendFactor::One, DstBlendFactor::Zero}, // CLEAR (Shader outputs 0) {true, false, SrcBlendFactor::DstClr, DstBlendFactor::Zero}, // AND {true, true, SrcBlendFactor::One, DstBlendFactor::InvSrcClr}, // AND_REVERSE {false, false, SrcBlendFactor::One, DstBlendFactor::Zero}, // COPY {true, true, SrcBlendFactor::DstClr, DstBlendFactor::One}, // AND_INVERTED {true, false, SrcBlendFactor::Zero, DstBlendFactor::One}, // NOOP {true, false, SrcBlendFactor::InvDstClr, DstBlendFactor::InvSrcClr}, // XOR {true, false, SrcBlendFactor::InvDstClr, DstBlendFactor::One}, // OR {true, false, SrcBlendFactor::InvDstClr, DstBlendFactor::InvSrcClr}, // NOR {true, false, SrcBlendFactor::InvDstClr, DstBlendFactor::Zero}, // EQUIV {true, false, SrcBlendFactor::InvDstClr, DstBlendFactor::Zero}, // INVERT (Shader outputs 255) {true, false, SrcBlendFactor::One, DstBlendFactor::InvDstAlpha}, // OR_REVERSE {false, false, SrcBlendFactor::One, DstBlendFactor::Zero}, // COPY_INVERTED (Shader inverts) {true, false, SrcBlendFactor::InvDstClr, DstBlendFactor::One}, // OR_INVERTED {true, false, SrcBlendFactor::InvDstClr, DstBlendFactor::InvSrcClr}, // NAND {false, false, SrcBlendFactor::One, DstBlendFactor::Zero}, // SET (Shader outputs 255) // clang-format on }}; logicopenable = false; usedualsrc = false; const LogicOpApproximation& approximation = approximations[static_cast(logicmode.Value())]; if (approximation.blendEnable) { blendenable = true; subtract = approximation.subtract; srcfactor = approximation.srcfactor; srcfactoralpha = approximation.srcfactor; dstfactor = approximation.dstfactor; dstfactoralpha = approximation.dstfactor; } } bool BlendingState::LogicOpApproximationIsExact() { switch (logicmode.Value()) { case LogicOp::Clear: case LogicOp::Set: case LogicOp::NoOp: case LogicOp::Invert: case LogicOp::CopyInverted: case LogicOp::Copy: return true; default: return false; } } bool BlendingState::LogicOpApproximationWantsShaderHelp() { switch (logicmode.Value()) { case LogicOp::Clear: case LogicOp::Set: case LogicOp::NoOp: case LogicOp::Invert: case LogicOp::CopyInverted: return true; default: return false; } } void SamplerState::Generate(const BPMemory& bp, u32 index) { auto tex = bp.tex.GetUnit(index); const TexMode0& bp_tm0 = tex.texMode0; const TexMode1& bp_tm1 = tex.texMode1; // GX can configure the mip filter to none. However, D3D and Vulkan can't express this in their // sampler states. Therefore, we set the min/max LOD to zero if this option is used. tm0.min_filter = bp_tm0.min_filter; tm0.mipmap_filter = bp_tm0.mipmap_filter == MipMode::Linear ? FilterMode::Linear : FilterMode::Near; tm0.mag_filter = bp_tm0.mag_filter; // If mipmaps are disabled, clamp min/max lod if (bp_tm0.mipmap_filter == MipMode::None) { tm1.max_lod = 0; tm1.min_lod = 0; tm0.lod_bias = 0; } else { // NOTE: When comparing, max is checked first, then min; if max is less than min, max wins tm1.max_lod = bp_tm1.max_lod.Value(); tm1.min_lod = std::min(tm1.max_lod.Value(), bp_tm1.min_lod.Value()); tm0.lod_bias = bp_tm0.lod_bias * (256 / 32); } // Wrap modes // Hardware testing indicates that wrap_mode set to 3 behaves the same as clamp. auto filter_invalid_wrap = [](WrapMode mode) { return (mode <= WrapMode::Mirror) ? mode : WrapMode::Clamp; }; tm0.wrap_u = filter_invalid_wrap(bp_tm0.wrap_s); tm0.wrap_v = filter_invalid_wrap(bp_tm0.wrap_t); tm0.diag_lod = bp_tm0.diag_lod; tm0.anisotropic_filtering = false; // TODO: Respect BP anisotropic filtering mode tm0.lod_clamp = bp_tm0.lod_clamp; // TODO: What does this do? } namespace RenderState { RasterizationState GetInvalidRasterizationState() { RasterizationState state; state.hex = UINT32_C(0xFFFFFFFF); return state; } RasterizationState GetNoCullRasterizationState(PrimitiveType primitive) { RasterizationState state = {}; state.cullmode = CullMode::None; state.primitive = primitive; return state; } RasterizationState GetCullBackFaceRasterizationState(PrimitiveType primitive) { RasterizationState state = {}; state.cullmode = CullMode::Back; state.primitive = primitive; return state; } DepthState GetInvalidDepthState() { DepthState state; state.hex = UINT32_C(0xFFFFFFFF); return state; } DepthState GetNoDepthTestingDepthState() { DepthState state = {}; state.testenable = false; state.updateenable = false; state.func = CompareMode::Always; return state; } DepthState GetAlwaysWriteDepthState() { DepthState state = {}; state.testenable = true; state.updateenable = true; state.func = CompareMode::Always; return state; } BlendingState GetInvalidBlendingState() { BlendingState state; state.hex = UINT32_C(0xFFFFFFFF); return state; } BlendingState GetNoBlendingBlendState() { BlendingState state = {}; state.usedualsrc = false; state.blendenable = false; state.srcfactor = SrcBlendFactor::One; state.srcfactoralpha = SrcBlendFactor::One; state.dstfactor = DstBlendFactor::Zero; state.dstfactoralpha = DstBlendFactor::Zero; state.logicopenable = false; state.colorupdate = true; state.alphaupdate = true; return state; } BlendingState GetNoColorWriteBlendState() { BlendingState state = {}; state.usedualsrc = false; state.blendenable = false; state.srcfactor = SrcBlendFactor::One; state.srcfactoralpha = SrcBlendFactor::One; state.dstfactor = DstBlendFactor::Zero; state.dstfactoralpha = DstBlendFactor::Zero; state.logicopenable = false; state.colorupdate = false; state.alphaupdate = false; return state; } SamplerState GetInvalidSamplerState() { SamplerState state; state.tm0.hex = 0xFFFFFFFF; state.tm1.hex = 0xFFFFFFFF; return state; } SamplerState GetPointSamplerState() { SamplerState state = {}; state.tm0.min_filter = FilterMode::Near; state.tm0.mag_filter = FilterMode::Near; state.tm0.mipmap_filter = FilterMode::Near; state.tm0.wrap_u = WrapMode::Clamp; state.tm0.wrap_v = WrapMode::Clamp; state.tm1.min_lod = 0; state.tm1.max_lod = 255; state.tm0.lod_bias = 0; state.tm0.anisotropic_filtering = false; state.tm0.diag_lod = LODType::Edge; state.tm0.lod_clamp = false; return state; } SamplerState GetLinearSamplerState() { SamplerState state = {}; state.tm0.min_filter = FilterMode::Linear; state.tm0.mag_filter = FilterMode::Linear; state.tm0.mipmap_filter = FilterMode::Linear; state.tm0.wrap_u = WrapMode::Clamp; state.tm0.wrap_v = WrapMode::Clamp; state.tm1.min_lod = 0; state.tm1.max_lod = 255; state.tm0.lod_bias = 0; state.tm0.anisotropic_filtering = false; state.tm0.diag_lod = LODType::Edge; state.tm0.lod_clamp = false; return state; } FramebufferState GetColorFramebufferState(AbstractTextureFormat format) { FramebufferState state = {}; state.color_texture_format = format; state.depth_texture_format = AbstractTextureFormat::Undefined; state.per_sample_shading = false; state.samples = 1; state.additional_color_attachment_count = 0; return state; } FramebufferState GetRGBA8FramebufferState() { return GetColorFramebufferState(AbstractTextureFormat::RGBA8); } } // namespace RenderState