New Depth of Field implementation (#1773)

This commit is contained in:
Jasper St. Pierre
2026-05-24 09:41:59 -07:00
committed by GitHub
parent 0504f1dda7
commit af162bbd0a
9 changed files with 183 additions and 36 deletions
+13 -1
View File
@@ -15,6 +15,12 @@ enum class BloomMode : int {
Dusk = 2,
};
enum class DepthOfFieldMode : int {
Off = 0,
Classic = 1,
Dusk = 2,
};
enum class Resampler : int {
Bilinear = 0,
Area = 1,
@@ -58,6 +64,12 @@ struct ConfigEnumRange<BloomMode> {
static constexpr auto max = BloomMode::Dusk;
};
template <>
struct ConfigEnumRange<DepthOfFieldMode> {
static constexpr auto min = DepthOfFieldMode::Off;
static constexpr auto max = DepthOfFieldMode::Dusk;
};
template <>
struct ConfigEnumRange<Resampler> {
static constexpr auto min = Resampler::Bilinear;
@@ -160,13 +172,13 @@ struct UserSettings {
// Graphics
ConfigVar<BloomMode> bloomMode;
ConfigVar<float> bloomMultiplier;
ConfigVar<DepthOfFieldMode> depthOfFieldMode;
ConfigVar<bool> disableWaterRefraction;
ConfigVar<bool> enableTextureReplacements;
ConfigVar<FrameInterpMode> enableFrameInterpolation;
ConfigVar<int> internalResolutionScale;
ConfigVar<int> shadowResolutionMultiplier;
ConfigVar<Resampler> resampler;
ConfigVar<bool> enableDepthOfField;
ConfigVar<bool> enableMapBackground;
ConfigVar<bool> disableCutscenePillarboxing;
+1 -1
View File
@@ -750,7 +750,7 @@ void dMenuMapCommon_c::debugIcon() {
#if TARGET_PC
static constexpr struct {
std::string stagename;
std::string_view stagename;
u8 switch_no;
s8 region_id;
u8 save_id;
+1
View File
@@ -172,6 +172,7 @@ namespace dusk::config {
template class ConfigImpl<f64>;
template class ConfigImpl<std::string>;
template class ConfigImpl<dusk::BloomMode>;
template class ConfigImpl<dusk::DepthOfFieldMode>;
template class ConfigImpl<dusk::DiscVerificationState>;
template class ConfigImpl<dusk::GameLanguage>;
template class ConfigImpl<dusk::GyroMode>;
+2 -2
View File
@@ -60,13 +60,13 @@ UserSettings g_userSettings = {
// Graphics
.bloomMode {"game.bloomMode", BloomMode::Dusk},
.bloomMultiplier {"game.bloomMultiplier", 1.0f},
.depthOfFieldMode{"game.depthOfFieldMode", DepthOfFieldMode::Dusk},
.disableWaterRefraction {"game.disableWaterRefraction", false},
.enableTextureReplacements {"game.enableTextureReplacements", true},
.enableFrameInterpolation {"game.enableFrameInterpolation", FrameInterpMode::Off},
.internalResolutionScale {"game.internalResolutionScale", 0},
.shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1},
.resampler {"game.resampler", Resampler::Bilinear},
.enableDepthOfField {"game.enableDepthOfField", true},
.enableMapBackground {"game.enableMapBackground", true},
.disableCutscenePillarboxing {"game.disableCutscenePillarboxing", false},
@@ -225,12 +225,12 @@ void registerSettings() {
Register(g_userSettings.game.enableDiscordPresence);
Register(g_userSettings.game.bloomMode);
Register(g_userSettings.game.bloomMultiplier);
Register(g_userSettings.game.depthOfFieldMode);
Register(g_userSettings.game.disableWaterRefraction);
Register(g_userSettings.game.enableTextureReplacements);
Register(g_userSettings.game.internalResolutionScale);
Register(g_userSettings.game.resampler);
Register(g_userSettings.game.shadowResolutionMultiplier);
Register(g_userSettings.game.enableDepthOfField);
Register(g_userSettings.game.enableMapBackground);
Register(g_userSettings.game.disableCutscenePillarboxing);
Register(g_userSettings.game.enableFastIronBoots);
+16
View File
@@ -52,6 +52,8 @@ int get_value(GraphicsOption option) {
return std::clamp(
static_cast<int>(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0,
100);
case GraphicsOption::DepthOfFieldMode:
return static_cast<int>(getSettings().game.depthOfFieldMode.getValue());
}
return 0;
}
@@ -85,6 +87,10 @@ void set_value(GraphicsOption option, int value) {
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(std::clamp(
value, static_cast<int>(BloomMode::Off), static_cast<int>(BloomMode::Dusk))));
break;
case GraphicsOption::DepthOfFieldMode:
getSettings().game.depthOfFieldMode.setValue(static_cast<DepthOfFieldMode>(std::clamp(
value, static_cast<int>(DepthOfFieldMode::Off), static_cast<int>(DepthOfFieldMode::Dusk))));
break;
case GraphicsOption::BloomMultiplier:
getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f);
break;
@@ -214,6 +220,16 @@ Rml::String format_graphics_setting_value(GraphicsOption option, int value) {
return "Dusklight";
}
break;
case GraphicsOption::DepthOfFieldMode:
switch (static_cast<DepthOfFieldMode>(value)) {
case DepthOfFieldMode::Off:
return "Off";
case DepthOfFieldMode::Classic:
return "Classic";
case DepthOfFieldMode::Dusk:
return "Dusklight";
}
break;
case GraphicsOption::BloomMultiplier:
return fmt::format("{}%", value);
}
+1
View File
@@ -45,6 +45,7 @@ enum class GraphicsOption {
Resampler,
BloomMode,
BloomMultiplier,
DepthOfFieldMode,
};
Rml::String format_graphics_setting_value(GraphicsOption option, int value);
+2
View File
@@ -14,6 +14,7 @@ void applyPresetClassic() {
auto& s = getSettings();
s.video.lockAspectRatio.setValue(true);
s.game.bloomMode.setValue(BloomMode::Classic);
s.game.depthOfFieldMode.setValue(DepthOfFieldMode::Classic);
s.game.enableAchievementToasts.setValue(false);
s.game.enableControllerToasts.setValue(false);
s.game.internalResolutionScale.setValue(1);
@@ -44,6 +45,7 @@ void applyPresetDusk() {
s.game.enableFrameInterpolation.setValue(FrameInterpMode::Unlimited);
s.game.sunsSong.setValue(true);
s.game.bloomMode.setValue(BloomMode::Dusk);
s.game.depthOfFieldMode.setValue(DepthOfFieldMode::Dusk);
s.game.internalResolutionScale.setValue(0);
s.game.shadowResolutionMultiplier.setValue(4);
s.game.enableGyroAim.setValue(true);
+15 -6
View File
@@ -370,6 +370,9 @@ const Rml::String kBloomHelpText =
"a higher-quality bloom pass.";
const Rml::String kBloomBrightnessHelpText =
"Configure bloom intensity. Higher values make bright areas glow more strongly.";
const Rml::String kDepthOfFieldHelpText =
"Configure the post-processing depth-of-field effect. Classic uses the original depth-of-field pass;"
" Dusklight uses a higher-quality depth-of-field pass.";
const Rml::String kUnlockFramerateHelpText =
"<br/>Uses inter-frame interpolation to enable higher frame rates.<br/><br/>May introduce minor "
"visual artifacts or animation glitches.";
@@ -842,7 +845,18 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
.valueMax = 100,
.defaultValue = 100,
.step = 10,
}, mPrelaunch);
},
mPrelaunch);
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.depthOfFieldMode,
GraphicsTunerProps{
.option = GraphicsOption::DepthOfFieldMode,
.title = "Depth of Field",
.helpText = kDepthOfFieldHelpText,
.valueMin = static_cast<int>(DepthOfFieldMode::Off),
.valueMax = static_cast<int>(DepthOfFieldMode::Dusk),
.defaultValue = static_cast<int>(DepthOfFieldMode::Classic),
},
mPrelaunch);
leftPane.add_section("Rendering");
config_bool_select(leftPane, rightPane, getSettings().game.enableTextureReplacements,
@@ -884,11 +898,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
config_int_select(leftPane, rightPane, getSettings().video.maxFrameRate,
"Framerate Cap", "Limit the framerate to the specified value.", 30, 540, 1,
[] { return getSettings().game.enableFrameInterpolation.getValue() != FrameInterpMode::Capped; });
config_bool_select(leftPane, rightPane, getSettings().game.enableDepthOfField,
{
.key = "Enable Depth of Field",
.helpText = "Render a blurring effect for out-of-focus areas in some situations. May impact performance."
});
config_bool_select(leftPane, rightPane, getSettings().game.enableMapBackground,
{
.key = "Enable Mini-Map Shadows",
+132 -26
View File
@@ -928,6 +928,103 @@ void mDoGph_drawFilterQuad(s8 param_0, s8 param_1) {
GXTexCoord2s8(0, 1);
GXEnd();
}
static void CopyToTexObj(GXTexObj* pDst, uintptr_t texID, u16 dstWidth, u16 dstHeight, GXTexFmt dstFmt = GX_TF_RGBA8) {
GXSetTexCopyDst(dstWidth, dstHeight, dstFmt, FALSE);
GXCopyTex((void*)texID, false);
GXInitTexObj(pDst, (void*)texID, dstWidth, dstHeight, dstFmt, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObjLOD(pDst, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
}
static void drawDepth_blurTex(TGXTexObj &dst) {
u32 hw = u32(JUTVideo::getManager()->getRenderWidth()) >> 1;
u32 hh = u32(JUTVideo::getManager()->getRenderHeight()) >> 1;
Mtx44 ortho;
C_MTXOrtho(ortho, 0.0f, hh, 0.0f, hw, 0.0f, 10.0f);
GXLoadPosMtxImm(cMtx_getIdentity(), GX_PNMTX0);
GXSetProjection(ortho, GX_ORTHOGRAPHIC);
GXSetCurrentMtx(GX_PNMTX0);
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S8, 0);
GXCreateFrameBuffer(hw, hh);
auto divCopySrc = [&](int divNo) {
u32 w = u32(hw) >> divNo, h = u32(hh) >> divNo;
GXSetTexCopySrc(0, 0, w, h);
};
enum { MaxTexNum = 4 };
TGXTexObj tmpTex[MaxTexNum];
auto divCopyTex = [&](uintptr_t texNo, int divNo) -> GXTexObj* {
u32 w = u32(hw) >> divNo, h = u32(hh) >> divNo;
CopyToTexObj(&tmpTex[texNo], texNo, w, h);
return &tmpTex[texNo];
};
auto divQuad = [&](int divNo) {
u32 w = u32(hw) >> divNo, h = u32(hh) >> divNo;
f32 x0 = 0.0f, y0 = 0.0f;
f32 x1 = w, y1 = h;
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
GXPosition3f32(x0, y0, -5);
GXTexCoord2s8(0, 0);
GXPosition3f32(x1, y0, -5);
GXTexCoord2s8(1, 0);
GXPosition3f32(x1, y1, -5);
GXTexCoord2s8(1, 1);
GXPosition3f32(x0, y1, -5);
GXTexCoord2s8(0, 1);
GXEnd();
};
u32 texMtxID = GX_TEXMTX0;
int angle = 0;
float blurScale = 0.003f;
GXSetNumTexGens(8);
GXSetNumTevStages(8);
for (int stage = 0; stage < 8; stage++) {
GXSetTexCoordGen((GXTexCoordID)stage, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID);
mDoMtx_stack_c::transS(
(blurScale * cM_scos(angle)) * mDoGph_gInf_c::getInvScale(), blurScale * cM_ssin(angle), 0.0f);
GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4);
texMtxID += 3;
angle += 0x2000;
GXTevStageID tevStage = (GXTevStageID)stage;
GXSetTevOrder(tevStage, (GXTexCoordID)stage, GX_TEXMAP1, GX_COLOR_NULL);
GXSetTevColorIn(tevStage, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A1, stage == 0 ? GX_CC_ZERO : GX_CC_CPREV);
GXSetTevColorOp(tevStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevAlphaIn(tevStage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
GXSetTevAlphaOp(tevStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
}
GXSetTevColor(GX_TEVREG1, {0, 0, 0, 256 / 8});
// assume the input tex obj is in GX_TEXMAP1
int divNum = 3;
for (int i = 0; i < divNum; i++) {
// Apply blur filter.
divQuad(i);
// Copy to next layer.
divCopySrc(i);
// Set up for the next pass down.
GXTexObj* blurTex = divCopyTex(i, i + 1);
GXLoadTexObj(blurTex, GX_TEXMAP1);
}
// upsample back to half-res buffer 0
divQuad(0);
divCopySrc(0);
CopyToTexObj(&dst, 100, hw, hh);
GXRestoreFrameBuffer();
}
#endif
static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_2) {
@@ -1081,6 +1178,21 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_
}
#endif
#if TARGET_PC
if (dusk::getSettings().game.depthOfFieldMode.getValue() == dusk::DepthOfFieldMode::Off)
return;
if (!(l_tevColor0.a > -255 && sp8 == 1))
return;
TGXTexObj blurTex;
if (dusk::getSettings().game.depthOfFieldMode.getValue() == dusk::DepthOfFieldMode::Dusk)
{
drawDepth_blurTex(blurTex);
GXLoadTexObj(&blurTex, GX_TEXMAP1);
}
#endif
GXSetTevColorS10(GX_TEVREG0, l_tevColor0);
GXSetTevSwapModeTable(GX_TEV_SWAP3, GX_CH_ALPHA, GX_CH_GREEN, GX_CH_BLUE, GX_CH_RED);
GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP3);
@@ -1129,37 +1241,42 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_
param_1->x_orig + param_1->width, 0.0f, 10.0f);
GXLoadPosMtxImm(cMtx_getIdentity(), 0);
#if DEBUG
#if DEBUG
mDoMtx_stack_c::transS(g_kankyoHIO.navy.demo_focus_offset_x, g_kankyoHIO.navy.demo_focus_offset_y, 0.0f);
#else
#else
mDoMtx_stack_c::transS(0.0025f, 0.0025f, 0.0f);
#endif
GXLoadTexMtxImm(mDoMtx_stack_c::get(), 0x1e, GX_MTX2x4);
#endif
GXLoadTexMtxImm(mDoMtx_stack_c::get(), GX_TEXMTX0, GX_MTX2x4);
#if DEBUG
#if DEBUG
mDoMtx_stack_c::transS(-g_kankyoHIO.navy.demo_focus_offset_x, -g_kankyoHIO.navy.demo_focus_offset_y, 0.0f);
#else
#else
mDoMtx_stack_c::transS(-0.0025f, -0.0025f, 0.0f);
#endif
GXLoadTexMtxImm(mDoMtx_stack_c::get(), 0x21, GX_MTX2x4);
#endif
GXLoadTexMtxImm(mDoMtx_stack_c::get(), GX_TEXMTX1, GX_MTX2x4);
GXClearVtxDesc();
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S16, 0);
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_POS_XYZ, GX_S8, 0);
GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, 0x3c);
GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, 0x1e);
GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX0, 0x21);
GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX1);
GXSetNumChans(0);
GXSetNumTexGens(3);
GXSetNumTevStages(4);
GXSetProjection(ortho, GX_ORTHOGRAPHIC);
GXSetCurrentMtx(0);
#ifdef TARGET_PC
if (dusk::getSettings().game.enableDepthOfField)
GXSetProjection(ortho, GX_ORTHOGRAPHIC);
GXSetCurrentMtx(GX_PNMTX0);
#if TARGET_PC
if (dusk::getSettings().game.depthOfFieldMode.getValue() == dusk::DepthOfFieldMode::Dusk) {
GXSetNumTevStages(3);
GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR_NULL);
}
#endif
if (l_tevColor0.a > -255 && sp8 == 1) {
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
GXPosition3s16(x_orig, y_orig_pos, -5);
@@ -1334,19 +1451,8 @@ void mDoGph_gInf_c::bloom_c::remove() {
}
#if TARGET_PC
static void CopyToTexObj(GXTexObj* pDst, uintptr_t texID, u16 dstWidth, u16 dstHeight, GXTexFmt dstFmt = GX_TF_RGBA8) {
GXSetTexCopyDst(dstWidth, dstHeight, dstFmt, FALSE);
GXCopyTex((void*)texID, false);
GXInitTexObj(pDst, (void*)texID, dstWidth, dstHeight, dstFmt, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObjLOD(pDst, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
}
void mDoGph_gInf_c::bloom_c::draw2() {
ZoneScoped;
// if (!dusk::getSettings().game.enableBloom) {
// return;
// }
bool enabled = mEnable;
if (mMonoColor.a == 0 && !enabled)
return;