mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
The GE's blend terms are floor((2c+1)(2f+1)/1024), which truncates, while the hardware multiplies by f/255 and rounds, so every pass of a fixed color blend chain came out up to a level brighter. God Eater Burst's bloom is many such passes and ended up far too bright. For additive FIXA/FIXB blends that aren't just 0 and 0xFFFFFF, the shader now computes the src term exactly and lowers it by 0.5 - (2B+1)/1024, and the blend constant is the GE's dst factor (2B+1)/512, so the hardware's rounding of the sum lands on the GE's floor (except where the src term would go below zero). Blend constants are passed as floats to make that possible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
306 lines
11 KiB
C++
306 lines
11 KiB
C++
// 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/.
|
|
|
|
|
|
// Alpha/stencil is a convoluted mess. Some good comments are here:
|
|
// https://github.com/hrydgard/ppsspp/issues/3768
|
|
|
|
#include "ppsspp_config.h"
|
|
#include "GPU/GLES/StateMappingGLES.h"
|
|
#include "Common/Profiler/Profiler.h"
|
|
#include "Common/GPU/OpenGL/GLDebugLog.h"
|
|
#include "Common/GPU/OpenGL/GLRenderManager.h"
|
|
#include "Common/Data/Convert/SmallDataConvert.h"
|
|
|
|
#include "GPU/GPUState.h"
|
|
#include "GPU/ge_constants.h"
|
|
#include "GPU/GLES/GPU_GLES.h"
|
|
#include "GPU/GLES/FramebufferManagerGLES.h"
|
|
|
|
static constexpr GLushort glBlendFactorLookup[(size_t)BlendFactor::COUNT] = {
|
|
GL_ZERO,
|
|
GL_ONE,
|
|
GL_SRC_COLOR,
|
|
GL_ONE_MINUS_SRC_COLOR,
|
|
GL_DST_COLOR,
|
|
GL_ONE_MINUS_DST_COLOR,
|
|
GL_SRC_ALPHA,
|
|
GL_ONE_MINUS_SRC_ALPHA,
|
|
GL_DST_ALPHA,
|
|
GL_ONE_MINUS_DST_ALPHA,
|
|
GL_CONSTANT_COLOR,
|
|
GL_ONE_MINUS_CONSTANT_COLOR,
|
|
GL_CONSTANT_ALPHA,
|
|
GL_ONE_MINUS_CONSTANT_ALPHA,
|
|
#if !defined(USING_GLES2) // TODO: Remove when we have better headers
|
|
GL_SRC1_COLOR,
|
|
GL_ONE_MINUS_SRC1_COLOR,
|
|
GL_SRC1_ALPHA,
|
|
GL_ONE_MINUS_SRC1_ALPHA,
|
|
#elif !PPSSPP_PLATFORM(IOS)
|
|
GL_SRC1_COLOR_EXT,
|
|
GL_ONE_MINUS_SRC1_COLOR_EXT,
|
|
GL_SRC1_ALPHA_EXT,
|
|
GL_ONE_MINUS_SRC1_ALPHA_EXT,
|
|
#else
|
|
GL_INVALID_ENUM,
|
|
GL_INVALID_ENUM,
|
|
GL_INVALID_ENUM,
|
|
GL_INVALID_ENUM,
|
|
#endif
|
|
GL_INVALID_ENUM,
|
|
};
|
|
|
|
static constexpr GLushort glBlendEqLookup[(size_t)BlendEq::COUNT] = {
|
|
GL_FUNC_ADD,
|
|
GL_FUNC_SUBTRACT,
|
|
GL_FUNC_REVERSE_SUBTRACT,
|
|
GL_MIN,
|
|
GL_MAX,
|
|
};
|
|
|
|
static constexpr GLushort cullingMode[] = {
|
|
GL_FRONT,
|
|
GL_BACK,
|
|
};
|
|
|
|
static constexpr GLushort compareOps[] = {
|
|
GL_NEVER, GL_ALWAYS, GL_EQUAL, GL_NOTEQUAL,
|
|
GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL,
|
|
};
|
|
|
|
static constexpr GLushort stencilOps[] = {
|
|
GL_KEEP,
|
|
GL_ZERO,
|
|
GL_REPLACE,
|
|
GL_INVERT,
|
|
GL_INCR,
|
|
GL_DECR,
|
|
GL_KEEP, // reserved
|
|
GL_KEEP, // reserved
|
|
};
|
|
|
|
#if !defined(USING_GLES2)
|
|
static constexpr GLushort logicOps[] = {
|
|
GL_CLEAR,
|
|
GL_AND,
|
|
GL_AND_REVERSE,
|
|
GL_COPY,
|
|
GL_AND_INVERTED,
|
|
GL_NOOP,
|
|
GL_XOR,
|
|
GL_OR,
|
|
GL_NOR,
|
|
GL_EQUIV,
|
|
GL_INVERT,
|
|
GL_OR_REVERSE,
|
|
GL_COPY_INVERTED,
|
|
GL_OR_INVERTED,
|
|
GL_NAND,
|
|
GL_SET,
|
|
};
|
|
#endif
|
|
|
|
void DrawEngineGLES::ApplyDrawState(int prim) {
|
|
GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
|
|
|
if (!gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE)) {
|
|
// Nothing to do, let's early-out
|
|
return;
|
|
}
|
|
|
|
// Start profiling here to skip SetTexture which is already accounted for
|
|
PROFILE_THIS_SCOPE("applydrawstate");
|
|
|
|
uint64_t dirtyRequiresRecheck_ = 0;
|
|
bool useBufferedRendering = framebufferManager_->UseBufferedRendering();
|
|
|
|
if (gstate_c.IsDirty(DIRTY_BLEND_STATE)) {
|
|
if (gstate.isModeClear()) {
|
|
renderManager->SetNoBlendAndMask(ClearModeChannelMask());
|
|
} else {
|
|
pipelineState_.Convert(draw_->GetShaderLanguageDesc().bitwiseOps, gstate_c.Use(GPU_USE_SHADER_BLENDING));
|
|
GenericMaskState &maskState = pipelineState_.maskState;
|
|
GenericBlendState &blendState = pipelineState_.blendState;
|
|
GenericLogicState &logicState = pipelineState_.logicState;
|
|
|
|
if (pipelineState_.FramebufferRead()) {
|
|
FBOTexState fboTexBindState = FBO_TEX_NONE;
|
|
ApplyFramebufferRead(&fboTexBindState);
|
|
// The shader takes over the responsibility for blending, so recompute.
|
|
ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState);
|
|
|
|
// We copy the framebuffer here, as doing so will wipe any blend state if we do it later.
|
|
// fboTexNeedsBind_ won't be set if we can read directly from the target.
|
|
if (fboTexBindState == FBO_TEX_COPY_BIND_TEX) {
|
|
// Note that this is positions, not UVs, that we need the copy from.
|
|
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY | BINDFBCOLOR_UNCACHED, 0);
|
|
// If we are rendering at a higher resolution, linear is probably best for the dest color.
|
|
renderManager->SetTextureSampler(1, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR, 0.0f);
|
|
fboTexBound_ = true;
|
|
|
|
framebufferManager_->RebindFramebuffer("RebindFramebuffer - ApplyDrawState");
|
|
// Must dirty blend state here so we re-copy next time. Example: Lunar's spell effects.
|
|
dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE;
|
|
gstate_c.Dirty(DIRTY_BLEND_STATE);
|
|
} else if (fboTexBindState == FBO_TEX_READ_FRAMEBUFFER) {
|
|
// No action needed here.
|
|
fboTexBindState = FBO_TEX_NONE;
|
|
}
|
|
dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE;
|
|
gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE);
|
|
} else {
|
|
if (fboTexBound_) {
|
|
GLRenderManager *renderManager = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
|
renderManager->BindTexture(TEX_SLOT_SHADERBLEND_SRC, nullptr);
|
|
fboTexBound_ = false;
|
|
dirtyRequiresRecheck_ |= DIRTY_FRAGMENTSHADER_STATE;
|
|
gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE);
|
|
}
|
|
}
|
|
|
|
if (blendState.blendEnabled) {
|
|
if (blendState.dirtyShaderBlendFixValues) {
|
|
// Not quite sure how necessary this is.
|
|
dirtyRequiresRecheck_ |= DIRTY_SHADERBLEND;
|
|
gstate_c.Dirty(DIRTY_SHADERBLEND);
|
|
}
|
|
if (blendState.useBlendColor) {
|
|
float col[4];
|
|
blendState.blendColorToFloat4(col);
|
|
renderManager->SetBlendFactor(col);
|
|
}
|
|
}
|
|
|
|
int mask = (int)maskState.channelMask;
|
|
if (blendState.blendEnabled) {
|
|
renderManager->SetBlendAndMask(mask, blendState.blendEnabled,
|
|
glBlendFactorLookup[(size_t)blendState.srcColor], glBlendFactorLookup[(size_t)blendState.dstColor],
|
|
glBlendFactorLookup[(size_t)blendState.srcAlpha], glBlendFactorLookup[(size_t)blendState.dstAlpha],
|
|
glBlendEqLookup[(size_t)blendState.eqColor], glBlendEqLookup[(size_t)blendState.eqAlpha]);
|
|
} else {
|
|
renderManager->SetNoBlendAndMask(mask);
|
|
}
|
|
|
|
// TODO: Get rid of the ifdef
|
|
#ifndef USING_GLES2
|
|
if (gstate_c.Use(GPU_USE_LOGIC_OP)) {
|
|
renderManager->SetLogicOp(logicState.logicOpEnabled, logicOps[(int)logicState.logicOp]);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (gstate_c.IsDirty(DIRTY_RASTER_STATE)) {
|
|
// Dither
|
|
bool dither = gstate.isDitherEnabled();
|
|
bool cullEnable;
|
|
GLenum cullMode = cullingMode[gstate.getCullMode() ^ !useBufferedRendering];
|
|
|
|
cullEnable = !gstate.isModeClear() && prim != GE_PRIM_RECTANGLES && prim > GE_PRIM_LINE_STRIP && gstate.isCullEnabled();
|
|
|
|
bool depthClampEnable = false;
|
|
if (gstate.isModeClear() || gstate.isModeThrough()) {
|
|
// TODO: Might happen in clear mode if not through...
|
|
depthClampEnable = false;
|
|
} else {
|
|
if (gstate.getDepthRangeMin() == 0 || gstate.getDepthRangeMax() == 65535) {
|
|
// We get some extra clamping behavior if clipping is enabled.
|
|
depthClampEnable = gstate.isDepthClipEnabled() && gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
|
} else {
|
|
// We just want to clip in this case, the clamp would be clipped anyway.
|
|
depthClampEnable = false;
|
|
}
|
|
}
|
|
|
|
renderManager->SetRaster(cullEnable, GL_CCW, cullMode, dither, depthClampEnable);
|
|
}
|
|
|
|
if (gstate_c.IsDirty(DIRTY_DEPTHSTENCIL_STATE)) {
|
|
ConvertStencilFuncState(stencilState_);
|
|
|
|
if (gstate.isModeClear()) {
|
|
renderManager->SetStencil(
|
|
gstate.isClearModeAlphaMask(), GL_ALWAYS, 0xFF, 0xFF,
|
|
stencilState_.writeMask, GL_REPLACE, GL_REPLACE, GL_REPLACE);
|
|
renderManager->SetDepth(true, gstate.isClearModeDepthMask() ? true : false, GL_ALWAYS);
|
|
} else {
|
|
// Depth Test
|
|
bool depthTestUsed = !IsDepthTestEffectivelyDisabled();
|
|
renderManager->SetDepth(depthTestUsed, gstate.isDepthWriteEnabled(), compareOps[gstate.getDepthTestFunction()]);
|
|
if (depthTestUsed)
|
|
UpdateEverUsedEqualDepth(gstate.getDepthTestFunction());
|
|
|
|
// Stencil Test
|
|
if (stencilState_.enabled) {
|
|
renderManager->SetStencil(
|
|
stencilState_.enabled, compareOps[stencilState_.testFunc], stencilState_.testRef, stencilState_.testMask,
|
|
stencilState_.writeMask, stencilOps[stencilState_.sFail], stencilOps[stencilState_.zFail], stencilOps[stencilState_.zPass]);
|
|
|
|
// Nasty special case for Spongebob and similar where it tries to write zeros to alpha/stencil during
|
|
// depth-fail. We can't write to alpha then because the pixel is killed. However, we can invert the depth
|
|
// test and modify the alpha function...
|
|
if (SpongebobDepthInverseConditions(stencilState_)) {
|
|
renderManager->SetBlendAndMask(0x8, true, GL_ZERO, GL_ZERO, GL_ZERO, GL_ZERO, GL_FUNC_ADD, GL_FUNC_ADD);
|
|
renderManager->SetDepth(true, false, GL_LESS);
|
|
renderManager->SetStencil(true, GL_ALWAYS, 0xFF, 0xFF, 0xFF, GL_ZERO, GL_KEEP, GL_ZERO);
|
|
|
|
dirtyRequiresRecheck_ |= DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE;
|
|
gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE);
|
|
}
|
|
} else {
|
|
renderManager->SetStencilDisabled();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) {
|
|
ConvertViewportAndScissor(
|
|
framebufferManager_->GetDisplayLayoutConfigCopy(),
|
|
useBufferedRendering,
|
|
framebufferManager_->GetRenderWidth(), framebufferManager_->GetRenderHeight(),
|
|
framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(),
|
|
vpAndScissor_);
|
|
|
|
renderManager->SetScissor(GLRect2D{ vpAndScissor_.scissorX, vpAndScissor_.scissorY, vpAndScissor_.scissorW, vpAndScissor_.scissorH });
|
|
renderManager->SetViewport({
|
|
vpAndScissor_.viewportX, vpAndScissor_.viewportY,
|
|
vpAndScissor_.viewportW, vpAndScissor_.viewportH, 0.0f, 1.0f });
|
|
}
|
|
|
|
gstate_c.Clean(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_BLEND_STATE);
|
|
gstate_c.Dirty(dirtyRequiresRecheck_);
|
|
dirtyRequiresRecheck_ = 0;
|
|
}
|
|
|
|
void DrawEngineGLES::ApplyDrawStateLate(bool setStencilValue, int stencilValue) {
|
|
if (setStencilValue) {
|
|
// NOTE: The write mask goes in the writeMask slot, not the enabled slot - see the clear-mode call above.
|
|
render_->SetStencil(true, GL_ALWAYS, stencilValue, 255, stencilState_.writeMask, GL_REPLACE, GL_REPLACE, GL_REPLACE);
|
|
gstate_c.Dirty(DIRTY_DEPTHSTENCIL_STATE); // For the next time.
|
|
}
|
|
|
|
// At this point, we know if the vertices are full alpha or not.
|
|
// TODO: Set the nearest/linear here (since we correctly know if alpha/color tests are needed)?
|
|
if (!gstate.isModeClear() && gstate_c.Use(GPU_USE_FRAGMENT_TEST_CACHE)) {
|
|
// Apply last, once we know the alpha params of the texture.
|
|
if (gstate.isAlphaTestEnabled() || gstate.isColorTestEnabled()) {
|
|
fragmentTestCache_->BindTestTexture(TEX_SLOT_ALPHATEST);
|
|
}
|
|
}
|
|
}
|