diff --git a/public/include/remix/remix_c.h b/public/include/remix/remix_c.h index ae7e70eeb..9e045d8ea 100644 --- a/public/include/remix/remix_c.h +++ b/public/include/remix/remix_c.h @@ -424,6 +424,7 @@ extern "C" { REMIXAPI_INSTANCE_CATEGORY_BIT_IGNORE_ALPHA_CHANNEL = 1 << 21, REMIXAPI_INSTANCE_CATEGORY_BIT_IGNORE_TRANSPARENCY_LAYER = 1 << 22, REMIXAPI_INSTANCE_CATEGORY_BIT_PARTICLE_EMITTER = 1 << 23, + REMIXAPI_INSTANCE_CATEGORY_BIT_LEGACY_EMISSIVE = 1 << 24, } remixapi_InstanceCategoryBit; typedef uint32_t remixapi_InstanceCategoryFlags; diff --git a/src/d3d9/d3d9_rtx.cpp b/src/d3d9/d3d9_rtx.cpp index ffb935767..ca85a062d 100644 --- a/src/d3d9/d3d9_rtx.cpp +++ b/src/d3d9/d3d9_rtx.cpp @@ -22,7 +22,7 @@ namespace dxvk { const uint32_t kRenderTargetIndex = 0; #define CATEGORIES_REQUIRE_DRAW_CALL_STATE InstanceCategories::Sky, InstanceCategories::Terrain - #define CATEGORIES_REQUIRE_GEOMETRY_COPY InstanceCategories::Terrain, InstanceCategories::WorldUI + #define CATEGORIES_REQUIRE_GEOMETRY_COPY InstanceCategories::Terrain, InstanceCategories::WorldUI, InstanceCategories::LegacyEmissive D3D9Rtx::D3D9Rtx(D3D9DeviceEx* d3d9Device, bool enableDrawCallConversion) : m_rtStagingData(d3d9Device->GetDXVKDevice(), "RtxStagingDataAlloc: D3D9", (VkMemoryPropertyFlagBits) (VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) diff --git a/src/dxvk/imgui/dxvk_imgui.cpp b/src/dxvk/imgui/dxvk_imgui.cpp index a02b859bb..da56271da 100644 --- a/src/dxvk/imgui/dxvk_imgui.cpp +++ b/src/dxvk/imgui/dxvk_imgui.cpp @@ -166,6 +166,7 @@ namespace dxvk { {"uitextures", "UI Texture", &RtxOptions::uiTexturesObject()}, {"worldspaceuitextures", "World Space UI Texture", &RtxOptions::worldSpaceUiTexturesObject()}, {"worldspaceuibackgroundtextures", "World Space UI Background Texture", &RtxOptions::worldSpaceUiBackgroundTexturesObject()}, + {"legacyemissivetextures", "Legacy Emissive Texture", &RtxOptions::legacyEmissiveTexturesObject()}, {"skytextures", "Sky Texture", &RtxOptions::skyBoxTexturesObject()}, {"ignoretextures", "Ignore Texture (optional)", &RtxOptions::ignoreTexturesObject()}, {"hidetextures", "Hide Texture Instance (optional)", &RtxOptions::hideInstanceTexturesObject()}, @@ -2133,6 +2134,107 @@ namespace dxvk { if (IMGUI_ADD_TOOLTIP(ImGui::Checkbox(rtxOption.displayName, &rtxOption.bufferToggle), rtxOption.textureSetOption->getDescription())) { toggleTextureSelection(texHash, rtxOption.uniqueId, rtxOption.textureSetOption); } + + // Show emissive strength control for Legacy Emissive Texture + if (strcmp(rtxOption.uniqueId, "legacyemissivetextures") == 0 && rtxOption.bufferToggle) { + ImGui::Indent(); + + // Get current intensity for this specific texture + auto legacyEmissiveIntensities = RtxOptions::parseLegacyEmissiveIntensities(RtxOptions::legacyEmissiveIntensitiesString()); + float currentIntensity = 2.0f; // Default intensity + auto intensityIt = legacyEmissiveIntensities.find(texHash); + if (intensityIt != legacyEmissiveIntensities.end()) { + currentIntensity = intensityIt->second; + } + + ImGui::Text("Emissive Strength:"); + ImGui::PushItemWidth(150.0f); + if (ImGui::DragFloat("##emissive_strength", ¤tIntensity, 0.1f, 0.0f, 20.0f, "%.1f")) { + // Update the intensity for this texture + legacyEmissiveIntensities[texHash] = currentIntensity; + std::string intensityString = RtxOptions::legacyEmissiveIntensitiesToString(legacyEmissiveIntensities); + RtxOptions::legacyEmissiveIntensitiesStringObject().setDeferred(intensityString); + } + ImGui::PopItemWidth(); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Controls the brightness of emissive glow for this texture (default: 2.0)"); + } + + // Reset button + ImGui::SameLine(); + if (ImGui::Button("Reset##emissive_reset")) { + legacyEmissiveIntensities.erase(texHash); + std::string intensityString = RtxOptions::legacyEmissiveIntensitiesToString(legacyEmissiveIntensities); + RtxOptions::legacyEmissiveIntensitiesStringObject().setDeferred(intensityString); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reset to default intensity (2.0)"); + } + + // Get current color tint for this specific texture + auto legacyEmissiveColors = RtxOptions::parseLegacyEmissiveColors(RtxOptions::legacyEmissiveColorsString()); + Vector3 currentColor = Vector3(1.0f, 1.0f, 1.0f); // Default white + auto colorIt = legacyEmissiveColors.find(texHash); + if (colorIt != legacyEmissiveColors.end()) { + currentColor = colorIt->second; + } + + // Convert Vector3 to float array for ImGui color picker + float colorArray[3] = { currentColor.x, currentColor.y, currentColor.z }; + + ImGui::Text("Emissive Color Tint:"); + ImGui::PushItemWidth(150.0f); + if (ImGui::ColorEdit3("##emissive_color", colorArray, ImGuiColorEditFlags_NoInputs)) { + // Update the color map + legacyEmissiveColors[texHash] = Vector3(colorArray[0], colorArray[1], colorArray[2]); + + // Convert back to string and save + std::string colorString = RtxOptions::legacyEmissiveColorsToString(legacyEmissiveColors); + RtxOptions::legacyEmissiveColorsStringObject().setDeferred(colorString); + + // DEBUG: Log the color string being saved + char hashStr[32]; + sprintf_s(hashStr, "%016llX", texHash); + Logger::info(str::format("DEBUG ImGui: Setting color tint for hash ", hashStr, " -> color string: ", colorString)); + } + ImGui::PopItemWidth(); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Tints the emissive glow color for this texture (default: white)"); + } + + // Color reset button + ImGui::SameLine(); + if (ImGui::Button("Reset##color_reset")) { + legacyEmissiveColors.erase(texHash); + std::string colorString = RtxOptions::legacyEmissiveColorsToString(legacyEmissiveColors); + RtxOptions::legacyEmissiveColorsStringObject().setDeferred(colorString); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reset to default color (white)"); + } + + // Alpha invert toggle for Legacy Emissive Texture + auto legacyEmissiveAlphaInvert = RtxOptions::parseLegacyEmissiveAlphaInvert(RtxOptions::legacyEmissiveAlphaInvertString()); + bool currentAlphaInvert = legacyEmissiveAlphaInvert.find(texHash) != legacyEmissiveAlphaInvert.end(); + + if (ImGui::Checkbox("Invert Alpha Mask", ¤tAlphaInvert)) { + // Update the alpha invert set + if (currentAlphaInvert) { + legacyEmissiveAlphaInvert.insert(texHash); + } else { + legacyEmissiveAlphaInvert.erase(texHash); + } + std::string invertString = RtxOptions::legacyEmissiveAlphaInvertToString(legacyEmissiveAlphaInvert); + RtxOptions::legacyEmissiveAlphaInvertStringObject().setDeferred(invertString); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("When enabled: black alpha = full emission, white alpha = no emission\nWhen disabled: black alpha = no emission, white alpha = full emission"); + } + + ImGui::Unindent(); + } } ImGui::EndPopup(); return texHash; @@ -2653,6 +2755,7 @@ namespace dxvk { ImGui::Indent(); ImGui::DragFloat("Force Cutout Alpha", &RtxOptions::forceCutoutAlphaObject(), 0.01f, 0.0f, 1.0f, "%.3f", sliderFlags); ImGui::DragFloat("World Space UI Background Offset", &RtxOptions::worldSpaceUiBackgroundOffsetObject(), 0.01f, -FLT_MAX, FLT_MAX, "%.3f", sliderFlags); + ImGui::Checkbox("Ignore last texture stage", &RtxOptions::ignoreLastTextureStageObject()); ImGui::Checkbox("Enable Multiple Stage Texture Factor Blending", &RtxOptions::enableMultiStageTextureFactorBlendingObject()); ImGui::Unindent(); diff --git a/src/dxvk/rtx_render/rtx_instance_manager.cpp b/src/dxvk/rtx_render/rtx_instance_manager.cpp index 3cc5b9b23..a66f0ea99 100644 --- a/src/dxvk/rtx_render/rtx_instance_manager.cpp +++ b/src/dxvk/rtx_render/rtx_instance_manager.cpp @@ -115,6 +115,7 @@ namespace dxvk { , m_isHidden(src.m_isHidden) , m_isPlayerModel(src.m_isPlayerModel) , m_isWorldSpaceUI(src.m_isWorldSpaceUI) + , m_isLegacyEmissive(src.m_isLegacyEmissive) , m_isUnordered(src.m_isUnordered) , m_isObjectToWorldMirrored(src.m_isObjectToWorldMirrored) , m_linkedBlas(src.m_linkedBlas) @@ -395,6 +396,7 @@ namespace dxvk { "Is Hidden: ", m_isHidden ? "true" : "false", "\n", "Is Player Model: ", m_isPlayerModel ? "true" : "false", "\n", "Is World Space UI: ", m_isWorldSpaceUI ? "true" : "false", "\n", + "Is Legacy Emissive: ", m_isLegacyEmissive ? "true" : "false", "\n", "Is Unordered: ", m_isUnordered ? "true" : "false", "\n", "Is Object To World Mirrored: ", m_isObjectToWorldMirrored ? "true" : "false", "\n", "Is Created By Renderer: ", m_isCreatedByRenderer ? "true" : "false", "\n", @@ -587,6 +589,7 @@ namespace dxvk { // Note: Even if the Alpha Test enable flag is set, we consider it disabled if the actual test type is set to always. const bool forceAlphaTest = drawCall.getCategoryFlags().test(InstanceCategories::AlphaBlendToCutout); + const bool alphaTestEnabled = forceAlphaTest || (AlphaTestType)drawCall.getMaterialData().alphaTestCompareOp != AlphaTestType::kAlways; // Note: Use the Opaque Material Data's alpha test state information directly if requested, @@ -994,6 +997,7 @@ namespace dxvk { currentInstance.m_isHidden = currentInstance.testCategoryFlags(InstanceCategories::Hidden); currentInstance.m_isPlayerModel = currentInstance.testCategoryFlags(InstanceCategories::ThirdPersonPlayerModel); currentInstance.m_isWorldSpaceUI = currentInstance.testCategoryFlags(InstanceCategories::WorldUI); + currentInstance.m_isLegacyEmissive = currentInstance.testCategoryFlags(InstanceCategories::LegacyEmissive); // Hide the sky instance since it is not raytraced. // Sky mesh and material are only good for capture and replacement purposes. @@ -1078,6 +1082,70 @@ namespace dxvk { materialData.getOpaqueMaterialData().setEnableEmission(true); materialData.getOpaqueMaterialData().setEmissiveIntensity(2.0f); materialData.getOpaqueMaterialData().setEmissiveColorTexture(materialData.getOpaqueMaterialData().getAlbedoOpacityTexture()); + } else if (currentInstance.m_isLegacyEmissive) { + // For legacy emissive, use alpha channel if available, otherwise use albedo. Per-texture controllable intensity. + // Use the same texture hash that was used to categorize this as legacy emissive (from original material) + const XXH64_hash_t textureHash = drawCall.getMaterialData().getColorTexture().getImageHash(); + + // Get per-texture emissive intensity, default to 2.0f if not specified + float emissiveIntensity = 2.0f; + const auto intensityMap = RtxOptions::parseLegacyEmissiveIntensities(RtxOptions::legacyEmissiveIntensitiesString()); + auto intensityIt = intensityMap.find(textureHash); + if (intensityIt != intensityMap.end()) { + emissiveIntensity = intensityIt->second; + } + + materialData.getOpaqueMaterialData().setEnableEmission(true); + materialData.getOpaqueMaterialData().setEmissiveIntensity(emissiveIntensity); + + // Get per-texture emissive color tint, default to white (1.0, 1.0, 1.0) if not specified + Vector3 emissiveColorTint = Vector3(1.0f, 1.0f, 1.0f); + const auto colorMap = RtxOptions::parseLegacyEmissiveColors(RtxOptions::legacyEmissiveColorsString()); + auto colorIt = colorMap.find(textureHash); + if (colorIt != colorMap.end()) { + emissiveColorTint = colorIt->second; + } + + materialData.getOpaqueMaterialData().setEmissiveColorTint(emissiveColorTint); + + // Check for per-texture alpha invert setting + bool alphaInvert = false; + const auto invertSet = RtxOptions::parseLegacyEmissiveAlphaInvert(RtxOptions::legacyEmissiveAlphaInvertString()); + alphaInvert = invertSet.find(textureHash) != invertSet.end(); + + // Try to use alpha channel for emissive if available, otherwise use albedo + const auto& albedoTexture = materialData.getOpaqueMaterialData().getAlbedoOpacityTexture(); + if (albedoTexture.isValid() && albedoTexture.getImageView() && albedoTexture.getImageView()->info().format != VK_FORMAT_UNDEFINED) { + // Check if texture has alpha channel (includes uncompressed RGBA and compressed formats like BC3/DXT5) + const VkFormat format = albedoTexture.getImageView()->info().format; + const bool hasAlpha = (format == VK_FORMAT_R8G8B8A8_UNORM || + format == VK_FORMAT_R8G8B8A8_SRGB || + format == VK_FORMAT_B8G8R8A8_UNORM || + format == VK_FORMAT_B8G8R8A8_SRGB || + format == VK_FORMAT_A8B8G8R8_UNORM_PACK32 || + format == VK_FORMAT_A8B8G8R8_SRGB_PACK32 || + format == VK_FORMAT_BC3_UNORM_BLOCK || // DXT5 - commonly used by Source engine + format == VK_FORMAT_BC3_SRGB_BLOCK || // DXT5 sRGB variant + format == VK_FORMAT_BC7_UNORM_BLOCK || // BC7 with alpha + format == VK_FORMAT_BC7_SRGB_BLOCK); + + if (hasAlpha) { + // Use the same texture, but the shader will use alpha channel for emissive masking + materialData.getOpaqueMaterialData().setEmissiveColorTexture(albedoTexture); + materialData.getOpaqueMaterialData().setEmissiveAlphaMask(true); + materialData.getOpaqueMaterialData().setEmissiveAlphaInvert(alphaInvert); + } else { + // No alpha channel, use albedo for emissive + materialData.getOpaqueMaterialData().setEmissiveColorTexture(albedoTexture); + materialData.getOpaqueMaterialData().setEmissiveAlphaMask(false); + materialData.getOpaqueMaterialData().setEmissiveAlphaInvert(false); // No invert needed without alpha + } + } else { + // Fallback to albedo if texture is invalid + materialData.getOpaqueMaterialData().setEmissiveColorTexture(materialData.getOpaqueMaterialData().getAlbedoOpacityTexture()); + materialData.getOpaqueMaterialData().setEmissiveAlphaMask(false); + materialData.getOpaqueMaterialData().setEmissiveAlphaInvert(false); // No invert needed without alpha + } } else if (currentInstance.surface.alphaState.emissiveBlend && RtxOptions::enableEmissiveBlendEmissiveOverride() && useLegacyAlphaState) { // If the user has decided to override the legacy alpha state, assume they know what they are doing and allow for explicit emission controls. materialData.getOpaqueMaterialData().setEnableEmission(true); diff --git a/src/dxvk/rtx_render/rtx_instance_manager.h b/src/dxvk/rtx_render/rtx_instance_manager.h index c87ff7996..f02b3566a 100644 --- a/src/dxvk/rtx_render/rtx_instance_manager.h +++ b/src/dxvk/rtx_render/rtx_instance_manager.h @@ -202,6 +202,7 @@ uint32_t getFirstBillboardIndex() const { return m_firstBillboard; } bool m_isHidden = false; bool m_isPlayerModel = false; bool m_isWorldSpaceUI = false; + bool m_isLegacyEmissive = false; bool m_isUnordered = false; bool m_isObjectToWorldMirrored = false; bool m_isCreatedByRenderer = false; diff --git a/src/dxvk/rtx_render/rtx_material_data.h b/src/dxvk/rtx_render/rtx_material_data.h index efda12f4f..27210e408 100644 --- a/src/dxvk/rtx_render/rtx_material_data.h +++ b/src/dxvk/rtx_render/rtx_material_data.h @@ -58,6 +58,9 @@ X(MetallicConstant, metallic_constant, float, 0.f, 1.f, 0.f) \ X(EmissiveColorConstant, emissive_color_constant, Vector3, Vector3(0.f), Vector3(1.f), Vector3(1.0f, 0.1f, 0.1f)) \ X(EnableEmission, enable_emission, bool, false, true, false) \ + X(EmissiveAlphaMask, emissive_alpha_mask, bool, false, true, false) \ + X(EmissiveAlphaInvert, emissive_alpha_invert, bool, false, true, false) \ + X(EmissiveColorTint, emissive_color_tint, Vector3, Vector3(0.f), Vector3(10.f), Vector3(1.0f, 1.0f, 1.0f)) \ X(SpriteSheetRows, sprite_sheet_rows, uint8_t, 0, 255, 0) \ X(SpriteSheetCols, sprite_sheet_cols, uint8_t, 0, 255, 0) \ X(SpriteSheetFPS, sprite_sheet_fps, uint8_t, 0, 255, 0) \ diff --git a/src/dxvk/rtx_render/rtx_materials.h b/src/dxvk/rtx_render/rtx_materials.h index 546d9e16e..f1ca13a53 100644 --- a/src/dxvk/rtx_render/rtx_materials.h +++ b/src/dxvk/rtx_render/rtx_materials.h @@ -499,8 +499,8 @@ struct RtOpaqueSurfaceMaterial { float anisotropy, float emissiveIntensity, const Vector4& albedoOpacityConstant, float roughnessConstant, float metallicConstant, - const Vector3& emissiveColorConstant, bool enableEmission, - bool ignoreAlphaChannel, bool enableThinFilm, bool alphaIsThinFilmThickness, float thinFilmThicknessConstant, + const Vector3& emissiveColorConstant, bool enableEmission, bool emissiveAlphaMask, bool emissiveAlphaInvert, + const Vector3& emissiveColorTint, bool ignoreAlphaChannel, bool enableThinFilm, bool alphaIsThinFilmThickness, float thinFilmThicknessConstant, uint32_t samplerIndex, float displaceIn, float displaceOut, uint32_t subsurfaceMaterialIndex, bool isRaytracedRenderTarget, uint16_t samplerFeedbackStamp @@ -511,12 +511,13 @@ struct RtOpaqueSurfaceMaterial { m_anisotropy{ anisotropy }, m_emissiveIntensity{ emissiveIntensity }, m_albedoOpacityConstant{ albedoOpacityConstant }, m_roughnessConstant{ roughnessConstant }, m_metallicConstant{ metallicConstant }, - m_emissiveColorConstant{ emissiveColorConstant }, m_enableEmission{ enableEmission }, - m_ignoreAlphaChannel { ignoreAlphaChannel }, m_enableThinFilm { enableThinFilm }, m_alphaIsThinFilmThickness { alphaIsThinFilmThickness }, + m_emissiveColorConstant{ emissiveColorConstant }, m_enableEmission{ enableEmission }, m_emissiveAlphaMask{ emissiveAlphaMask }, m_emissiveAlphaInvert{ emissiveAlphaInvert }, + m_emissiveColorTint{ emissiveColorTint }, m_ignoreAlphaChannel { ignoreAlphaChannel }, m_enableThinFilm { enableThinFilm }, m_alphaIsThinFilmThickness { alphaIsThinFilmThickness }, m_thinFilmThicknessConstant { thinFilmThicknessConstant }, m_samplerIndex{ samplerIndex }, m_displaceIn{ displaceIn }, m_displaceOut{ displaceOut }, m_subsurfaceMaterialIndex(subsurfaceMaterialIndex), m_isRaytracedRenderTarget(isRaytracedRenderTarget), m_samplerFeedbackStamp{ samplerFeedbackStamp } { + updateCachedData(); updateCachedHash(); } @@ -548,6 +549,14 @@ struct RtOpaqueSurfaceMaterial { flags |= OPAQUE_SURFACE_MATERIAL_FLAG_IS_RAYTRACED_RENDER_TARGET; } + if (m_emissiveAlphaMask) { + flags |= OPAQUE_SURFACE_MATERIAL_FLAG_EMISSIVE_ALPHA_MASK; + } + + if (m_emissiveAlphaInvert) { + flags |= OPAQUE_SURFACE_MATERIAL_FLAG_EMISSIVE_ALPHA_INVERT; + } + float displaceIn = m_displaceIn * getDisplacementFactor(); float displaceOut = m_displaceOut * getDisplacementFactor(); uint32_t heightTextureIndex = m_heightTextureIndex; @@ -599,11 +608,15 @@ struct RtOpaqueSurfaceMaterial { writeGPUHelper(data, offset, glm::packHalf1x16(m_anisotropy)); writeGPUHelperExplicit<2>(data, offset, m_tangentTextureIndex); - // data[24] + // data[24 - 27] + writeGPUHelper(data, offset, glm::packHalf1x16(m_emissiveColorTint.x)); + writeGPUHelper(data, offset, glm::packHalf1x16(m_emissiveColorTint.y)); + writeGPUHelper(data, offset, glm::packHalf1x16(m_emissiveColorTint.z)); + writeGPUPadding<2>(data, offset); // Padding for tint alignment + + // data[28 - 31] writeGPUHelperExplicit<2>(data, offset, m_samplerFeedbackStamp); - - // data[25 - 31] - writeGPUPadding<14>(data, offset); + writeGPUPadding<6>(data, offset); // Remaining padding to maintain 64-byte total assert(offset - oldOffset == kSurfaceMaterialGPUSize); } @@ -717,6 +730,9 @@ struct RtOpaqueSurfaceMaterial { h = XXH64(&m_metallicConstant, sizeof(m_metallicConstant), h); h = XXH64(&m_emissiveColorConstant, sizeof(m_emissiveColorConstant), h); h = XXH64(&m_enableEmission, sizeof(m_enableEmission), h); + h = XXH64(&m_emissiveAlphaMask, sizeof(m_emissiveAlphaMask), h); + h = XXH64(&m_emissiveAlphaInvert, sizeof(m_emissiveAlphaInvert), h); + h = XXH64(&m_emissiveColorTint, sizeof(m_emissiveColorTint), h); h = XXH64(&m_ignoreAlphaChannel, sizeof(m_ignoreAlphaChannel), h); h = XXH64(&m_enableThinFilm, sizeof(m_enableThinFilm), h); h = XXH64(&m_alphaIsThinFilmThickness, sizeof(m_alphaIsThinFilmThickness), h); @@ -760,6 +776,9 @@ struct RtOpaqueSurfaceMaterial { Vector3 m_emissiveColorConstant; bool m_enableEmission; + bool m_emissiveAlphaMask; + bool m_emissiveAlphaInvert; + Vector3 m_emissiveColorTint; bool m_ignoreAlphaChannel; bool m_enableThinFilm; diff --git a/src/dxvk/rtx_render/rtx_options.h b/src/dxvk/rtx_render/rtx_options.h index fcb913efa..78a281328 100644 --- a/src/dxvk/rtx_render/rtx_options.h +++ b/src/dxvk/rtx_render/rtx_options.h @@ -27,6 +27,7 @@ #include #include "../util/util_keybind.h" +#include "../util/util_string.h" #include "../util/config/config.h" #include "../util/xxHash/xxhash.h" #include "../util/util_math.h" @@ -191,6 +192,22 @@ namespace dxvk { "Hack/workaround option for dynamic world space UI textures with a coplanar background.\n" "Apply to backgrounds if the foreground material is a dynamic world texture rendered in UI that is unpredictable and rapidly changing.\n" "This offsets the background texture backwards."); + RTX_OPTION("rtx", fast_unordered_set, legacyEmissiveTextures, {}, + "Textures on draw calls that should be treated as legacy emissive elements.\n" + "Similar to worldspace UI but uses alpha channel for emissive if available, with per-texture controllable emissive strength."); + RTX_OPTION("rtx", std::string, legacyEmissiveIntensitiesString, "", + "Per-texture emissive intensity values for legacy emissive textures.\n" + "Format: hash1:intensity1,hash2:intensity2,hash3:intensity3\n" + "Default intensity is 2.0f if not specified."); + + RTX_OPTION("rtx", std::string, legacyEmissiveColorsString, "", + "Per-texture emissive color tint values for legacy emissive textures.\n" + "Format: hash1:r,g,b;hash2:r,g,b;hash3:r,g,b\n" + "Default color is white (1.0,1.0,1.0) if not specified."); + RTX_OPTION("rtx", std::string, legacyEmissiveAlphaInvertString, "", + "Per-texture alpha invert flags for legacy emissive textures.\n" + "Format: hash1,hash2,hash3 (only lists hashes that should have inverted alpha)\n" + "When inverted: black alpha = full emission, white alpha = no emission."); RTX_OPTION("rtx", fast_unordered_set, hideInstanceTextures, {}, "Textures on draw calls that should be hidden from rendering, but not totally ignored.\n" "This is similar to rtx.ignoreTextures but instead of completely ignoring such draw calls they are only hidden from rendering, allowing for the hidden objects to still appear in captures.\n" @@ -1375,5 +1392,131 @@ namespace dxvk { return (float(RtxOptions::userBrightness() - 50) / 100.f) * RtxOptions::userBrightnessEVRange(); } + + // Helper functions for Legacy Emissive intensity parsing + static fast_unordered_cache parseLegacyEmissiveIntensities(const std::string& str) { + fast_unordered_cache result; + if (str.empty()) { + return result; + } + + const auto pairs = dxvk::str::split(str, ','); + for (const auto& pair : pairs) { + const auto hashIntensity = dxvk::str::split(pair, ':'); + if (hashIntensity.size() == 2) { + try { + XXH64_hash_t hash = std::stoull(hashIntensity[0], nullptr, 16); + float intensity = std::stof(hashIntensity[1]); + result[hash] = intensity; + } catch (...) { + // Skip invalid entries + } + } + } + return result; + } + + static std::string legacyEmissiveIntensitiesToString(const fast_unordered_cache& cache) { + std::string result; + bool first = true; + for (const auto& pair : cache) { + if (!first) { + result += ","; + } + first = false; + + char hashStr[32]; + snprintf(hashStr, sizeof(hashStr), "%016llX", pair.first); + result += hashStr; + result += ":"; + result += std::to_string(pair.second); + } + return result; + } + + // Helper functions for Legacy Emissive color parsing + static fast_unordered_cache parseLegacyEmissiveColors(const std::string& str) { + fast_unordered_cache result; + if (str.empty()) { + return result; + } + + const auto pairs = dxvk::str::split(str, ';'); + for (const auto& pair : pairs) { + const auto hashColor = dxvk::str::split(pair, ':'); + if (hashColor.size() == 2) { + try { + XXH64_hash_t hash = std::stoull(hashColor[0], nullptr, 16); + const auto colorValues = dxvk::str::split(hashColor[1], ','); + if (colorValues.size() == 3) { + Vector3 color( + std::stof(colorValues[0]), + std::stof(colorValues[1]), + std::stof(colorValues[2]) + ); + result[hash] = color; + } + } catch (...) { + // Skip invalid entries + } + } + } + return result; + } + + static std::string legacyEmissiveColorsToString(const fast_unordered_cache& cache) { + std::string result; + bool first = true; + for (const auto& pair : cache) { + if (!first) { + result += ";"; + } + first = false; + + char hashStr[32]; + snprintf(hashStr, sizeof(hashStr), "%016llX", pair.first); + result += hashStr; + result += ":"; + result += std::to_string(pair.second.x) + "," + + std::to_string(pair.second.y) + "," + + std::to_string(pair.second.z); + } + return result; + } + + // Helper functions for Legacy Emissive alpha invert parsing + static fast_unordered_set parseLegacyEmissiveAlphaInvert(const std::string& str) { + fast_unordered_set result; + if (str.empty()) { + return result; + } + + const auto hashes = dxvk::str::split(str, ','); + for (const auto& hashStr : hashes) { + try { + XXH64_hash_t hash = std::stoull(hashStr, nullptr, 16); + result.insert(hash); + } catch (...) { + // Skip invalid entries + } + } + return result; + } + + static std::string legacyEmissiveAlphaInvertToString(const fast_unordered_set& hashSet) { + std::string result; + bool first = true; + for (const auto& hash : hashSet) { + if (!first) { + result += ","; + } + first = false; + + char hashStr[32]; + snprintf(hashStr, sizeof(hashStr), "%016llX", hash); + result += hashStr; + } + return result; + } }; } diff --git a/src/dxvk/rtx_render/rtx_remix_api.cpp b/src/dxvk/rtx_render/rtx_remix_api.cpp index a939b66f9..3d6ac0fec 100644 --- a/src/dxvk/rtx_render/rtx_remix_api.cpp +++ b/src/dxvk/rtx_render/rtx_remix_api.cpp @@ -263,6 +263,9 @@ namespace { src.getMetallicConstant(), src.getEmissiveColorConstant(), src.getEnableEmission(), + src.getEmissiveAlphaMask(), + src.getEmissiveAlphaInvert(), + src.getEmissiveColorTint(), src.getSpriteSheetRows(), src.getSpriteSheetCols(), src.getSpriteSheetFPS(), @@ -359,6 +362,9 @@ namespace { extOpaque->metallicConstant, tovec3(info.emissiveColorConstant), info.emissiveIntensity > 0.f, + false, // EmissiveAlphaMask - not used for API materials + false, // EmissiveAlphaInvert - not used for API materials + Vector3(1.0f, 1.0f, 1.0f), // EmissiveColorTint - white default for API materials info.spriteSheetRow, info.spriteSheetCol, info.spriteSheetFps, @@ -616,8 +622,9 @@ namespace { if (flags & REMIXAPI_INSTANCE_CATEGORY_BIT_IGNORE_BAKED_LIGHTING ){ result.set(InstanceCategories::IgnoreBakedLighting ); } if (flags & REMIXAPI_INSTANCE_CATEGORY_BIT_IGNORE_TRANSPARENCY_LAYER){ result.set(InstanceCategories::IgnoreTransparencyLayer); } if (flags & REMIXAPI_INSTANCE_CATEGORY_BIT_PARTICLE_EMITTER) { result.set(InstanceCategories::ParticleEmitter); } + if (flags & REMIXAPI_INSTANCE_CATEGORY_BIT_LEGACY_EMISSIVE) { result.set(InstanceCategories::LegacyEmissive); } - static_assert((int)InstanceCategories::Count == 24, "Instance categories changed, please update Remix SDK"); + static_assert((int)InstanceCategories::Count == 25, "Instance categories changed, please update Remix SDK"); return result; } diff --git a/src/dxvk/rtx_render/rtx_scene_manager.cpp b/src/dxvk/rtx_render/rtx_scene_manager.cpp index c6824ef89..73d253663 100644 --- a/src/dxvk/rtx_render/rtx_scene_manager.cpp +++ b/src/dxvk/rtx_render/rtx_scene_manager.cpp @@ -583,7 +583,7 @@ namespace dxvk { const bool highlightUnsafeAnchor = RtxOptions::useHighlightUnsafeAnchorMode() && input.getGeometryData().indexBuffer.defined() && input.getGeometryData().vertexCount > input.getGeometryData().indexCount; if (highlightUnsafeAnchor) { const static MaterialData sHighlightMaterialData(OpaqueMaterialData(TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), - 0.f, 1.f, Vector3(0.2f, 0.2f, 0.2f), 1.0f, 0.1f, 0.1f, Vector3(0.46f, 0.26f, 0.31f), true, 1, 1, 0, false, false, 200.f, true, false, BlendType::kAlpha, false, AlphaTestType::kAlways, 0, 0.0f, 0.0f, Vector3(), 0.0f, Vector3(), 0.0f, false, Vector3(), 0.0f, 0.0f, + 0.f, 1.f, Vector3(0.2f, 0.2f, 0.2f), 1.0f, 0.1f, 0.1f, Vector3(0.46f, 0.26f, 0.31f), true, false, false, Vector3(1.0f, 1.0f, 1.0f), 1, 1, 0, false, false, 200.f, true, false, BlendType::kAlpha, false, AlphaTestType::kAlways, 0, 0.0f, 0.0f, Vector3(), 0.0f, Vector3(), 0.0f, false, Vector3(), 0.0f, 0.0f, lss::Mdl::Filter::Nearest, lss::Mdl::WrapMode::Repeat, lss::Mdl::WrapMode::Repeat)); return sHighlightMaterialData; } @@ -707,7 +707,7 @@ namespace dxvk { } if (highlightUnsafeReplacement) { const static MaterialData sHighlightMaterialData(OpaqueMaterialData(TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), TextureRef(), - 0.f, 1.f, Vector3(0.2f, 0.2f, 0.2f), 1.f, 0.1f, 0.1f, Vector3(1.f, 0.f, 0.f), true, 1, 1, 0, false, false, 200.f, true, false, BlendType::kAlpha, false, AlphaTestType::kAlways, 0, 0.0f, 0.0f, Vector3(), 0.0f, Vector3(), 0.0f, false, Vector3(), 0.0f, 0.0f, + 0.f, 1.f, Vector3(0.2f, 0.2f, 0.2f), 1.f, 0.1f, 0.1f, Vector3(1.f, 0.f, 0.f), true, false, false, Vector3(1.0f, 1.0f, 1.0f), 1, 1, 0, false, false, 200.f, true, false, BlendType::kAlpha, false, AlphaTestType::kAlways, 0, 0.0f, 0.0f, Vector3(), 0.0f, Vector3(), 0.0f, false, Vector3(), 0.0f, 0.0f, lss::Mdl::Filter::Nearest, lss::Mdl::WrapMode::Repeat, lss::Mdl::WrapMode::Repeat)); if ((GlobalTime::get().absoluteTimeMs()) / 200 % 2 == 0) { renderMaterialData = sHighlightMaterialData; @@ -1127,6 +1127,9 @@ namespace dxvk { emissiveIntensity = opaqueMaterialData.getEmissiveIntensity() * RtxOptions::emissiveIntensity(); emissiveColorConstant = opaqueMaterialData.getEmissiveColorConstant(); enableEmissive = opaqueMaterialData.getEnableEmission(); + bool emissiveAlphaMask = opaqueMaterialData.getEmissiveAlphaMask(); + bool emissiveAlphaInvert = opaqueMaterialData.getEmissiveAlphaInvert(); + Vector3 emissiveColorTint = opaqueMaterialData.getEmissiveColorTint(); anisotropy = opaqueMaterialData.getAnisotropyConstant(); thinFilmEnable = opaqueMaterialData.getEnableThinFilm(); @@ -1201,8 +1204,8 @@ namespace dxvk { anisotropy, emissiveIntensity, albedoOpacityConstant, roughnessConstant, metallicConstant, - emissiveColorConstant, enableEmissive, - ignoreAlphaChannel, thinFilmEnable, alphaIsThinFilmThickness, + emissiveColorConstant, enableEmissive, emissiveAlphaMask, emissiveAlphaInvert, + emissiveColorTint, ignoreAlphaChannel, thinFilmEnable, alphaIsThinFilmThickness, thinFilmThicknessConstant, samplerIndex, displaceIn, displaceOut, subsurfaceMaterialIndex, isUsingRaytracedRenderTarget, samplerFeedbackStamp, diff --git a/src/dxvk/rtx_render/rtx_terrain_baker.cpp b/src/dxvk/rtx_render/rtx_terrain_baker.cpp index cffa6f907..5600024de 100644 --- a/src/dxvk/rtx_render/rtx_terrain_baker.cpp +++ b/src/dxvk/rtx_render/rtx_terrain_baker.cpp @@ -654,6 +654,9 @@ namespace dxvk { Material::Properties::metallicConstant(), Material::Properties::emissiveColorConstant(), Material::Properties::enableEmission(), + false, // EmissiveAlphaMask - not used for terrain baking + false, // EmissiveAlphaInvert - not used for terrain baking + Vector3(1.0f, 1.0f, 1.0f), // EmissiveColorTint - white default for terrain baking // Setting expected constant values. Baked terrain should not need to have other values for the below material parameters set 1, 1, 0, /* spriteSheet* */ false, // LegacyMaterialDefaults::enableThinFilm(), diff --git a/src/dxvk/rtx_render/rtx_types.cpp b/src/dxvk/rtx_render/rtx_types.cpp index dca0dc3f9..4501baa94 100644 --- a/src/dxvk/rtx_render/rtx_types.cpp +++ b/src/dxvk/rtx_render/rtx_types.cpp @@ -345,6 +345,7 @@ namespace dxvk { setCategory(InstanceCategories::WorldUI, lookupHash(RtxOptions::worldSpaceUiTextures(), textureHash)); setCategory(InstanceCategories::WorldMatte, lookupHash(RtxOptions::worldSpaceUiBackgroundTextures(), textureHash)); + setCategory(InstanceCategories::LegacyEmissive, lookupHash(RtxOptions::legacyEmissiveTextures(), textureHash)); setCategory(InstanceCategories::Ignore, lookupHash(RtxOptions::ignoreTextures(), textureHash)); setCategory(InstanceCategories::IgnoreLights, lookupHash(RtxOptions::ignoreLights(), textureHash)); diff --git a/src/dxvk/rtx_render/rtx_types.h b/src/dxvk/rtx_render/rtx_types.h index c6e19dbb4..ad4cdba96 100644 --- a/src/dxvk/rtx_render/rtx_types.h +++ b/src/dxvk/rtx_render/rtx_types.h @@ -562,6 +562,7 @@ enum class InstanceCategories : uint32_t { IgnoreBakedLighting, IgnoreTransparencyLayer, ParticleEmitter, + LegacyEmissive, Count, }; diff --git a/src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh b/src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh index 9b151a047..b27c9f74f 100644 --- a/src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh +++ b/src/dxvk/shaders/rtx/concept/surface_material/opaque_surface_material_interaction.slangh @@ -554,6 +554,36 @@ OpaqueSurfaceMaterialInteraction opaqueSurfaceMaterialInteractionCreate( if (emissiveColorLoaded) { emissiveColor = emissiveColorSample.xyz; + + // Apply alpha masking for Legacy Emissive materials + if (opaqueSurfaceMaterial.flags & OPAQUE_SURFACE_MATERIAL_FLAG_EMISSIVE_ALPHA_MASK) + { + // Use alpha channel to modulate emissive intensity + float16_t alphaMask = emissiveColorSample.a; + + // Apply alpha inversion if requested (flips the meaning: black alpha = full emission, white alpha = no emission) + if (opaqueSurfaceMaterial.flags & OPAQUE_SURFACE_MATERIAL_FLAG_EMISSIVE_ALPHA_INVERT) + { + alphaMask = float16_t(1.0) - alphaMask; + } + + // Apply masking with smart energy compensation to prevent reflectivity dominance + // Only apply minimum emissive strength in the "danger zone" where reflectivity would dominate + float16_t emissiveMask; + if (alphaMask < float16_t(0.05)) // Very low alpha - allow true zero emission + { + emissiveMask = float16_t(0.0); + } + else if (alphaMask < float16_t(0.3)) // Danger zone - apply minimum to prevent reflectivity dominance + { + emissiveMask = max(alphaMask, float16_t(0.15)); // Higher minimum in danger zone + } + else // High alpha - use original masking + { + emissiveMask = alphaMask; + } + emissiveColor *= emissiveMask; + } } // Load Albedo/Opacity @@ -680,6 +710,9 @@ OpaqueSurfaceMaterialInteraction opaqueSurfaceMaterialInteractionCreate( // Todo: Disable this for when a sRGB texture is the source of the emissive color. emissiveColor = gammaToLinear(emissiveColor); + // Apply emissive color tint for Legacy Emissive materials (after texture operations) + emissiveColor *= opaqueSurfaceMaterial.emissiveColorTint; + // Apply material modifiers/overrides albedo = saturate(albedo * cb.opaqueMaterialArgs.albedoScale + cb.opaqueMaterialArgs.albedoBias); diff --git a/src/dxvk/shaders/rtx/concept/surface_material/surface_material.h b/src/dxvk/shaders/rtx/concept/surface_material/surface_material.h index 96f5da592..f52fa548d 100644 --- a/src/dxvk/shaders/rtx/concept/surface_material/surface_material.h +++ b/src/dxvk/shaders/rtx/concept/surface_material/surface_material.h @@ -94,7 +94,11 @@ struct OpaqueSurfaceMaterial float16_t anisotropy; uint16_t tangentTextureIndex; - // 24 + // 24-27 + f16vec3 emissiveColorTint; + uint16_t _padding_tint; // Ensure proper alignment + + // 28-31 uint16_t samplerFeedbackStamp; // Todo: Fixed function blend state info here in the future (Actually this should go on a Legacy Material, or some sort of non-PBR Legacy Surface) diff --git a/src/dxvk/shaders/rtx/utility/shared_constants.h b/src/dxvk/shaders/rtx/utility/shared_constants.h index ba5647d16..1d67d85a3 100644 --- a/src/dxvk/shaders/rtx/utility/shared_constants.h +++ b/src/dxvk/shaders/rtx/utility/shared_constants.h @@ -44,6 +44,8 @@ static const uint8_t surfaceMaterialTypeMask = uint8_t(0x3u); #define OPAQUE_SURFACE_MATERIAL_FLAG_IGNORE_ALPHA_CHANNEL (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(2)) #define OPAQUE_SURFACE_MATERIAL_FLAG_IS_RAYTRACED_RENDER_TARGET (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(3)) #define OPAQUE_SURFACE_MATERIAL_FLAG_HAS_DISPLACEMENT (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(4)) +#define OPAQUE_SURFACE_MATERIAL_FLAG_EMISSIVE_ALPHA_MASK (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(5)) +#define OPAQUE_SURFACE_MATERIAL_FLAG_EMISSIVE_ALPHA_INVERT (1 << COMMON_MATERIAL_FLAG_TYPE_OFFSET(6)) #define OPAQUE_SURFACE_MATERIAL_INTERACTION_FLAG_HAS_HEIGHT_TEXTURE (1 << 0) diff --git a/src/lssusd/usd_common.h b/src/lssusd/usd_common.h index dc04792b5..c28ea83b6 100644 --- a/src/lssusd/usd_common.h +++ b/src/lssusd/usd_common.h @@ -3,7 +3,7 @@ namespace dxvk { // Used when readin/writing with Remix USD mods. static const char* getInstanceCategorySubKey(InstanceCategories cat) { - static_assert((uint32_t) InstanceCategories::Count == 24, "Please add/remove the category to the below table."); + static_assert((uint32_t) InstanceCategories::Count == 25, "Please add/remove the category to the below table."); switch (cat) { case InstanceCategories::WorldUI: return "remix_category:world_ui"; @@ -53,6 +53,8 @@ namespace dxvk { return "remix_category:ignore_transparency_layer"; case InstanceCategories::ParticleEmitter: return "remix_category:particle_emitter"; + case InstanceCategories::LegacyEmissive: + return "remix_category:legacy_emissive"; default: Logger::err("Category key name requested, but no category found."); return "";