Skip to content

Commit

Permalink
Enhanced HDR Features for The Town of Light (#58)
Browse files Browse the repository at this point in the history
* thetownoflight: rename gamma adjust shader

* thetownoflight: move tonemap to colorgrade, add final shaders, add peak and paper whitesliders, add grunge shader

* thetownoflight: undo code skip in gamma adjust shader, rename gammaadjust to postprocessandgammaadjust, add links

* thetownoflight: fix inaccurate comments

* thetownoflight: add more comments
  • Loading branch information
mqhaji authored Oct 15, 2024
1 parent 479e598 commit 98cdec2
Show file tree
Hide file tree
Showing 7 changed files with 460 additions and 47 deletions.
5 changes: 5 additions & 0 deletions src/games/thetownoflight/X_Final_0xFFFFFFFD.vs_5_0.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
void main(uint id: SV_VERTEXID, out float4 pos: SV_POSITION, out float2 uv: TEXCOORD0) {
uv.x = (id == 1) ? 2.0 : 0.0;
uv.y = (id == 2) ? 2.0 : 0.0;
pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
}
17 changes: 17 additions & 0 deletions src/games/thetownoflight/X_Final_0xFFFFFFFE.ps_5_0.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "./shared.h"

SamplerState sourceSampler_s : register(s0);
Texture2D<float4> sourceTexture : register(t0);

void main(
float4 vpos: SV_Position,
float2 texcoord: TEXCOORD,
out float4 output: SV_Target0) {
float4 color = sourceTexture.Sample(sourceSampler_s, texcoord.xy);

// Linearize with 2.2 Gamma and scale paper white
float3 linearizedColor = renodx::color::gamma::DecodeSafe(color.rgb, 2.2);
linearizedColor *= injectedData.toneMapUINits / renodx::color::srgb::REFERENCE_WHITE;

output.rgba = float4(linearizedColor, color.a);
}
313 changes: 305 additions & 8 deletions src/games/thetownoflight/addon.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 Filippo Tarpini
* Copyright (C) 2024 Musa Haji
* SPDX-License-Identifier: MIT
*/

Expand All @@ -9,32 +10,329 @@

#include <embed/0x9D6291BC.h>
#include <embed/0xB103EAA6.h>
#include <embed/0xE61B6A3B.h>

#include <chrono>
#include <embed/0xFFFFFFFD.h> // Custom final VS
#include <embed/0xFFFFFFFE.h> // Custom final PS

#include <deps/imgui/imgui.h>
#include <include/reshade.hpp>

#include "../../mods/shader.hpp"
#include "../../mods/swapchain.hpp"
#include "../../utils/settings.hpp"
#include "./shared.h"

namespace {

renodx::mods::shader::CustomShaders custom_shaders = {
CustomShaderEntry(0x9D6291BC), // Color grading LUT + fog + fade
CustomShaderEntry(0xB103EAA6), // Post process (e.g. contrast) + user gamma adjustment (defaulted to 1)
CustomShaderEntry(0xB103EAA6), // Post process and user gamma adjustment (defaulted to 1)
CustomShaderEntry(0xE61B6A3B), // Overlay effect in main menu
};

ShaderInjectData shader_injection;

renodx::utils::settings::Settings settings = {
new renodx::utils::settings::Setting{
.key = "toneMapPeakNits",
.binding = &shader_injection.toneMapPeakNits,
.default_value = 1000.f,
.can_reset = false,
.label = "Peak Brightness",
.section = "Tone Mapping",
.tooltip = "Sets the value of peak white in nits",
.min = 48.f,
.max = 4000.f,
},
new renodx::utils::settings::Setting{
.key = "toneMapGameNits",
.binding = &shader_injection.toneMapGameNits,
.default_value = 203.f,
.can_reset = false,
.label = "Game Brightness",
.section = "Tone Mapping",
.tooltip = "Sets the value of 100% white in nits",
.min = 48.f,
.max = 500.f,
},
new renodx::utils::settings::Setting{
.key = "toneMapUINits",
.binding = &shader_injection.toneMapUINits,
.default_value = 203.f,
.can_reset = false,
.label = "UI Brightness",
.section = "Tone Mapping",
.tooltip = "Sets the brightness of UI and HUD elements in nits",
.min = 48.f,
.max = 500.f,
},
new renodx::utils::settings::Setting{
.value_type = renodx::utils::settings::SettingValueType::BUTTON,
.label = "Discord",
.section = "Links",
.group = "button-line-1",
.tint = 0x5865F2,
.on_change = []() {
system("start https://discord.gg/5WZXDpmbpP");
},
},
new renodx::utils::settings::Setting{
.value_type = renodx::utils::settings::SettingValueType::BUTTON,
.label = "Github",
.section = "Links",
.group = "button-line-1",
.on_change = []() {
system("start https://github.com/clshortfuse/renodx");
},
},
new renodx::utils::settings::Setting{
.value_type = renodx::utils::settings::SettingValueType::BUTTON,
.label = "Buy Pumbo a Coffee",
.section = "Links",
.group = "button-line-1",
.tint = 0xFF5F5F,
.on_change = []() {
system("start https://buymeacoffee.com/realfiloppi");
},
},
};

void OnPresetOff() {
renodx::utils::settings::UpdateSetting("toneMapPeakNits", 203.f);
renodx::utils::settings::UpdateSetting("toneMapGameNits", 203.f);
renodx::utils::settings::UpdateSetting("toneMapUINits", 203.f);
}

} // namespace

extern "C" __declspec(dllexport) constexpr const char* NAME = "RenoDX";
extern "C" __declspec(dllexport) constexpr const char* DESCRIPTION = "RenoDX for The Town of Light";
// NOLINTBEGIN(readability-identifier-naming)

extern "C" __declspec(dllexport) const char* NAME = "RenoDX";
extern "C" __declspec(dllexport) const char* DESCRIPTION = "RenoDX for The Town of Light";

// NOLINTEND(readability-identifier-naming)

// Final shader [ty Ersh/FF14]
struct __declspec(uuid("1228220F-364A-46A2-BB29-1CCE591A018A")) DeviceData {
reshade::api::effect_runtime* main_runtime = nullptr;
std::atomic_bool rendered_effects = false;
std::vector<reshade::api::resource_view> swapchain_rtvs;
reshade::api::pipeline final_pipeline = {};
reshade::api::resource final_texture = {};
reshade::api::resource_view final_texture_view = {};
reshade::api::sampler final_texture_sampler = {};
reshade::api::pipeline_layout final_layout = {};
reshade::api::pipeline_layout copy_layout = {};
};

constexpr reshade::api::pipeline_layout PIPELINE_LAYOUT{0};

void OnInitDevice(reshade::api::device* device) {
auto& data = device->create_private_data<DeviceData>();

// create pipeline
{
std::vector<reshade::api::pipeline_subobject> subobjects;

reshade::api::shader_desc vs_desc = {};
vs_desc.code = _0xFFFFFFFD;
vs_desc.code_size = sizeof(_0xFFFFFFFD);
subobjects.push_back({reshade::api::pipeline_subobject_type::vertex_shader, 1, &vs_desc});

reshade::api::shader_desc ps_desc = {};
ps_desc.code = _0xFFFFFFFE;
ps_desc.code_size = sizeof(_0xFFFFFFFE);
subobjects.push_back({reshade::api::pipeline_subobject_type::pixel_shader, 1, &ps_desc});

reshade::api::format format = reshade::api::format::r16g16b16a16_float;
subobjects.push_back({reshade::api::pipeline_subobject_type::render_target_formats, 1, &format});

uint32_t num_vertices = 3;
subobjects.push_back({reshade::api::pipeline_subobject_type::max_vertex_count, 1, &num_vertices});

auto topology = reshade::api::primitive_topology::triangle_list;
subobjects.push_back({reshade::api::pipeline_subobject_type::primitive_topology, 1, &topology});

reshade::api::blend_desc blend_state = {};
subobjects.push_back({reshade::api::pipeline_subobject_type::blend_state, 1, &blend_state});

reshade::api::rasterizer_desc rasterizer_state = {};
rasterizer_state.cull_mode = reshade::api::cull_mode::none;
subobjects.push_back({reshade::api::pipeline_subobject_type::rasterizer_state, 1, &rasterizer_state});

reshade::api::depth_stencil_desc depth_stencil_state = {};
depth_stencil_state.depth_enable = false;
depth_stencil_state.depth_write_mask = false;
depth_stencil_state.depth_func = reshade::api::compare_op::always;
depth_stencil_state.stencil_enable = false;
depth_stencil_state.front_stencil_read_mask = 0xFF;
depth_stencil_state.front_stencil_write_mask = 0xFF;
depth_stencil_state.front_stencil_func = depth_stencil_state.back_stencil_func;
depth_stencil_state.front_stencil_fail_op = depth_stencil_state.back_stencil_fail_op;
depth_stencil_state.front_stencil_depth_fail_op = depth_stencil_state.back_stencil_depth_fail_op;
depth_stencil_state.front_stencil_pass_op = depth_stencil_state.back_stencil_pass_op;
depth_stencil_state.back_stencil_read_mask = 0xFF;
depth_stencil_state.back_stencil_write_mask = 0xFF;
depth_stencil_state.back_stencil_func = reshade::api::compare_op::always;
depth_stencil_state.back_stencil_fail_op = reshade::api::stencil_op::keep;
depth_stencil_state.back_stencil_depth_fail_op = reshade::api::stencil_op::keep;
depth_stencil_state.back_stencil_pass_op = reshade::api::stencil_op::keep;

subobjects.push_back({reshade::api::pipeline_subobject_type::depth_stencil_state, 1, &depth_stencil_state});

device->create_pipeline(PIPELINE_LAYOUT, static_cast<uint32_t>(subobjects.size()), subobjects.data(), &data.final_pipeline);
}

// create layout
{
reshade::api::pipeline_layout_param new_params;
new_params.type = reshade::api::pipeline_layout_param_type::push_constants;
new_params.push_constants.count = 1;
new_params.push_constants.dx_register_index = 13;
new_params.push_constants.visibility = reshade::api::shader_stage::vertex | reshade::api::shader_stage::pixel | reshade::api::shader_stage::compute;
device->create_pipeline_layout(1, &new_params, &data.final_layout);
}

{
reshade::api::pipeline_layout_param new_params;
new_params.type = reshade::api::pipeline_layout_param_type::push_constants;
new_params.push_constants.count = 1;
new_params.push_constants.dx_register_index = 12;
new_params.push_constants.visibility = reshade::api::shader_stage::vertex | reshade::api::shader_stage::pixel | reshade::api::shader_stage::compute;
device->create_pipeline_layout(1, &new_params, &data.copy_layout);
}
}

void OnDestroyDevice(reshade::api::device* device) {
auto& data = device->get_private_data<DeviceData>();

device->destroy_pipeline(data.final_pipeline);
device->destroy_pipeline_layout(data.final_layout);

device->destroy_private_data<DeviceData>();
}

void OnInitSwapchain(reshade::api::swapchain* swapchain) {
auto device = swapchain->get_device();
auto& data = device->get_private_data<DeviceData>();

for (int i = 0; i < swapchain->get_back_buffer_count(); ++i) {
auto back_buffer_resource = swapchain->get_back_buffer(i);
auto back_buffer_desc = device->get_resource_desc(back_buffer_resource);
auto desc = reshade::api::resource_view_desc(reshade::api::resource_view_type::texture_2d, reshade::api::format_to_default_typed(back_buffer_desc.texture.format), 0, 1, 0, 1);
device->create_resource_view(back_buffer_resource, reshade::api::resource_usage::render_target, desc, &data.swapchain_rtvs.emplace_back());
}

// create copy target
{
auto back_buffer_resource = swapchain->get_back_buffer(0);
auto back_buffer_desc = device->get_resource_desc(back_buffer_resource);
reshade::api::resource_desc desc = {};
desc.type = reshade::api::resource_type::texture_2d;
desc.texture = {
back_buffer_desc.texture.width,
back_buffer_desc.texture.height,
1,
1,
reshade::api::format_to_typeless(back_buffer_desc.texture.format),
1,
};
desc.heap = reshade::api::memory_heap::gpu_only;
desc.usage = reshade::api::resource_usage::copy_dest | reshade::api::resource_usage::shader_resource;
desc.flags = reshade::api::resource_flags::none;
device->create_resource(desc, nullptr, reshade::api::resource_usage::shader_resource, &data.final_texture);
device->create_resource_view(data.final_texture, reshade::api::resource_usage::shader_resource, reshade::api::resource_view_desc(reshade::api::format_to_default_typed(desc.texture.format)), &data.final_texture_view);
device->create_sampler({}, &data.final_texture_sampler);
}
}

void OnDestroySwapchain(reshade::api::swapchain* swapchain) {
auto device = swapchain->get_device();
auto& data = device->get_private_data<DeviceData>();

for (const auto& rtv : data.swapchain_rtvs) {
device->destroy_resource_view(rtv);
}

data.swapchain_rtvs.clear();

device->destroy_sampler(data.final_texture_sampler);
device->destroy_resource_view(data.final_texture_view);
device->destroy_resource(data.final_texture);
}

// more or less the same as what reshade does to render its techniques
void OnPresent(reshade::api::command_queue* queue, reshade::api::swapchain* swapchain, const reshade::api::rect* source_rect, const reshade::api::rect* dest_rect, uint32_t dirty_rect_count, const reshade::api::rect* dirty_rects) {
auto device = queue->get_device();
auto cmd_list = queue->get_immediate_command_list();

auto& data = device->get_private_data<DeviceData>();

auto back_buffer_resource = swapchain->get_current_back_buffer();
auto back_buffer_desc = device->get_resource_desc(back_buffer_resource);

// copy backbuffer
{
const reshade::api::resource resources[2] = {back_buffer_resource, data.final_texture};
const reshade::api::resource_usage state_old[2] = {reshade::api::resource_usage::render_target, reshade::api::resource_usage::shader_resource};
const reshade::api::resource_usage state_new[2] = {reshade::api::resource_usage::copy_source, reshade::api::resource_usage::copy_dest};

cmd_list->barrier(2, resources, state_old, state_new);
cmd_list->copy_texture_region(back_buffer_resource, 0, nullptr, data.final_texture, 0, nullptr);
cmd_list->barrier(2, resources, state_new, state_old);
}

cmd_list->bind_pipeline(reshade::api::pipeline_stage::all_graphics, data.final_pipeline);

cmd_list->barrier(back_buffer_resource, reshade::api::resource_usage::shader_resource, reshade::api::resource_usage::render_target);

reshade::api::render_pass_render_target_desc render_target = {};
render_target.view = data.swapchain_rtvs.at(swapchain->get_current_back_buffer_index());
cmd_list->begin_render_pass(1, &render_target, nullptr);

cmd_list->push_descriptors(reshade::api::shader_stage::all_graphics, PIPELINE_LAYOUT, 0, reshade::api::descriptor_table_update{{}, 0, 0, 1, reshade::api::descriptor_type::texture_shader_resource_view, &data.final_texture_view});
cmd_list->push_descriptors(reshade::api::shader_stage::all_graphics, PIPELINE_LAYOUT, 0, reshade::api::descriptor_table_update{{}, 0, 0, 1, reshade::api::descriptor_type::sampler, &data.final_texture_sampler});

// push the renodx settings
cmd_list->push_constants(reshade::api::shader_stage::all_graphics, data.final_layout, 0, 0, sizeof(shader_injection) / 4, &shader_injection);

const reshade::api::viewport viewport = {
0.0f, 0.0f,
static_cast<float>(back_buffer_desc.texture.width),
static_cast<float>(back_buffer_desc.texture.height),
0.0f, 1.0f};
cmd_list->bind_viewports(0, 1, &viewport);

cmd_list->draw(3, 1, 0, 0);
cmd_list->end_render_pass();

cmd_list->barrier(back_buffer_resource, reshade::api::resource_usage::render_target, reshade::api::resource_usage::shader_resource);

// reset the copy tracker, entirely unrelated to the final shader above
// track_next_copy = false; // We dont need this, just pasted from FF14 code
// shader_injection.copyTracker = 0; // We dont need this, just pasted from FF14 code
}

BOOL APIENTRY DllMain(HMODULE h_module, DWORD fdw_reason, LPVOID lpv_reserved) {
switch (fdw_reason) {
case DLL_PROCESS_ATTACH:
if (!reshade::register_addon(h_module)) return FALSE;

renodx::mods::shader::expected_constant_buffer_index = 13;

// renodx::mods::shader::force_pipeline_cloning = true;
// renodx::mods::shader::trace_unmodified_shaders = true;
renodx::mods::swapchain::force_borderless = false;
// renodx::mods::swapchain::prevent_full_screen = true;

// Final shader
reshade::register_event<reshade::addon_event::init_device>(OnInitDevice);
reshade::register_event<reshade::addon_event::destroy_device>(OnDestroyDevice);
reshade::register_event<reshade::addon_event::init_swapchain>(OnInitSwapchain);
reshade::register_event<reshade::addon_event::destroy_swapchain>(OnDestroySwapchain);
reshade::register_event<reshade::addon_event::present>(OnPresent);

renodx::mods::swapchain::swap_chain_upgrade_targets.push_back({
.old_format = reshade::api::format::r8g8b8a8_unorm,
.new_format = reshade::api::format::r16g16b16a16_float,
Expand Down Expand Up @@ -63,17 +361,16 @@ BOOL APIENTRY DllMain(HMODULE h_module, DWORD fdw_reason, LPVOID lpv_reserved) {
#endif

break;

case DLL_PROCESS_DETACH:
reshade::unregister_addon(h_module);
break;
}

// TODO(Filoppi): add user shader settings for tonemapping (paper white, peak brightness), and allow selecting between sRGB vs 2.2 gamma
// TODO(Filoppi): add a final shader pass that does linearization, at the moment the mod requires an external ReShade shader to be linearized (with gamma 2.2) and for paper white scaling (80->203 nits)
renodx::utils::settings::Use(fdw_reason, &settings, &OnPresetOff);

renodx::mods::swapchain::Use(fdw_reason);
renodx::mods::shader::Use(fdw_reason, custom_shaders);

renodx::mods::shader::Use(fdw_reason, custom_shaders, &shader_injection);

return TRUE;
}
Loading

0 comments on commit 98cdec2

Please sign in to comment.