diff --git a/README-CN.md b/README-CN.md index d74968f..9d9ad46 100644 --- a/README-CN.md +++ b/README-CN.md @@ -80,12 +80,30 @@ git clone https://github.com/Minecraft-Radiance/MCVR.git 最后,用`./gradlew build`构建。 +# 画面 / 图形设置怎么打开 + +Radiance 目前的游戏内设置入口在 Minecraft 的视频设置里: + +1. 打开 **选项** +2. 打开 **视频设置** +3. 找到 **Pipeline** 分类 +4. 点击 **Radiance Settings** + +进入后就是 Radiance 的统一设置界面,可以调整画面、光照、云、水体、DLSS/HDR、材质和后处理。 + +# 设置文档 + +当前所有模块设置的中文参考文档见: + +- [`docs/SETTINGS_REFERENCE.zh-CN.md`](docs/SETTINGS_REFERENCE.zh-CN.md) + # Todo列表 - [ ] 移植到更多版本和mod加载器(WIP,最高优先级) - [x] XESS支持 -- [ ] 帧生成 -- [ ] HDR +- [ ] 实验性帧生成(NVIDIA + Windows + Vulkan,进行中) +- [x] HDR(实验性 scRGB 输出) +- [ ] 第一人称手的影子会显示在手持物品上 以及更多... diff --git a/README.md b/README.md index 5e9deea..f0e991d 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,29 @@ Use `cmake` to build it and install it. Please refer to [this](https://github.co Finally, build with `./gradlew build`. +# Opening the graphics/settings screen + +Radiance's in-game settings are currently opened from Minecraft's video settings: + +1. Open **Options** +2. Open **Video Settings** +3. Find the **Pipeline** category +4. Click **Radiance Settings** + +This opens the unified Radiance settings screen for graphics, lighting, clouds, water, DLSS/HDR, materials, and post-processing. + +# Settings Reference + +A Chinese settings reference covering all current module options is available at: + +- [`docs/SETTINGS_REFERENCE.zh-CN.md`](docs/SETTINGS_REFERENCE.zh-CN.md) + # TODO List - [ ] port to more versions and mod loaders (WIP, first priority) - [x] XESS support -- [ ] Frame Generation -- [ ] HDR +- [ ] Experimental Frame Generation (NVIDIA + Windows + Vulkan, WIP) +- [x] HDR (Experimental scRGB output) And more... diff --git a/build.gradle b/build.gradle index fa93c0f..ac96d36 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,91 @@ base { archivesName = project.archives_base_name } +def radianceRuntimeDirPath = providers.gradleProperty("radianceRuntimeDir") + .orElse(providers.environmentVariable("RADIANCE_RUNTIME_DIR")) + .orNull +def radianceRuntimeDir = radianceRuntimeDirPath ? file(radianceRuntimeDirPath) : null +def builtWindowsCoreDll = file("external/MCVR/build/src/core/Release/core.dll") +def radianceWindowsJarPath = providers.gradleProperty("radianceWindowsJar") + .orElse(providers.environmentVariable("RADIANCE_WINDOWS_JAR")) + .orNull +def radianceWindowsJar = radianceWindowsJarPath ? file(radianceWindowsJarPath) : null +def extractedRadianceWindowsRuntimeDir = layout.buildDirectory.dir("generated/radianceRuntime/windows") +def syncedBuiltWindowsRuntimeDir = layout.buildDirectory.dir("generated/radianceRuntime/localBuild") +def bundledXessRuntimeDir = file("external/MCVR/extern/xess/bin") +def radianceAllowIncompleteRuntimePackaging = providers + .gradleProperty("radianceAllowIncompleteRuntimePackaging") + .map { it.toBoolean() } + .orElse(false) +def radianceRequestedTasks = gradle.startParameter.taskNames.collect { it.toLowerCase() } +def radiancePackagingTaskRequested = radianceRequestedTasks.any { taskName -> + taskName == "build" + || taskName == "assemble" + || taskName.contains("jar") + || taskName.contains("publish") + || taskName.contains("remap") +} + +def radianceWindowsJarExplicitlyConfigured = + gradle.startParameter.projectProperties.containsKey("radianceWindowsJar") + || System.getenv("RADIANCE_WINDOWS_JAR") != null + +tasks.register("extractRadianceWindowsRuntime") { + onlyIf { + radianceWindowsJar != null && radianceWindowsJar.exists() + } + if (radianceWindowsJar != null) { + inputs.file(radianceWindowsJar) + } + outputs.dir(extractedRadianceWindowsRuntimeDir) + + doLast { + if (radianceWindowsJar == null || !radianceWindowsJar.exists()) { + throw new GradleException("Radiance Windows runtime jar not found: ${radianceWindowsJarPath}") + } + + def outputDir = extractedRadianceWindowsRuntimeDir.get().asFile + delete(outputDir) + outputDir.mkdirs() + + copy { + from(zipTree(radianceWindowsJar)) + include "core.dll" + include "nvngx_*.dll" + include "libxess*.dll" + include "sl.*.dll" + include "NvLowLatencyVk.dll" + into outputDir + } + + def extractedCoreDll = new File(outputDir, "core.dll") + if (!extractedCoreDll.exists()) { + throw new GradleException("core.dll was not found in ${radianceWindowsJar}") + } + + logger.lifecycle("Extracted Radiance Windows runtime from ${radianceWindowsJar} -> ${outputDir}") + } +} + +tasks.register("syncBuiltWindowsRuntime") { + onlyIf { + builtWindowsCoreDll.exists() + } + inputs.file(builtWindowsCoreDll) + outputs.dir(syncedBuiltWindowsRuntimeDir) + + doLast { + def destinationDir = syncedBuiltWindowsRuntimeDir.get().asFile + delete(destinationDir) + destinationDir.mkdirs() + copy { + from(builtWindowsCoreDll) + into(destinationDir) + rename { "core.dll" } + } + } +} + compileJava { options.compilerArgs += ['-h', "${projectDir}/src/main/native/include"] outputs.upToDateWhen { false } @@ -49,8 +134,8 @@ dependencies { modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" // https://mvnrepository.com/artifact/org.yaml/snakeyaml - implementation "org.yaml:snakeyaml:2.5" - include "org.yaml:snakeyaml:2.5" + implementation "org.yaml:snakeyaml:2.6" + include "org.yaml:snakeyaml:2.6" } processResources { @@ -58,6 +143,88 @@ processResources { inputs.property "minecraft_version", project.minecraft_version inputs.property "loader_version", project.loader_version filteringCharset "UTF-8" + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + def runtimeDirectorySource = null + def supplementalWindowsRuntimeSource = null + def mainResourcesDir = file("src/main/resources") + if (radianceRuntimeDir != null) { + if (radianceRuntimeDir.exists()) { + runtimeDirectorySource = radianceRuntimeDir + if (!new File(radianceRuntimeDir, "core.dll").exists() && builtWindowsCoreDll.exists()) { + dependsOn(tasks.named("syncBuiltWindowsRuntime")) + supplementalWindowsRuntimeSource = syncedBuiltWindowsRuntimeDir + } + } else { + logger.warn("Configured radiance runtime directory does not exist: ${radianceRuntimeDir}") + } + } else if (radianceWindowsJar != null && radianceWindowsJar.exists()) { + dependsOn(tasks.named("extractRadianceWindowsRuntime")) + runtimeDirectorySource = extractedRadianceWindowsRuntimeDir + } else if (builtWindowsCoreDll.exists()) { + dependsOn(tasks.named("syncBuiltWindowsRuntime")) + supplementalWindowsRuntimeSource = syncedBuiltWindowsRuntimeDir + } else if (radianceWindowsJarExplicitlyConfigured && radianceWindowsJar != null + && !radianceWindowsJar.exists()) { + logger.warn("Configured Radiance Windows jar does not exist: ${radianceWindowsJar}") + } + + boolean packagedWindowsCoreAvailable = + new File(mainResourcesDir, "core.dll").exists() || builtWindowsCoreDll.exists() + boolean packagedLinuxCoreAvailable = new File(mainResourcesDir, "libcore.so").exists() + + if (radianceRuntimeDir != null && radianceRuntimeDir.exists()) { + packagedWindowsCoreAvailable |= new File(radianceRuntimeDir, "core.dll").exists() + packagedLinuxCoreAvailable |= new File(radianceRuntimeDir, "libcore.so").exists() + } else if (radianceWindowsJar != null && radianceWindowsJar.exists()) { + packagedWindowsCoreAvailable = true + } + + if (!packagedWindowsCoreAvailable || !packagedLinuxCoreAvailable) { + def missingRuntimeTargets = [] + if (!packagedWindowsCoreAvailable) { + missingRuntimeTargets << "Windows (core.dll)" + } + if (!packagedLinuxCoreAvailable) { + missingRuntimeTargets << "Linux (libcore.so)" + } + + def incompleteRuntimeMessage = ( + "Radiance runtime packaging is incomplete: missing " + + missingRuntimeTargets.join(", ") + + ". Provide the missing runtime via src/main/resources or -PradianceRuntimeDir=..., " + + "or pass -PradianceAllowIncompleteRuntimePackaging=true to package a platform-limited artifact intentionally." + ) + if (radiancePackagingTaskRequested && !radianceAllowIncompleteRuntimePackaging.get()) { + throw new GradleException(incompleteRuntimeMessage) + } + logger.warn(incompleteRuntimeMessage) + } + + if (runtimeDirectorySource != null) { + from(runtimeDirectorySource) { + include "core.dll" + include "libcore.so" + include "nvngx_*.dll" + include "libxess*.dll" + include "libxell.dll" + include "sl.*.dll" + include "NvLowLatencyVk.dll" + } + } + + if (supplementalWindowsRuntimeSource != null) { + from(supplementalWindowsRuntimeSource) { + include "core.dll" + } + } + + if (bundledXessRuntimeDir.exists()) { + from(bundledXessRuntimeDir) { + include "libxess*.dll" + include "libxell.dll" + } + } filesMatching("fabric.mod.json") { expand "version": project.version, diff --git a/docs/SETTINGS_REFERENCE.zh-CN.md b/docs/SETTINGS_REFERENCE.zh-CN.md new file mode 100644 index 0000000..eb5b61d --- /dev/null +++ b/docs/SETTINGS_REFERENCE.zh-CN.md @@ -0,0 +1,207 @@ +# Radiance 设置参考(中文) + +> 本文档覆盖 `src/main/resources/modules/*.yaml` 中当前暴露的全部模块设置。 +> 默认值以仓库当前版本为准;如果后续 YAML 变更,请同步更新本文档。 + +## 设置入口 + +游戏内打开路径: + +- **选项** +- **视频设置** +- **Pipeline** +- **Radiance Settings** + +进入后即可打开 Radiance 的统一画面设置界面。 + +## 阅读方式 + +- **默认值**:模块 YAML 里的当前默认值。 +- **作用**:这个设置主要控制什么。 +- **调整建议**:什么时候该调高 / 调低,或者推荐保持默认。 + +--- + +## 视频设置:画质等级 + +> 位置:**视频设置 -> Pipeline -> 画质等级** + +`画质等级` 会联动调整**整条渲染管线**,而不是只改一两个数字。 +当前会覆盖这些模块 / 参数: + +- **Ray Tracing**:反射次数、远景几何、远景材质、地形 meshing、merge span、GI 模式、反射材质分级、更新间隔、PBR 采样、SHaRC、基础辐亮度、直接/间接光 +- **NRD**:历史长度、预模糊半径、最大模糊半径、命中距离重建、anti-firefly +- **Upscaler**:FSR/XeSS/DLSS 质量档,FSR 锐化 +- **Tone Mapping**:曲线、自动曝光、测光模式、饱和度、白点 + +### 档位说明 + +| 档位 | 定位 | 说明 | +|---|---|---| +| `performance` | 优先帧率 | 远景简化更激进,但保留 **2 次** 反射,避免环境完全发黑。 | +| `balanced` | 默认推荐 | 当前默认档;兼顾画质、稳定性和帧数。 | +| `quality` | 优先画质 | 远景保真、反射与 GI 更完整,但 GPU / CPU 压力明显更高。 | + +--- + +## DLSS 模块 + +| 设置 | 默认值 | 作用 | 调整建议 | +|---|---:|---|---| +| `mode` | `balanced` | 控制 DLSS 质量档位。 | 帧率不够用 `performance`;画质优先 `quality`;原生抗锯齿可用 `dlaa`。 | + +--- + +## FSR Upscaler 模块 + +| 设置 | 默认值 | 作用 | 调整建议 | +|---|---:|---|---| +| `enable` | `true` | 是否启用 FSR3 Upscaler。 | 出问题先关掉排查。 | +| `quality_mode` | `balanced` | 控制内部渲染分辨率与最终输出分辨率的比例。 | 低端机器可降到 `balanced / performance`。 | +| `sharpness` | `0.7` | 锐化强度。 | 过高会有振铃/边缘发硬;过低会发糊。 | + +--- + +## NRD 模块 + +> NRD 主要是降噪稳定性、拖影控制、历史长度、预模糊半径一类参数。 +> 如果你不知道怎么调,**优先保持默认**。 + +| 设置 | 默认值 | 作用 | 调整建议 | +|---|---:|---|---| +| `antilag_luminance_sigma_scale` | `4.0` | 亮度变化触发 anti-lag 的阈值尺度。 | 亮度闪烁明显时可略调低;误触发太多可调高。 | +| `antilag_luminance_sensitivity` | `3.0` | 亮度变化敏感度。 | 过高更容易重置历史,过低更容易拖影。 | +| `responsive_accumulation_roughness_threshold` | `0.0` | 粗糙度到达该阈值后更偏向响应式累积。 | 反射拖影明显时可略提高。 | +| `responsive_accumulation_min_accumulated_frame_num` | `3` | 响应式累积启动前需要的最少历史帧数。 | 动态场景多时可略减。 | +| `max_accumulated_frame_num` | `60` | 最大历史累积帧数。 | 调高更稳但更容易拖;调低更灵敏但更噪。 | +| `max_fast_accumulated_frame_num` | `3` | 快速路径历史上限。 | 过高容易残影。 | +| `max_stabilized_frame_num` | `63` | 稳定阶段历史长度上限。 | 静态画面噪点多时可调高。 | +| `history_fix_frame_num` | `3` | 历史修正使用的帧数。 | 一般保持默认。 | +| `history_fix_base_pixel_stride` | `14` | 历史修正基础像素步长。 | 越大越便宜,但修正更粗。 | +| `history_fix_alternate_pixel_stride` | `14` | 历史修正交替步长。 | 通常与上项一起调。 | +| `fast_history_clamping_sigma_scale` | `1.5` | 快速历史夹取强度。 | 拖影重可调低,闪烁多可调高。 | +| `diffuse_prepass_blur_radius` | `30.0` | 漫反射预模糊半径。 | 噪声大可调高;细节损失大可调低。 | +| `specular_prepass_blur_radius` | `50.0` | 镜面预模糊半径。 | 反射噪点多可调高;反射发糊可调低。 | +| `min_hit_distance_weight` | `0.1` | 命中距离参与权重的最小值。 | 一般保持默认。 | +| `min_blur_radius` | `1.0` | 最小模糊半径。 | 想保细节可略降。 | +| `max_blur_radius` | `100.0` | 最大模糊半径。 | 想压噪可提高;不想糊可降低。 | +| `lobe_angle_fraction` | `0.15` | 用于镜面 lobe 相似度判定。 | 主要影响反射稳定性。 | +| `roughness_fraction` | `0.15` | 粗糙度相似性权重。 | 反射面变化快时可略调。 | +| `plane_distance_sensitivity` | `0.02` | 平面距离差异敏感度。 | 边缘漏光/串面时可调高。 | +| `specular_probability_thresholds_for_mv_modification_min` | `0.5` | 修改运动矢量时的镜面概率下限。 | 一般成对与 max 一起调。 | +| `specular_probability_thresholds_for_mv_modification_max` | `0.9` | 修改运动矢量时的镜面概率上限。 | 一般保持默认。 | +| `firefly_suppressor_min_relative_scale` | `2.0` | Firefly 抑制阈值。 | 亮点/火花多可调低。 | +| `min_material_for_diffuse` | `4.0` | 进入漫反射处理的最小材质阈值。 | 高级调参,通常不动。 | +| `min_material_for_specular` | `4.0` | 进入镜面处理的最小材质阈值。 | 高级调参,通常不动。 | +| `checkerboard_mode` | `off` | 棋盘式输入模式。 | 如果输入不是棋盘渲染,保持 `off`。 | +| `enable_anti_firefly` | `true` | 是否启用 Firefly 抑制。 | 出现亮点闪烁时不要关。 | +| `hit_distance_reconstruction_mode` | `5x5` | 命中距离重建卷积核。 | 画面更稳但更糊:`5x5`;更锐利但可能更噪:`3x3 / off`。 | + +--- + +## Post Render 模块 + +| 设置 | 默认值 | 作用 | 调整建议 | +|---|---:|---|---| +| `star_count` | `3000` | 星空粒子数量。 | 太多会增加后处理开销。 | +| `star_min_size` | `0.5` | 星星最小尺寸。 | 调太低会不明显。 | +| `star_max_size` | `0.7` | 星星最大尺寸。 | 调太高会显得假。 | +| `star_radius` | `400.0` | 星空分布半径。 | 影响视觉空间感,性能影响较小。 | + +--- + +## Ray Tracing 模块 + +> 这是当前最核心的一组设置。 +> 如果目标是“**尽量稳**”,推荐从默认值开始,只调下面几项: +> +> - `far_field_start_distance_chunks` +> - `far_field_material_mode` +> - `terrain_update_interval_frames` +> - `entity_update_interval_frames` +> - `num_ray_bounces` +> - `pbr_sampling_mode` + +| 设置 | 默认值 | 作用 | 调整建议 | +|---|---:|---|---| +| `shader_pack_path` | `""` | 自定义光追 shader pack 路径;为空时使用内置包。 | 只有在自定义 shader pack 时填写。 | +| `world_representation_mode` | `triangle_blas` | 世界主要表示方式:传统三角形 BLAS 或 Chunk AABB。 | 目前稳定优先用 `triangle_blas`。 | +| `chunk_traversal_mode` | `triangle_hit` | Chunk 内求交/遍历模式。 | 目前稳定优先用 `triangle_hit`。 | +| `chunk_data_layout` | `triangle_geometry` | Chunk 数据组织形式。 | 当前 native 主路径优先用 `triangle_geometry`。 | +| `chunk_macrocell_size` | `disabled` | 宏块加速尺寸。 | 实验性,非 Route-B 调试时建议关闭。 | +| `terrain_meshing_mode` | `legacy_quads` | 地形面片预处理模式。 | 当前默认回退到稳定的 `legacy_quads`;实验性优化可手动试 `coplanar_merge / greedy_meshing`。 | +| `greedy_merge_max_span` | `16` | Greedy/共面合并的最大跨度。 | 只有启用合并模式时才明显生效。 | +| `blas_inclusion_mode` | `opaque_and_shadow` | 哪些几何进入 BLAS。 | 稳定和性能都较均衡,推荐保持默认。 | +| `glass_path_mode` | `special_path` | 玻璃走 BLAS、特殊路径或排除。 | 玻璃表现异常时可切回 `blas`。 | +| `foliage_path_mode` | `blas` | 树叶/植物等 cutout 几何走哪条路径。 | 当前默认回退到 `blas`,优先保证稳定和完整显示。 | +| `decoration_path_mode` | `blas` | 装饰物几何走哪条路径。 | 当前默认回退到 `blas`,优先保证稳定。 | +| `far_field_geometry_mode` | `simplified_shell` | 远景几何简化方式。 | 远处块很多时默认就很值;若想保真可改 `exact_chunks`。 | +| `far_field_start_distance_chunks` | `24` | 从多少个 chunk 之外开始按“远景”处理。 | 性能不够就减小;远景细节太差就增大。 | +| `far_field_material_mode` | `flat_surface` | 远景材质是完整 PBR 还是扁平平面材质。 | 追求性能建议 `flat_surface`;追求一致性用 `full_pbr`。 | +| `reflection_ray_material_mode` | `water_glass_metal` | 哪些材质值得打反射光线。 | 低成本推荐默认;全材质反射更贵。 | +| `diffuse_gi_mode` | `low_cost_hybrid` | 漫反射 GI 的预算模型。 | 默认更适合游戏运行;画质极限再用 `full_ray_tracing`。 | +| `separate_entity_terrain_accel_structures` | `true` | 实体和地形是否分离更新/加速结构。 | 推荐开启。 | +| `terrain_update_interval_frames` | `8` | 地形重建最小帧间隔。 | 卡 CPU 可调高;更新不及时可调低。 | +| `entity_update_interval_frames` | `1` | 实体重放缓存的刷新帧间隔。 | 卡顿时可适当调高,但会引入位置滞后。 | +| `block_entity_update_interval_frames` | `1` | 方块实体重放缓存刷新间隔。 | 动态方块实体异常时不要调太高。 | +| `particle_update_interval_frames` | `1` | 粒子重放缓存刷新间隔。 | 性能优先可调高。 | +| `num_ray_bounces` | `2` | 光线反弹次数。 | 性能不够时优先降到 `2`;极限压性能才建议 `1`。 | +| `use_jitter` | `true` | 是否启用抖动采样。 | 关闭更稳定但更容易锯齿/噪声固定。 | +| `pbr_sampling_mode` | `bilinear` | PBR 贴图采样模式。 | `nearest` 更快更锐;`bilinear` 更平滑。 | +| `transparent_split_mode` | `deterministic` | 透明路径分裂策略。 | 排查不稳定行为时优先 `deterministic`。 | +| `direct_light_strength` | `1.0` | 直接光贡献强度。 | 过亮就降,过暗就升。 | +| `indirect_light_strength` | `16.0` | 间接光贡献强度。 | 场景偏灰/过曝时优先调整这里。 | +| `basic_radiance` | `5.0` | 基础辐亮度底噪。 | 太低画面死黑;太高会发灰。 | +| `atmosphere_planet_radius` | `6360.0` | 大气模型的行星半径。 | 通常不改。 | +| `atmosphere_top_radius` | `6460.0` | 大气顶高度。 | 通常不改。 | +| `rayleigh_scale_height` | `8.0` | 瑞利散射尺度高度。 | 影响天空过渡和远景蓝雾。 | +| `mie_scale_height` | `1.2` | 米氏散射尺度高度。 | 影响雾感、太阳附近泛白。 | +| `rayleigh_scattering_coefficient` | `0.000005802,0.000013558,0.0000331` | 瑞利散射系数 RGB。 | 高级调参,不熟悉建议别动。 | +| `mie_anisotropy` | `0.8` | 米氏散射各向异性。 | 越高越偏向前向散射。 | +| `mie_scattering_coefficient` | `0.000021,0.000021,0.000021` | 米氏散射系数 RGB。 | 高级调参。 | +| `minimum_view_cosine` | `0.02` | 天体/大气相关的最小视角余弦。 | 主要影响天体大小/可见范围。 | +| `sun_radiance` | `8.0,8.0,8.0` | 太阳辐亮度。 | 太阳太刺眼时降低。 | +| `moon_radiance` | `0.24,0.3,0.6` | 月亮辐亮度。 | 夜景太暗/太蓝可微调。 | +| `use_sharc` | `true` | 是否启用 SHaRC。 | 新 shader pack 或异常时可关掉排查。 | +| `sharc_debug_mode` | `off` | SHaRC 调试输出模式。 | 只有调试时才开。 | + +--- + +## Tone Mapping 模块 + +| 设置 | 默认值 | 作用 | 调整建议 | +|---|---:|---|---| +| `method` | `pbr_neutral` | 色调映射曲线。 | 默认最中性;想电影感可试 `aces / uncharted2`。 | +| `middle_grey` | `0.18` | 自动曝光的中灰目标。 | 画面整体偏亮/偏暗时可微调。 | +| `exposure_up_speed` | `8.0` | 变亮时曝光调整速度。 | 越高适应越快。 | +| `exposure_down_speed` | `8.0` | 变暗时曝光调整速度。 | 越高适应越快。 | +| `log2_luminance_min` | `-12.0` | 自动曝光统计下限。 | 极暗场景可略降。 | +| `log2_luminance_max` | `4.0` | 自动曝光统计上限。 | 极亮场景可略升。 | +| `low_percent` | `0.005` | 直方图低百分位截断。 | 可降低暗部异常值影响。 | +| `high_percent` | `0.99` | 直方图高百分位截断。 | 可降低高光异常值影响。 | +| `min_exposure` | `0.01` | 自动曝光最小值。 | 防止过暗。 | +| `max_exposure` | `2.0` | 自动曝光最大值。 | 防止过曝。 | +| `enable_auto_exposure` | `true` | 是否启用自动曝光。 | 想固定亮度时关闭。 | +| `exposure_metering_mode` | `center` | 曝光测光模式。 | 主体在中间时用 `center`;整体场景用 `global`。 | +| `center_metering_percent` | `20.0` | 中心测光区域占比。 | 越小越聚焦中央。 | +| `manual_exposure` | `1.0` | 关闭自动曝光时的手动曝光值。 | 仅在 auto exposure 关闭时生效。 | +| `exposure_bias` | `0.0` | 曝光偏移。 | 比大改曲线更适合做整体微调。 | +| `white_point` | `32.0` | 某些曲线的白点。 | 高光压缩不舒服时可调。 | +| `saturation` | `1.0` | 饱和度。 | 想更淡/更艳可调。 | +| `clamp_output` | `true` | 是否钳制输出范围。 | 正常建议开启。 | + +--- + +## XeSS 模块 + +| 设置 | 默认值 | 作用 | 调整建议 | +|---|---:|---|---| +| `enable` | `true` | 是否启用 XeSS。 | 平台不兼容时关闭。 | +| `quality_mode` | `balanced` | XeSS 质量档位。 | 低端机可降到 `balanced / performance`。 | +| `pre_exposure` | `1.0` | XeSS 使用的预曝光值。 | 只有在曝光链不稳定时才需要改。 | + +--- + +## Temporal Accumulation 模块 + +当前版本 **没有暴露可调设置**。 diff --git a/gradle.properties b/gradle.properties index 90f5058..e0cf47c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ -# Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx1G +# Keep the Gradle daemon modest so constrained Windows sessions can still start it. +org.gradle.jvmargs=-Xmx512m -Dfile.encoding=UTF-8 # Fabric Properties # check these on https://modmuss50.me/fabric.html minecraft_version=1.21.4 @@ -7,7 +7,7 @@ yarn_mappings=1.21.4+build.8 loader_version=0.18.3 loom_version=1.14-SNAPSHOT # Mod Properties -mod_version=0.1.4-alpha +mod_version=0.1.6-beta maven_group=com.radiance archives_base_name=Radiance # Dependencies diff --git a/scripts/inspect_radiance_runtime.ps1 b/scripts/inspect_radiance_runtime.ps1 new file mode 100644 index 0000000..1622334 --- /dev/null +++ b/scripts/inspect_radiance_runtime.ps1 @@ -0,0 +1,166 @@ +param( + [string]$JarPath = "C:\Users\Felix\Downloads\Radiance-0.1.3-alpha-fabric-1.21.4-windows.jar", + [string]$CoreDllPath +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +Add-Type -AssemblyName System.IO.Compression.FileSystem + +function Get-AsciiStrings { + param( + [byte[]]$Bytes, + [int]$MinLength = 4 + ) + + $results = New-Object System.Collections.Generic.List[string] + $builder = New-Object System.Text.StringBuilder + + foreach ($byteValue in $Bytes) { + if (($byteValue -ge 32 -and $byteValue -le 126) -or $byteValue -eq 9) { + [void]$builder.Append([char]$byteValue) + } else { + if ($builder.Length -ge $MinLength) { + $results.Add($builder.ToString()) + } + [void]$builder.Clear() + } + } + + if ($builder.Length -ge $MinLength) { + $results.Add($builder.ToString()) + } + + return $results +} + +function Get-ZipEntryBytes { + param( + [System.IO.Compression.ZipArchive]$Archive, + [string]$EntryName + ) + + $entry = $Archive.Entries | Where-Object { $_.FullName -eq $EntryName } | Select-Object -First 1 + if ($null -eq $entry) { + return $null + } + + $stream = $entry.Open() + try { + $memoryStream = New-Object System.IO.MemoryStream + $stream.CopyTo($memoryStream) + return $memoryStream.ToArray() + } finally { + $stream.Dispose() + } +} + +if (-not $CoreDllPath -and -not (Test-Path $JarPath)) { + throw "Jar not found: $JarPath" +} + +$coreDllBytes = $null +$shaderEntryMap = [ordered]@{} +$jar = $null + +if ($CoreDllPath) { + if (-not (Test-Path $CoreDllPath)) { + throw "core.dll path not found: $CoreDllPath" + } + $coreDllBytes = [System.IO.File]::ReadAllBytes((Resolve-Path $CoreDllPath)) +} + +if (Test-Path $JarPath) { + $resolvedJarPath = (Resolve-Path $JarPath) + $jar = [System.IO.Compression.ZipFile]::OpenRead($resolvedJarPath) + try { + if ($null -eq $coreDllBytes) { + $coreDllBytes = Get-ZipEntryBytes -Archive $jar -EntryName "core.dll" + } + + foreach ($entry in $jar.Entries) { + if ($entry.FullName -like "shaders/world/ray_tracing/*.spv") { + $shaderEntryMap[$entry.FullName] = Get-ZipEntryBytes -Archive $jar -EntryName $entry.FullName + } + } + } finally { + $jar.Dispose() + } +} + +if ($null -eq $coreDllBytes) { + throw "Unable to locate core.dll bytes from jar or explicit path." +} + +$optionPatterns = [ordered]@{ + "world_representation_mode" = @("world_representation_mode", "chunk_aabb") + "chunk_traversal_mode" = @("chunk_traversal_mode", "voxel_dda", "brick", "macrocell") + "chunk_data_layout" = @("chunk_data_layout", "occupancy_bitmask", "occupancy_palette", "face_mask") + "chunk_macrocell_size" = @("chunk_macrocell_size", "macrocell") + "diffuse_gi_mode" = @("diffuse_gi_mode", "radiance_cache", "low_cost_hybrid", "probe", "sharc") + "num_ray_bounces" = @("num_ray_bounces", "numRayBounces", "rayBounces") + "use_jitter" = @("use_jitter", "cameraJitter", "unjitteredPixelCenter") +} + +$coreStrings = Get-AsciiStrings -Bytes $coreDllBytes -MinLength 4 +$coreSha256 = [System.BitConverter]::ToString( + [System.Security.Cryptography.SHA256]::HashData($coreDllBytes) +).Replace("-", "").ToLowerInvariant() + +Write-Host "=== core.dll ===" +Write-Host "size : $($coreDllBytes.Length)" +Write-Host "sha256 : $coreSha256" +Write-Host "" + +Write-Host "=== core.dll option evidence ===" +foreach ($kvp in $optionPatterns.GetEnumerator()) { + $hits = New-Object System.Collections.Generic.List[string] + foreach ($needle in $kvp.Value) { + foreach ($candidate in $coreStrings) { + if ($candidate -like "*$needle*" -and -not $hits.Contains($candidate)) { + $hits.Add($candidate) + } + } + } + + if ($hits.Count -eq 0) { + Write-Host ("- {0}: no direct core.dll string evidence" -f $kvp.Key) + } else { + Write-Host ("- {0}:" -f $kvp.Key) + foreach ($hit in $hits | Select-Object -First 6) { + Write-Host (" {0}" -f $hit) + } + } +} + +Write-Host "" +Write-Host "=== shader option evidence (world/ray_tracing) ===" +foreach ($kvp in $optionPatterns.GetEnumerator()) { + $shaderHits = New-Object System.Collections.Generic.List[string] + foreach ($shaderEntry in $shaderEntryMap.GetEnumerator()) { + $shaderStrings = Get-AsciiStrings -Bytes $shaderEntry.Value -MinLength 4 + $matched = $false + foreach ($needle in $kvp.Value) { + if ($matched) { + break + } + foreach ($candidate in $shaderStrings) { + if ($candidate -like "*$needle*") { + $shaderHits.Add("$($shaderEntry.Key) :: $candidate") + $matched = $true + break + } + } + } + } + + if ($shaderHits.Count -eq 0) { + Write-Host ("- {0}: no ray-tracing shader string evidence" -f $kvp.Key) + } else { + Write-Host ("- {0}:" -f $kvp.Key) + foreach ($hit in $shaderHits | Select-Object -First 8) { + Write-Host (" {0}" -f $hit) + } + } +} diff --git a/src/main/java/com/radiance/client/RadianceClient.java b/src/main/java/com/radiance/client/RadianceClient.java index fed8c42..ea011df 100644 --- a/src/main/java/com/radiance/client/RadianceClient.java +++ b/src/main/java/com/radiance/client/RadianceClient.java @@ -1,6 +1,8 @@ package com.radiance.client; import com.mojang.logging.LogUtils; +import com.radiance.client.util.LightSourceRegistry; +import com.radiance.client.util.MaterialToolkit; import com.radiance.client.option.Options; import com.radiance.client.pipeline.Pipeline; import com.radiance.client.proxy.vulkan.RendererProxy; @@ -44,30 +46,52 @@ public void onInitializeClient() { // core lib String osName = System.getProperty("os.name"); if (osName.toLowerCase().contains("windows")) { - Path libTargetPath = radianceDir.resolve("core.lib"); - Path libResourcePath = Path.of("core.lib"); - copyFileFromResource(libTargetPath, libResourcePath); - Path dllTargetPath = radianceDir.resolve("core.dll"); Path dllResourcePath = Path.of("core.dll"); copyFileFromResource(dllTargetPath, dllResourcePath); + Path ngxDlssPath = radianceDir.resolve("nvngx_dlss.dll"); + Path ngxDlssdPath = radianceDir.resolve("nvngx_dlssd.dll"); + Path ngxDlssgPath = radianceDir.resolve("nvngx_dlssg.dll"); Path xessPath = radianceDir.resolve("libxess.dll"); Path xessDx11Path = radianceDir.resolve("libxess_dx11.dll"); Path xessFgPath = radianceDir.resolve("libxess_fg.dll"); + copyOptionalFileFromResource(ngxDlssPath, Path.of("nvngx_dlss.dll")); + copyOptionalFileFromResource(ngxDlssdPath, Path.of("nvngx_dlssd.dll")); + copyOptionalFileFromResource(ngxDlssgPath, Path.of("nvngx_dlssg.dll")); copyOptionalFileFromResource(xessPath, Path.of("libxess.dll")); // currently not used, can be used later for fg copyOptionalFileFromResource(xessDx11Path, Path.of("libxess_dx11.dll")); copyOptionalFileFromResource(xessFgPath, Path.of("libxess_fg.dll")); + copyOptionalFileFromResource(radianceDir.resolve("sl.interposer.dll"), + Path.of("sl.interposer.dll")); + copyOptionalFileFromResource(radianceDir.resolve("sl.common.dll"), + Path.of("sl.common.dll")); + copyOptionalFileFromResource(radianceDir.resolve("sl.reflex.dll"), + Path.of("sl.reflex.dll")); + copyOptionalFileFromResource(radianceDir.resolve("sl.pcl.dll"), + Path.of("sl.pcl.dll")); + copyOptionalFileFromResource(radianceDir.resolve("NvLowLatencyVk.dll"), + Path.of("NvLowLatencyVk.dll")); loadOptionalLibrary(xessPath); - System.load(dllTargetPath.toAbsolutePath().toString()); + try { + System.load(dllTargetPath.toAbsolutePath().toString()); + } catch (UnsatisfiedLinkError e) { + throw new RuntimeException("Failed to load core library from: " + dllTargetPath + + ". This may indicate missing dependencies or an incompatible platform.", e); + } } else if (osName.toLowerCase().contains("linux")) { Path soTargetPath = radianceDir.resolve("libcore.so"); Path soResourcePath = Path.of("libcore.so"); copyFileFromResource(soTargetPath, soResourcePath); - System.load(soTargetPath.toAbsolutePath().toString()); + try { + System.load(soTargetPath.toAbsolutePath().toString()); + } catch (UnsatisfiedLinkError e) { + throw new RuntimeException("Failed to load core library from: " + soTargetPath + + ". This may indicate missing dependencies or an incompatible platform.", e); + } } else { throw new RuntimeException("The OS " + osName + " is not supported"); } @@ -75,15 +99,17 @@ public void onInitializeClient() { // shaders Path shaderTargetPath = radianceDir.resolve("shaders"); Path shaderResourcePath = Path.of("shaders"); - copyFolderFromResource(shaderTargetPath, shaderResourcePath); + copyFolderFromResource(shaderTargetPath, shaderResourcePath, true); // modules Path moduleTargetPath = radianceDir.resolve("modules"); Path moduleResourcePath = Path.of("modules"); - copyFolderFromResource(moduleTargetPath, moduleResourcePath); + copyFolderFromResource(moduleTargetPath, moduleResourcePath, true); RendererProxy.initFolderPath(radianceDir.toAbsolutePath().toString()); Pipeline.initFolderPath(radianceDir); + MaterialToolkit.init(radianceDir); + LightSourceRegistry.init(radianceDir); Options.readOptions(); @@ -93,7 +119,13 @@ public void onInitializeClient() { public void copyFileFromResource(Path targetPath, Path resourcePath) { try (InputStream is = getClass().getResourceAsStream(toResourcePath(resourcePath))) { if (is == null) { - throw new IOException("Cannot find target path: " + resourcePath); + if (Files.exists(targetPath)) { + LOGGER.warn("Resource {} not found in jar, using existing file {}", resourcePath, + targetPath.toAbsolutePath()); + return; + } + throw new IOException("Required runtime file is missing from both jar and disk. Resource: " + + resourcePath + ", expected existing file: " + targetPath.toAbsolutePath()); } Files.createDirectories(targetPath.getParent()); @@ -129,11 +161,21 @@ public String toResourcePath(Path path) { } public void copyFolderFromResource(Path targetPath, Path resourcePath) { + copyFolderFromResource(targetPath, resourcePath, false); + } + + public void copyFolderFromResource(Path targetPath, Path resourcePath, boolean preserveExisting) { String resourcePathStr = toResourcePath(resourcePath); URL url = getClass().getResource(resourcePathStr); if (url == null) { - throw new RuntimeException("Resource folder not found: " + resourcePathStr); + if (Files.isDirectory(targetPath)) { + LOGGER.warn("Resource folder {} not found in jar, using existing directory {}", + resourcePathStr, targetPath.toAbsolutePath()); + return; + } + throw new RuntimeException("Required runtime folder is missing from both jar and disk. Resource: " + + resourcePathStr + ", expected existing directory: " + targetPath.toAbsolutePath()); } try { @@ -155,30 +197,35 @@ public void copyFolderFromResource(Path targetPath, Path resourcePath) { } Path root = fs.getPath(resourcePathStr); - walkAndCopy(root, targetPath, resourcePath); + walkAndCopy(root, targetPath, resourcePath, preserveExisting); } finally { if (created) { try { fs.close(); - } catch (IOException ignored) { + } catch (IOException e) { + LOGGER.warn("Failed to close JAR filesystem", e); } } } } else { Path root = Paths.get(uri); - walkAndCopy(root, targetPath, resourcePath); + walkAndCopy(root, targetPath, resourcePath, preserveExisting); } } catch (URISyntaxException | IOException e) { throw new RuntimeException("Failed to copy resource folder", e); } } - private void walkAndCopy(Path walkRoot, Path targetRoot, Path baseResourcePath) + private void walkAndCopy(Path walkRoot, Path targetRoot, Path baseResourcePath, + boolean preserveExisting) throws IOException { try (Stream stream = Files.walk(walkRoot)) { stream.filter(Files::isRegularFile).forEach(source -> { String relativePathStr = walkRoot.relativize(source).toString(); Path targetFile = targetRoot.resolve(relativePathStr); + if (preserveExisting && Files.exists(targetFile)) { + return; + } Path childResourcePath = baseResourcePath.resolve(relativePathStr); copyFileFromResource(targetFile, childResourcePath); }); diff --git a/src/main/java/com/radiance/client/constant/VulkanConstants.java b/src/main/java/com/radiance/client/constant/VulkanConstants.java index 7d19c82..fff2377 100644 --- a/src/main/java/com/radiance/client/constant/VulkanConstants.java +++ b/src/main/java/com/radiance/client/constant/VulkanConstants.java @@ -53,20 +53,30 @@ public String getName() { return name; } + public VkFormat toUnorm() { + return switch (this) { + case VK_FORMAT_R8_SRGB -> VK_FORMAT_R8_UNORM; + case VK_FORMAT_R8G8_SRGB -> VK_FORMAT_R8G8_UNORM; + case VK_FORMAT_R8G8B8_SRGB -> VK_FORMAT_R8G8B8_UNORM; + case VK_FORMAT_R8G8B8A8_SRGB -> VK_FORMAT_R8G8B8A8_UNORM; + default -> this; + }; + } + public NativeImage.InternalFormat getNativeImageInternalFormat() { return switch (this) { - case VK_FORMAT_R8_UNORM -> NativeImage.InternalFormat.RED; - case VK_FORMAT_R8G8_UNORM -> NativeImage.InternalFormat.RG; - case VK_FORMAT_R8G8B8_UNORM -> NativeImage.InternalFormat.RGB; - case VK_FORMAT_R8G8B8A8_UNORM -> NativeImage.InternalFormat.RGBA; + case VK_FORMAT_R8_UNORM, VK_FORMAT_R8_SRGB -> NativeImage.InternalFormat.RED; + case VK_FORMAT_R8G8_UNORM, VK_FORMAT_R8G8_SRGB -> NativeImage.InternalFormat.RG; + case VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8_SRGB -> NativeImage.InternalFormat.RGB; + case VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB -> NativeImage.InternalFormat.RGBA; default -> throw new IllegalStateException("Unexpected value: " + this.value); }; } public NativeImage.Format getNativeImageFormat() { return switch (this) { - case VK_FORMAT_R8G8B8_UNORM -> NativeImage.Format.RGB; - case VK_FORMAT_R8G8B8A8_UNORM -> NativeImage.Format.RGBA; + case VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_R8G8B8_SRGB -> NativeImage.Format.RGB; + case VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB -> NativeImage.Format.RGBA; default -> throw new IllegalStateException("Unexpected value: " + this.value); }; } diff --git a/src/main/java/com/radiance/client/gui/AttributeWidgetUtil.java b/src/main/java/com/radiance/client/gui/AttributeWidgetUtil.java index 7bf932e..d64d8b0 100644 --- a/src/main/java/com/radiance/client/gui/AttributeWidgetUtil.java +++ b/src/main/java/com/radiance/client/gui/AttributeWidgetUtil.java @@ -5,8 +5,10 @@ import java.util.List; import java.util.Locale; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.client.gui.widget.SliderWidget; @@ -61,28 +63,55 @@ static void layoutWidgets(List widgets, int x, int y, int singl ClickableWidget w = widgets.get(0); w.setX(x); w.setY(y); - w.setWidth(singleWidth); + if (w.getWidth() <= 0) { + w.setWidth(singleWidth); + } return; } - if (widgets.size() == 3) { + if (widgets.size() == 3 && widgets.stream().allMatch( + widget -> widget instanceof TextFieldWidget)) { for (int i = 0; i < 3; i++) { ClickableWidget cw = widgets.get(i); cw.setX(x + i * (tripleWidth + gap)); cw.setY(y); cw.setWidth(tripleWidth); } + return; + } + + int cursorX = x; + for (ClickableWidget widget : widgets) { + int preferredWidth = widget.getWidth() > 0 ? widget.getWidth() : singleWidth; + widget.setX(cursorX); + widget.setY(y); + widget.setWidth(preferredWidth); + cursorX += preferredWidth + gap; } } static int totalWidgetWidth(List widgets, int singleWidth, int tripleWidth, int gap) { - if (widgets.size() == 3) { + if (widgets.size() == 3 && widgets.stream().allMatch( + widget -> widget instanceof TextFieldWidget)) { return (tripleWidth * 3) + (gap * 2); } - return singleWidth; + if (widgets.size() == 1) { + ClickableWidget widget = widgets.get(0); + return widget.getWidth() > 0 ? widget.getWidth() : singleWidth; + } + + int total = 0; + for (int i = 0; i < widgets.size(); i++) { + ClickableWidget widget = widgets.get(i); + total += widget.getWidth() > 0 ? widget.getWidth() : singleWidth; + if (i + 1 < widgets.size()) { + total += gap; + } + } + return total; } - static List buildWidgets(AttributeConfig cfg, TextRenderer textRenderer, + static List buildWidgets(AttributeConfig cfg, Screen owner, TextRenderer textRenderer, int width, int vec3ComponentWidth) { String type = cfg.type == null ? "" : cfg.type.toLowerCase(Locale.ROOT); @@ -92,11 +121,11 @@ static List buildWidgets(AttributeConfig cfg, TextRenderer text } if (type.startsWith("int_range:")) { - return List.of(buildIntRange(cfg, cfg.type.substring(10), width)); + return buildIntRange(cfg, owner, cfg.type.substring(10), width); } if (type.startsWith("float_range:")) { - return List.of(buildFloatRange(cfg, cfg.type.substring(12), width)); + return buildFloatRange(cfg, owner, cfg.type.substring(12), width); } return switch (type) { @@ -114,7 +143,7 @@ private static ClickableWidget buildBoolWidget(AttributeConfig cfg, int width) { return ButtonWidget.builder( Text.translatable(b ? "render_pipeline.true" : "render_pipeline.false"), btn -> { boolean nv = !"render_pipeline.true".equalsIgnoreCase(cfg.value); - cfg.value = nv ? "render_pipeline.true" : "render_pipeline.false"; + setValue(cfg, nv ? "render_pipeline.true" : "render_pipeline.false"); btn.setMessage(Text.translatable(cfg.value)); }).dimensions(0, 0, width, 20).build(); } @@ -136,7 +165,7 @@ private static ClickableWidget buildEnumWidget(AttributeConfig cfg, String raw, int[] index = new int[]{idx}; return ButtonWidget.builder(Text.translatable(values[index[0]]), btn -> { index[0] = (index[0] + 1) % values.length; - cfg.value = values[index[0]]; + setValue(cfg, values[index[0]]); btn.setMessage(Text.translatable(cfg.value)); }).dimensions(0, 0, width, 20).build(); } @@ -149,7 +178,7 @@ private static ClickableWidget buildIntWidget(AttributeConfig cfg, TextRenderer tf.setTextPredicate(s -> s.isEmpty() || s.equals("-") || s.matches("-?\\d+")); tf.setChangedListener(text -> { if (isStrictInt(text)) { - cfg.value = text; + setValue(cfg, text); } }); return tf; @@ -166,7 +195,7 @@ private static ClickableWidget buildFloatWidget(AttributeConfig cfg, TextRendere || s.matches("-?\\d+\\.") || s.matches("-?\\d*\\.\\d+")); tf.setChangedListener(text -> { if (isStrictFloat(text)) { - cfg.value = text; + setValue(cfg, text); } }); return tf; @@ -177,7 +206,7 @@ private static ClickableWidget buildStringWidget(AttributeConfig cfg, TextRender TextFieldWidget tf = new TextFieldWidget(textRenderer, 0, 0, width, 20, Text.empty()); tf.setMaxLength(128); tf.setText(cfg.value == null ? "" : cfg.value); - tf.setChangedListener(text -> cfg.value = text); + tf.setChangedListener(text -> setValue(cfg, text)); return tf; } @@ -199,7 +228,7 @@ private static List buildVec3Widget(AttributeConfig cfg, String sz = z.getText(); if (isStrictFloat(sx) && isStrictFloat(sy) && isStrictFloat(sz)) { - cfg.value = sx + "," + sy + "," + sz; + setValue(cfg, sx + "," + sy + "," + sz); } }; @@ -222,7 +251,8 @@ private static TextFieldWidget vecField(TextRenderer textRenderer, float v, int return tf; } - private static ClickableWidget buildIntRange(AttributeConfig cfg, String raw, int width) { + private static List buildIntRange(AttributeConfig cfg, Screen owner, String raw, + int width) { Range r = parseRange(raw); int start = (int) r.start; int end = (int) r.end; @@ -231,21 +261,39 @@ private static ClickableWidget buildIntRange(AttributeConfig cfg, String raw, in start = end; end = t; } + final int minInt = start; + final int maxInt = end; - int cur = start; + int cur = minInt; if (isInt(cfg.value)) { cur = Integer.parseInt(cfg.value); } else { - cfg.value = String.valueOf(start); + setValue(cfg, String.valueOf(minInt)); } - cur = MathHelper.clamp(cur, start, end); + cur = MathHelper.clamp(cur, minInt, maxInt); - IntRangeSlider slider = new IntRangeSlider(0, 0, width, 20, start, end, cur, cfg); + int sliderWidth = Math.max(80, width - 24); + IntRangeSlider slider = new IntRangeSlider(0, 0, sliderWidth, 20, minInt, maxInt, cur, cfg, + cfg instanceof BoundAttributeConfig bound ? bound.defaultValue() + : Integer.toString(minInt)); slider.updateMessage(); - return slider; + ButtonWidget editButton = ButtonWidget.builder(Text.literal("#"), button -> { + if (owner == null) { + return; + } + MinecraftClient.getInstance().setScreen(new NumericSliderInputScreen(owner, cfg.name, + true, minInt, maxInt, cfg.value, cfg instanceof BoundAttributeConfig bound + ? bound.defaultValue() + : Integer.toString(minInt), value -> { + setValue(cfg, value); + slider.syncFromValueString(value); + })); + }).dimensions(0, 0, 20, 20).build(); + return List.of(slider, editButton); } - private static ClickableWidget buildFloatRange(AttributeConfig cfg, String raw, int width) { + private static List buildFloatRange(AttributeConfig cfg, Screen owner, + String raw, int width) { Range r = parseRange(raw); float start = (float) r.start; float end = (float) r.end; @@ -254,18 +302,43 @@ private static ClickableWidget buildFloatRange(AttributeConfig cfg, String raw, start = end; end = t; } + final float minFloat = start; + final float maxFloat = end; - float cur = start; + float cur = minFloat; if (isFloat(cfg.value)) { cur = Float.parseFloat(cfg.value); } else { - cfg.value = formatTwoDecimals(start); + setValue(cfg, formatTwoDecimals(minFloat)); } - cur = MathHelper.clamp(cur, start, end); + cur = MathHelper.clamp(cur, minFloat, maxFloat); - FloatRangeSlider slider = new FloatRangeSlider(0, 0, width, 20, start, end, cur, cfg); + int sliderWidth = Math.max(80, width - 24); + FloatRangeSlider slider = new FloatRangeSlider(0, 0, sliderWidth, 20, minFloat, maxFloat, + cur, cfg, cfg instanceof BoundAttributeConfig bound ? bound.defaultValue() + : formatTwoDecimals(minFloat)); slider.updateMessage(); - return slider; + ButtonWidget editButton = ButtonWidget.builder(Text.literal("#"), button -> { + if (owner == null) { + return; + } + MinecraftClient.getInstance().setScreen(new NumericSliderInputScreen(owner, cfg.name, + false, minFloat, maxFloat, cfg.value, cfg instanceof BoundAttributeConfig bound + ? bound.defaultValue() + : formatTwoDecimals(minFloat), value -> { + setValue(cfg, value); + slider.syncFromValueString(value); + })); + }).dimensions(0, 0, 20, 20).build(); + return List.of(slider, editButton); + } + + private static void setValue(AttributeConfig cfg, String value) { + if (cfg instanceof BoundAttributeConfig bound) { + bound.apply(value); + return; + } + cfg.value = value; } private static boolean isInt(String s) { @@ -336,14 +409,16 @@ private static class IntRangeSlider extends SliderWidget { private final int start; private final int end; private final AttributeConfig cfg; + private final String defaultValue; public IntRangeSlider(int x, int y, int width, int height, int start, int end, int cur, - AttributeConfig cfg) { + AttributeConfig cfg, String defaultValue) { super(x, y, width, height, Text.empty(), (cur - (double) start) / (double) (end - start)); this.start = start; this.end = end; this.cfg = cfg; + this.defaultValue = defaultValue; this.value = (cur - (double) start) / (double) (end - start); } @@ -362,7 +437,26 @@ protected void updateMessage() { @Override protected void applyValue() { int v = MathHelper.clamp(current(), start, end); - cfg.value = Integer.toString(v); + setValue(cfg, Integer.toString(v)); + } + + public void syncFromValueString(String valueString) { + if (!isInt(valueString)) { + return; + } + int value = MathHelper.clamp(Integer.parseInt(valueString), start, end); + this.value = (value - (double) start) / (double) (end - start); + updateMessage(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 1 && this.isMouseOver(mouseX, mouseY)) { + syncFromValueString(defaultValue); + setValue(cfg, Integer.toString(current())); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); } } @@ -371,14 +465,16 @@ private static class FloatRangeSlider extends SliderWidget { private final float start; private final float end; private final AttributeConfig cfg; + private final String defaultValue; public FloatRangeSlider(int x, int y, int width, int height, float start, float end, float cur, - AttributeConfig cfg) { + AttributeConfig cfg, String defaultValue) { super(x, y, width, height, Text.empty(), (cur - start) / (double) (end - start)); this.start = start; this.end = end; this.cfg = cfg; + this.defaultValue = defaultValue; this.value = (cur - start) / (double) (end - start); } @@ -397,7 +493,26 @@ protected void updateMessage() { @Override protected void applyValue() { float v = MathHelper.clamp(current(), start, end); - cfg.value = formatTwoDecimals(v); + setValue(cfg, formatTwoDecimals(v)); + } + + public void syncFromValueString(String valueString) { + if (!isFloat(valueString)) { + return; + } + float value = MathHelper.clamp(Float.parseFloat(valueString), start, end); + this.value = (value - start) / (double) (end - start); + updateMessage(); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 1 && this.isMouseOver(mouseX, mouseY)) { + syncFromValueString(defaultValue); + setValue(cfg, formatTwoDecimals(current())); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); } } } diff --git a/src/main/java/com/radiance/client/gui/ModuleAttributeScreen.java b/src/main/java/com/radiance/client/gui/ModuleAttributeScreen.java index 56e816f..7722e90 100644 --- a/src/main/java/com/radiance/client/gui/ModuleAttributeScreen.java +++ b/src/main/java/com/radiance/client/gui/ModuleAttributeScreen.java @@ -56,8 +56,8 @@ protected void init() { } for (AttributeConfig cfg : list) { - List ws = AttributeWidgetUtil.buildWidgets(cfg, textRenderer, WIDGET_WIDTH, - VEC3_COMPONENT_WIDTH); + List ws = AttributeWidgetUtil.buildWidgets(cfg, this, textRenderer, + WIDGET_WIDTH, VEC3_COMPONENT_WIDTH); for (ClickableWidget w : ws) { addDrawableChild(w); } @@ -70,14 +70,22 @@ public void close() { MinecraftClient.getInstance().setScreen(parent); } + @Override + public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) { + context.fill(0, 0, this.width, this.height, RadianceTheme.panelBg); + } + @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { super.render(context, mouseX, mouseY, delta); - context.drawTextWithShadow(textRenderer, Text.translatable(module.name), 10, HEADER_HEIGHT + 8, 0xFFEAEAEA); + RadianceTheme.drawOutlinedText(context, textRenderer, Text.translatable(module.name), 10, + HEADER_HEIGHT + 8, RadianceTheme.textPrimary); if (rows.isEmpty()) { - context.drawTextWithShadow(textRenderer, MODULE_ATTRIBUTE_SCREEN_NO_ATTRIBUTES, 10, 60, 0xFFB0B0B0); + RadianceTheme.drawOutlinedText(context, textRenderer, + Text.translatable(MODULE_ATTRIBUTE_SCREEN_NO_ATTRIBUTES), 10, 60, + RadianceTheme.textSecondary); return; } @@ -90,8 +98,8 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { boolean visible = y >= (HEADER_HEIGHT + 18) && y <= (this.height - 24); if (visible) { - context.drawTextWithShadow(textRenderer, Text.translatable(row.cfg.name), ROW_LEFT, y + 6, - 0xFFD0D0D0); + RadianceTheme.drawOutlinedText(context, textRenderer, + Text.translatable(row.cfg.name), ROW_LEFT, y + 6, RadianceTheme.textPrimary); } layoutRowWidgets(row, y); diff --git a/src/main/java/com/radiance/client/gui/RadianceTheme.java b/src/main/java/com/radiance/client/gui/RadianceTheme.java new file mode 100644 index 0000000..59e693e --- /dev/null +++ b/src/main/java/com/radiance/client/gui/RadianceTheme.java @@ -0,0 +1,204 @@ +package com.radiance.client.gui; + +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.text.Text; + +public final class RadianceTheme { + + private RadianceTheme() { + } + + private static final int BASE_PANEL = 0x0A0A0A; + private static final int BASE_WIDGET = 0x1A1A2A; + private static final int BASE_HOVER = 0x2A2A4A; + private static final int BASE_ACTIVE = 0x3A3A5A; + private static final int BASE_DROPDOWN = 0x101018; + private static final int BASE_HEADER = 0x0A0A0A; + private static final int BASE_BORDER = 0x808080; + private static final int BASE_BORDER_FOCUS = 0xC0C0E0; + private static final int BASE_TEXT_PRIMARY = 0xE0E0E0; + private static final int BASE_TEXT_SECONDARY = 0x909090; + private static final int BASE_TEXT_ACCENT = 0x8888CC; + + public static final int TEXT_ERROR = 0xFFFF5555; + public static final int TEXT_SUCCESS = 0xFF55FF55; + public static final int TEXT_LINK = 0xFF55FFFF; + public static final int TEXT_PATH = 0xFFFFAA00; + public static final int SELECTED_BAR = 0xFF6060FF; + public static final int GPU_TAG = 0xFF707090; + + public static int panelBg; + public static int widgetBg; + public static int widgetBgHover; + public static int widgetBgActive; + public static int dropdownBg; + public static int headerBg; + public static int borderDefault; + public static int borderFocused; + public static int textPrimary; + public static int textSecondary; + public static int textAccent; + + private static float globalAlpha = 0.55f; + private static float effectiveAlpha = 0.55f; + private static final java.util.Map screenAlphaOverrides = new java.util.HashMap<>(); + + public static ClickableWidget activeSlider = null; + private static long fadeStartMs = 0; + private static boolean fadingOut = false; + public static final long FADE_OUT_MS = 100; + public static final long FADE_IN_MS = 150; + + public static boolean peekActive = false; + private static boolean adaptiveDimmingEnabled = false; + private static float sceneBrightness = 0.5f; + + static { + recompute(); + } + + public static void setGlobalAlpha(float alpha) { + globalAlpha = Math.max(0f, Math.min(1f, alpha)); + recompute(); + } + + public static void setScreenAlpha(String screenName, float alpha) { + if (alpha < 0) { + screenAlphaOverrides.remove(screenName); + } else { + screenAlphaOverrides.put(screenName, Math.max(0f, Math.min(1f, alpha))); + } + } + + public static void setAdaptiveDimmingEnabled(boolean enabled) { + adaptiveDimmingEnabled = enabled; + recompute(); + } + + public static void setSceneBrightness(float brightness) { + sceneBrightness = Math.max(0f, Math.min(1f, brightness)); + if (adaptiveDimmingEnabled) { + recompute(); + } + } + + public static void recompute() { + effectiveAlpha = globalAlpha; + if (adaptiveDimmingEnabled) { + float adjustment = (sceneBrightness - 0.5f) * 0.2f; + effectiveAlpha = Math.max(0f, Math.min(1f, globalAlpha + adjustment)); + } + + panelBg = withAlpha(BASE_PANEL, effectiveAlpha * 0.7f); + widgetBg = withAlpha(BASE_WIDGET, effectiveAlpha); + widgetBgHover = withAlpha(BASE_HOVER, effectiveAlpha); + widgetBgActive = withAlpha(BASE_ACTIVE, effectiveAlpha); + dropdownBg = withAlpha(BASE_DROPDOWN, Math.min(1f, effectiveAlpha + 0.15f)); + headerBg = withAlpha(BASE_HEADER, effectiveAlpha * 0.5f); + borderDefault = withAlpha(BASE_BORDER, effectiveAlpha * 0.6f); + borderFocused = withAlpha(BASE_BORDER_FOCUS, effectiveAlpha); + textPrimary = withAlpha(BASE_TEXT_PRIMARY, 1.0f); + textSecondary = withAlpha(BASE_TEXT_SECONDARY, 0.8f); + textAccent = withAlpha(BASE_TEXT_ACCENT, 1.0f); + } + + public static int currentPanelBg(Screen screen) { + if (screen == null) { + return panelBg; + } + Float override = screenAlphaOverrides.get(screen.getClass().getSimpleName()); + if (override == null) { + return panelBg; + } + return withAlpha(BASE_PANEL, override * 0.7f); + } + + public static void beginSliderFocus(ClickableWidget slider) { + activeSlider = slider; + fadeStartMs = System.currentTimeMillis(); + fadingOut = true; + } + + public static void endSliderFocus() { + activeSlider = null; + fadeStartMs = System.currentTimeMillis(); + fadingOut = false; + } + + public static float inactiveFadeFactor() { + if (peekActive) { + return 0f; + } + if (activeSlider == null && fadeStartMs == 0) { + return 1f; + } + + long elapsed = System.currentTimeMillis() - fadeStartMs; + if (fadingOut || activeSlider != null) { + return Math.max(0f, 1f - (elapsed / (float) FADE_OUT_MS)); + } + float factor = Math.min(1f, elapsed / (float) FADE_IN_MS); + if (factor >= 1f) { + fadeStartMs = 0; + } + return factor; + } + + public static int withAlpha(int rgb, float alpha) { + int a = Math.max(0, Math.min(255, (int) (alpha * 255))); + return (a << 24) | (rgb & 0x00FFFFFF); + } + + public static int scaleAlpha(int argb, float multiplier) { + int a = (argb >>> 24) & 0xFF; + a = Math.max(0, Math.min(255, (int) (a * multiplier))); + return (a << 24) | (argb & 0x00FFFFFF); + } + + public static void drawOutlinedText(DrawContext ctx, TextRenderer renderer, Text text, int x, + int y, int color) { + drawOutlinedText(ctx, renderer, text, x, y, color, 1f); + } + + public static void drawOutlinedText(DrawContext ctx, TextRenderer renderer, Text text, int x, + int y, int color, float alphaMult) { + int outlineColor = withAlpha(0x000000, 0.4f * alphaMult); + int mainColor = scaleAlpha(color, alphaMult); + + ctx.drawText(renderer, text, x - 1, y, outlineColor, false); + ctx.drawText(renderer, text, x + 1, y, outlineColor, false); + ctx.drawText(renderer, text, x, y - 1, outlineColor, false); + ctx.drawText(renderer, text, x, y + 1, outlineColor, false); + ctx.drawText(renderer, text, x, y, mainColor, false); + } + + public static void drawCategoryHeader(DrawContext ctx, TextRenderer renderer, Text text, int x, + int y, int width, int entryHeight) { + drawCategoryHeader(ctx, renderer, text, x, y, width, entryHeight, 1f); + } + + public static void drawCategoryHeader(DrawContext ctx, TextRenderer renderer, Text text, int x, + int y, int width, int entryHeight, float alphaMult) { + if (alphaMult <= 0f) { + return; + } + + int lineY = y + entryHeight / 2; + int textW = renderer.getWidth(text); + int textX = x + (width - textW) / 2; + int textY = y + entryHeight - 9 - 1; + int lineColor = withAlpha(BASE_TEXT_ACCENT, 0.3f * alphaMult); + + if (textX > x + 4) { + ctx.fill(x, lineY, textX - 4, lineY + 1, lineColor); + } + if (textX + textW + 4 < x + width) { + ctx.fill(textX + textW + 4, lineY, x + width, lineY + 1, lineColor); + } + drawOutlinedText(ctx, renderer, text, textX, textY, + textAccent & 0x00FFFFFF | 0xFF000000, alphaMult); + } +} diff --git a/src/main/java/com/radiance/client/gui/RenderPipelineScreen.java b/src/main/java/com/radiance/client/gui/RenderPipelineScreen.java index 89d230f..7f5d1a1 100644 --- a/src/main/java/com/radiance/client/gui/RenderPipelineScreen.java +++ b/src/main/java/com/radiance/client/gui/RenderPipelineScreen.java @@ -256,7 +256,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { node.updateWidth(textRenderer); } - this.renderBackground(context, mouseX, mouseY, delta); + context.fill(0, 0, this.width, this.height, RadianceTheme.panelBg); context.getMatrices().push(); context.getMatrices().scale(GLOBAL_SCALE, GLOBAL_SCALE, 1f); @@ -268,8 +268,9 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { drawable.render(context, scaledMouseX, scaledMouseY, delta); } - context.drawTextWithShadow(textRenderer, - Text.translatable(RENDER_PIPELINE_SCREEN_BACK_HINT), 10, HEADER_HEIGHT + 8, 0xFFEAEAEA); + RadianceTheme.drawOutlinedText(context, textRenderer, + Text.translatable(RENDER_PIPELINE_SCREEN_BACK_HINT), 10, HEADER_HEIGHT + 8, + RadianceTheme.textSecondary); if (mode == Mode.PIPELINE) { for (ModuleNode node : nodes) { @@ -456,12 +457,12 @@ private void drawModuleNode(DrawContext context, ModuleNode moduleNode) { int w = moduleNode.width; int h = moduleNode.height(); - context.fill(x, y, x + w, y + h, 0xFF20242C); + context.fill(x, y, x + w, y + h, RadianceTheme.widgetBg); - context.fill(x, y, x + w, y + moduleNode.headerH, 0xFF2B3240); + context.fill(x, y, x + w, y + moduleNode.headerH, RadianceTheme.headerBg); - context.drawTextWithShadow(textRenderer, Text.translatable(moduleNode.module.name), x + 6, - y + 5, 0xFFEAEAEA); + RadianceTheme.drawOutlinedText(context, textRenderer, + Text.translatable(moduleNode.module.name), x + 6, y + 5, RadianceTheme.textPrimary); int btnSize = 12; int deleteX = x + w - btnSize - 4; @@ -471,7 +472,8 @@ private void drawModuleNode(DrawContext context, ModuleNode moduleNode) { context.drawTexture(RenderLayer::getGuiTextured, GEAR_TEX, gearX, btnY, 0, 0, btnSize, btnSize, btnSize, btnSize); - context.drawTextWithShadow(textRenderer, "×", deleteX + 3, btnY + 2, 0xFFFF5A5A); + context.drawTextWithShadow(textRenderer, "×", deleteX + 3, btnY + 2, + RadianceTheme.TEXT_ERROR); for (int i = 0; i < moduleNode.rows(); i++) { int ry = y + moduleNode.headerH + moduleNode.pad + i * moduleNode.rowH + 7; @@ -485,7 +487,8 @@ private void drawModuleNode(DrawContext context, ModuleNode moduleNode) { boolean isConnected = moduleConnections.stream().anyMatch(l -> l.dst == in); drawPortDot(context, dotX, dotY, color, isConnected, false); - context.drawTextWithShadow(textRenderer, in.name, x + 18, ry + 2, 0xFFD0D0D0); + context.drawTextWithShadow(textRenderer, in.name, x + 18, ry + 2, + RadianceTheme.textPrimary); } if (i < moduleNode.module.outputImageConfigs.size()) { @@ -499,7 +502,7 @@ private void drawModuleNode(DrawContext context, ModuleNode moduleNode) { int nameWidth = textRenderer.getWidth(out.name); context.drawTextWithShadow(textRenderer, out.name, (dotX - 8) - nameWidth, ry + 2, - 0xFFD0D0D0); + RadianceTheme.textPrimary); } } } @@ -510,7 +513,7 @@ private void drawPortDot(DrawContext ctx, int cx, int cy, int color, boolean fil ctx.fill(cx - 3, cy - 3, cx + 4, cy + 4, 0xFF000000); ctx.fill(cx - 2, cy - 2, cx + 3, cy + 3, color); if (!filled) { - ctx.fill(cx - 1, cy - 1, cx + 2, cy + 2, 0xFF20242C); + ctx.fill(cx - 1, cy - 1, cx + 2, cy + 2, RadianceTheme.widgetBg); } } @@ -836,15 +839,15 @@ public ModuleSelector(int x, int y, Map entries) { public void render(DrawContext ctx, int mouseX, int mouseY) { int currentY = y; ctx.fill(x - 1, y - 1, x + width + 1, y + (options.size() * itemHeight) + 1, - 0xFFFFFFFF); + RadianceTheme.borderDefault); for (ModuleEntry entry : options) { boolean hovered = mouseX >= x && mouseX <= x + width && mouseY >= currentY && mouseY <= currentY + itemHeight; ctx.fill(x, currentY, x + width, currentY + itemHeight, - hovered ? 0xFF444444 : 0xFF222222); + hovered ? RadianceTheme.widgetBgHover : RadianceTheme.dropdownBg); ctx.drawTextWithShadow(textRenderer, Text.translatable(entry.name), x + 5, - currentY + 5, 0xFFE0E0E0); + currentY + 5, RadianceTheme.textPrimary); currentY += itemHeight; } } @@ -948,7 +951,7 @@ private void syncPresetToPipeline() { } private List buildPresetWidgets(AttributeConfig cfg) { - return AttributeWidgetUtil.buildWidgets(cfg, textRenderer, 200, 64); + return AttributeWidgetUtil.buildWidgets(cfg, this, textRenderer, 200, 64); } private class PresetSelector { diff --git a/src/main/java/com/radiance/client/option/EnvironmentRenderStyles.java b/src/main/java/com/radiance/client/option/EnvironmentRenderStyles.java new file mode 100644 index 0000000..490a3c5 --- /dev/null +++ b/src/main/java/com/radiance/client/option/EnvironmentRenderStyles.java @@ -0,0 +1,94 @@ +package com.radiance.client.option; + +import com.radiance.client.pipeline.Pipeline; +import java.util.Objects; + +public final class EnvironmentRenderStyles { + + public static final String RAY_TRACING_MODULE = "render_pipeline.module.ray_tracing.name"; + public static final String WATER_SURFACE_MODE_ATTRIBUTE = + "render_pipeline.module.ray_tracing.attribute.water_surface_mode"; + public static final String CLOUD_VOLUME_MODE_ATTRIBUTE = + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode"; + public static final float WATER_SURFACE_SENTINEL = 31.25f; + + private EnvironmentRenderStyles() { + } + + public static WaterSurfaceMode waterSurfaceMode() { + return WaterSurfaceMode.fromKey(Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, + WATER_SURFACE_MODE_ATTRIBUTE, WaterSurfaceMode.SPARKLING.key())); + } + + public static CloudVolumeMode cloudVolumeMode() { + return CloudVolumeMode.fromKey(Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, + CLOUD_VOLUME_MODE_ATTRIBUTE, CloudVolumeMode.EFFICIENT_VOLUME.key())); + } + + public enum WaterSurfaceMode { + NATIVE_LIKE("render_pipeline.module.ray_tracing.attribute.water_surface_mode.native_like", + 0.0f), + SPARKLING("render_pipeline.module.ray_tracing.attribute.water_surface_mode.sparkling", + 1.0f); + + private final String key; + private final float shaderId; + + WaterSurfaceMode(String key, float shaderId) { + this.key = key; + this.shaderId = shaderId; + } + + public String key() { + return key; + } + + public float shaderId() { + return shaderId; + } + + public static WaterSurfaceMode fromKey(String value) { + for (WaterSurfaceMode mode : values()) { + if (Objects.equals(mode.key, value)) { + return mode; + } + } + return SPARKLING; + } + } + + public enum CloudVolumeMode { + NATIVE("render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.native", 0.0f), + EFFICIENT_VOLUME( + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.efficient_volume", + 1.0f), + REALISTIC_VOLUME( + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.realistic_volume", + 2.0f); + + private final String key; + private final float shaderId; + + CloudVolumeMode(String key, float shaderId) { + this.key = key; + this.shaderId = shaderId; + } + + public String key() { + return key; + } + + public float shaderId() { + return shaderId; + } + + public static CloudVolumeMode fromKey(String value) { + for (CloudVolumeMode mode : values()) { + if (Objects.equals(mode.key, value)) { + return mode; + } + } + return EFFICIENT_VOLUME; + } + } +} diff --git a/src/main/java/com/radiance/client/option/Options.java b/src/main/java/com/radiance/client/option/Options.java index 39830f9..eb2ef90 100644 --- a/src/main/java/com/radiance/client/option/Options.java +++ b/src/main/java/com/radiance/client/option/Options.java @@ -1,11 +1,16 @@ package com.radiance.client.option; import com.radiance.client.RadianceClient; +import com.radiance.client.pipeline.Pipeline; +import com.radiance.client.pipeline.Presets; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.Properties; public class Options { @@ -31,12 +36,20 @@ public class Options { public static final String DLSS_MODE_DLAA = "options.video.dlss_mode.dlaa"; public static final String DLSS_MODE_KEY = "options.video.dlss_mode"; + public static final String QUALITY_LEVEL_KEY = "options.video.quality_level"; public static final String UPSCALER_TYPE_KEY = "options.video.upscaler_type"; public static final String UPSCALER_QUALITY_KEY = "options.video.upscaler_quality"; public static final String DENOISER_MODE_KEY = "options.video.denoiser_mode"; + public static final String HDR_OUTPUT_KEY = "options.video.hdr_output"; + public static final String DLSS_FRAME_GENERATION_KEY = "options.video.dlss_frame_generation"; public static final String RAY_BOUNCES_KEY = "options.video.ray_bounces"; public static final String CHUNK_BUILDING_BATCH_SIZE_KEY = "options.video.chunk_building_batch_size"; public static final String CHUNK_BUILDING_TOTAL_BATCHES_KEY = "options.video.chunk_building_total_batches"; + public static final String OUTPUT_SCALE_2X_KEY = "options.video.output_scale_2x"; + public static final String SIMPLIFIED_INDIRECT_KEY = "options.video.simplified_indirect"; + public static final String REFLEX_ENABLED_KEY = "options.video.reflex_enabled"; + public static final String REFLEX_BOOST_KEY = "options.video.reflex_boost"; + public static final String VRR_MODE_KEY = "options.video.vrr_mode"; public static final String PIPELINE_SETUP_KEY = "options.video.pipeline_setup"; public static final String UPSCALER_TYPE_NATIVE = "options.video.upscaler_type.native"; @@ -46,22 +59,202 @@ public class Options { public static final String UPSCALER_QUALITY_QUALITY = "options.video.upscaler_quality.quality"; public static final String UPSCALER_QUALITY_BALANCED = "options.video.upscaler_quality.balanced"; public static final String UPSCALER_QUALITY_PERFORMANCE = "options.video.upscaler_quality.performance"; + public static final String QUALITY_LEVEL_FLUENT = "options.video.quality_level.fluent"; + public static final String QUALITY_LEVEL_PERFORMANCE = "options.video.quality_level.performance"; + public static final String QUALITY_LEVEL_BALANCED = "options.video.quality_level.balanced"; + public static final String QUALITY_LEVEL_QUALITY = "options.video.quality_level.quality"; + public static final String QUALITY_LEVEL_ULTRA = "options.video.quality_level.ultra"; + public static final String QUALITY_LEVEL_EXTREME = "options.video.quality_level.extreme"; public static final String DENOISER_MODE_DLSS = "options.video.denoiser_mode.dlss"; public static final String DENOISER_MODE_SVGF = "options.video.denoiser_mode.svgf"; public static final String DENOISER_MODE_NRD = "options.video.denoiser_mode.nrd"; public static final String DENOISER_MODE_TEMPORAL = "options.video.denoiser_mode.temporal"; + public static final String HDR_OUTPUT = "options.video.hdr_output"; + + private static final String RAY_TRACING_MODULE = "render_pipeline.module.ray_tracing.name"; + private static final String DLSS_MODULE = "render_pipeline.module.dlss.name"; + private static final String FSR3_MODULE = "render_pipeline.module.fsr_upscaler.name"; + private static final String XESS_MODULE = "render_pipeline.module.xess_sr.name"; + private static final String RT_TERRAIN_MESHING_MODE = + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode"; + private static final String RT_GREEDY_MERGE_MAX_SPAN = + "render_pipeline.module.ray_tracing.attribute.greedy_merge_max_span"; + private static final String RT_FAR_FIELD_GEOMETRY_MODE = + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode"; + private static final String RT_FAR_FIELD_START_DISTANCE_CHUNKS = + "render_pipeline.module.ray_tracing.attribute.far_field_start_distance_chunks"; + private static final String RT_FAR_FIELD_MATERIAL_MODE = + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode"; + private static final String RT_GLASS_PATH_MODE = + "render_pipeline.module.ray_tracing.attribute.glass_path_mode"; + private static final String RT_FOLIAGE_PATH_MODE = + "render_pipeline.module.ray_tracing.attribute.foliage_path_mode"; + private static final String RT_DECORATION_PATH_MODE = + "render_pipeline.module.ray_tracing.attribute.decoration_path_mode"; + private static final String RT_BLAS_INCLUSION_MODE = + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode"; + private static final String RT_REFLECTION_RAY_MATERIAL_MODE = + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode"; + private static final String RT_DIFFUSE_GI_MODE = + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode"; + private static final String RT_CLOUD_VOLUME_MODE = + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode"; + private static final String RT_TRANSPARENT_SPLIT_MODE = + "render_pipeline.module.ray_tracing.attribute.transparent_split_mode"; + private static final String RT_USE_JITTER = + "render_pipeline.module.ray_tracing.attribute.use_jitter"; + private static final String RT_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES = + "render_pipeline.module.ray_tracing.attribute.separate_entity_terrain_accel_structures"; + private static final String RT_TERRAIN_UPDATE_INTERVAL_FRAMES = + "render_pipeline.module.ray_tracing.attribute.terrain_update_interval_frames"; + private static final String RT_ENTITY_UPDATE_INTERVAL_FRAMES = + "render_pipeline.module.ray_tracing.attribute.entity_update_interval_frames"; + private static final String RT_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES = + "render_pipeline.module.ray_tracing.attribute.block_entity_update_interval_frames"; + private static final String RT_PARTICLE_UPDATE_INTERVAL_FRAMES = + "render_pipeline.module.ray_tracing.attribute.particle_update_interval_frames"; + private static final String RT_PARTICLE_CRIT_GLOW = + "render_pipeline.module.ray_tracing.attribute.particle_crit_glow"; + private static final String RT_PARTICLE_DEATH_SMOKE_GLOW = + "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow"; + private static final String RT_PARTICLE_CRIT_GLOW_STRENGTH = + "render_pipeline.module.ray_tracing.attribute.particle_crit_glow_strength"; + private static final String RT_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH = + "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow_strength"; + private static final String RT_NUM_RAY_BOUNCES = + "render_pipeline.module.ray_tracing.attribute.num_ray_bounces"; + private static final String RT_PBR_SAMPLING_MODE = + "render_pipeline.module.ray_tracing.attribute.pbr_sampling_mode"; + private static final String RT_USE_SHARC = + "render_pipeline.module.ray_tracing.attribute.use_sharc"; + private static final String RT_BASIC_RADIANCE = + "render_pipeline.module.ray_tracing.attribute.basic_radiance"; + private static final String RT_DIRECT_LIGHT_STRENGTH = + "render_pipeline.module.ray_tracing.attribute.direct_light_strength"; + private static final String RT_INDIRECT_LIGHT_STRENGTH = + "render_pipeline.module.ray_tracing.attribute.indirect_light_strength"; + private static final String FSR3_QUALITY_MODE = + "render_pipeline.module.fsr_upscaler.attribute.quality_mode"; + private static final String FSR3_SHARPNESS = + "render_pipeline.module.fsr_upscaler.attribute.sharpness"; + private static final String XESS_QUALITY_MODE = + "render_pipeline.module.xess_sr.attribute.quality_mode"; + private static final String XESS_PRE_EXPOSURE = + "render_pipeline.module.xess_sr.attribute.pre_exposure"; + private static final String DLSS_MODE_ATTRIBUTE = + "render_pipeline.module.dlss.attribute.mode"; + private static final String NRD_MODULE = "render_pipeline.module.nrd.name"; + private static final String NRD_ANTILAG_LUMINANCE_SIGMA_SCALE = + "render_pipeline.module.nrd.attribute.antilag_luminance_sigma_scale"; + private static final String NRD_ANTILAG_LUMINANCE_SENSITIVITY = + "render_pipeline.module.nrd.attribute.antilag_luminance_sensitivity"; + private static final String NRD_RESPONSIVE_ACCUMULATION_ROUGHNESS_THRESHOLD = + "render_pipeline.module.nrd.attribute.responsive_accumulation_roughness_threshold"; + private static final String NRD_RESPONSIVE_ACCUMULATION_MIN_ACCUMULATED_FRAME_NUM = + "render_pipeline.module.nrd.attribute.responsive_accumulation_min_accumulated_frame_num"; + private static final String NRD_MAX_ACCUMULATED_FRAME_NUM = + "render_pipeline.module.nrd.attribute.max_accumulated_frame_num"; + private static final String NRD_MAX_FAST_ACCUMULATED_FRAME_NUM = + "render_pipeline.module.nrd.attribute.max_fast_accumulated_frame_num"; + private static final String NRD_MAX_STABILIZED_FRAME_NUM = + "render_pipeline.module.nrd.attribute.max_stabilized_frame_num"; + private static final String NRD_HISTORY_FIX_FRAME_NUM = + "render_pipeline.module.nrd.attribute.history_fix_frame_num"; + private static final String NRD_HISTORY_FIX_BASE_PIXEL_STRIDE = + "render_pipeline.module.nrd.attribute.history_fix_base_pixel_stride"; + private static final String NRD_HISTORY_FIX_ALTERNATE_PIXEL_STRIDE = + "render_pipeline.module.nrd.attribute.history_fix_alternate_pixel_stride"; + private static final String NRD_FAST_HISTORY_CLAMPING_SIGMA_SCALE = + "render_pipeline.module.nrd.attribute.fast_history_clamping_sigma_scale"; + private static final String NRD_DIFFUSE_PREPASS_BLUR_RADIUS = + "render_pipeline.module.nrd.attribute.diffuse_prepass_blur_radius"; + private static final String NRD_SPECULAR_PREPASS_BLUR_RADIUS = + "render_pipeline.module.nrd.attribute.specular_prepass_blur_radius"; + private static final String NRD_MIN_HIT_DISTANCE_WEIGHT = + "render_pipeline.module.nrd.attribute.min_hit_distance_weight"; + private static final String NRD_MIN_BLUR_RADIUS = + "render_pipeline.module.nrd.attribute.min_blur_radius"; + private static final String NRD_MAX_BLUR_RADIUS = + "render_pipeline.module.nrd.attribute.max_blur_radius"; + private static final String NRD_LOBE_ANGLE_FRACTION = + "render_pipeline.module.nrd.attribute.lobe_angle_fraction"; + private static final String NRD_ROUGHNESS_FRACTION = + "render_pipeline.module.nrd.attribute.roughness_fraction"; + private static final String NRD_PLANE_DISTANCE_SENSITIVITY = + "render_pipeline.module.nrd.attribute.plane_distance_sensitivity"; + private static final String NRD_SPECULAR_PROBABILITY_THRESHOLD_MIN = + "render_pipeline.module.nrd.attribute.specular_probability_thresholds_for_mv_modification_min"; + private static final String NRD_SPECULAR_PROBABILITY_THRESHOLD_MAX = + "render_pipeline.module.nrd.attribute.specular_probability_thresholds_for_mv_modification_max"; + private static final String NRD_FIREFLY_SUPPRESSOR_MIN_RELATIVE_SCALE = + "render_pipeline.module.nrd.attribute.firefly_suppressor_min_relative_scale"; + private static final String NRD_MIN_MATERIAL_FOR_DIFFUSE = + "render_pipeline.module.nrd.attribute.min_material_for_diffuse"; + private static final String NRD_MIN_MATERIAL_FOR_SPECULAR = + "render_pipeline.module.nrd.attribute.min_material_for_specular"; + private static final String NRD_HIT_DISTANCE_RECONSTRUCTION_MODE = + "render_pipeline.module.nrd.attribute.hit_distance_reconstruction_mode"; + private static final String NRD_ENABLE_ANTI_FIREFLY = + "render_pipeline.module.nrd.attribute.enable_anti_firefly"; + private static final String TONE_MAPPING_MODULE = "render_pipeline.module.tone_mapping.name"; + private static final String TM_METHOD = + "render_pipeline.module.tone_mapping.attribute.method"; + private static final String TM_MIDDLE_GREY = + "render_pipeline.module.tone_mapping.attribute.middle_grey"; + private static final String TM_EXPOSURE_UP_SPEED = + "render_pipeline.module.tone_mapping.attribute.exposure_up_speed"; + private static final String TM_EXPOSURE_DOWN_SPEED = + "render_pipeline.module.tone_mapping.attribute.exposure_down_speed"; + private static final String TM_LOG2_LUMINANCE_MIN = + "render_pipeline.module.tone_mapping.attribute.log2_luminance_min"; + private static final String TM_LOG2_LUMINANCE_MAX = + "render_pipeline.module.tone_mapping.attribute.log2_luminance_max"; + private static final String TM_LOW_PERCENT = + "render_pipeline.module.tone_mapping.attribute.low_percent"; + private static final String TM_HIGH_PERCENT = + "render_pipeline.module.tone_mapping.attribute.high_percent"; + private static final String TM_MIN_EXPOSURE = + "render_pipeline.module.tone_mapping.attribute.min_exposure"; + private static final String TM_MAX_EXPOSURE = + "render_pipeline.module.tone_mapping.attribute.max_exposure"; + private static final String TM_ENABLE_AUTO_EXPOSURE = + "render_pipeline.module.tone_mapping.attribute.enable_auto_exposure"; + private static final String TM_EXPOSURE_METERING_MODE = + "render_pipeline.module.tone_mapping.attribute.exposure_metering_mode"; + private static final String TM_CENTER_METERING_PERCENT = + "render_pipeline.module.tone_mapping.attribute.center_metering_percent"; + private static final String TM_MANUAL_EXPOSURE = + "render_pipeline.module.tone_mapping.attribute.manual_exposure"; + private static final String TM_EXPOSURE_BIAS = + "render_pipeline.module.tone_mapping.attribute.exposure_bias"; + private static final String TM_SATURATION = + "render_pipeline.module.tone_mapping.attribute.saturation"; + private static final String TM_WHITE_POINT = + "render_pipeline.module.tone_mapping.attribute.white_point"; + private static final String TM_CLAMP_OUTPUT = + "render_pipeline.module.tone_mapping.attribute.clamp_output"; public static int maxFps = 260; public static int inactivityFpsLimit = 260; public static boolean vsync = true; + public static int qualityLevel = QualityLevel.BALANCED.getId(); public static int dlssMode = 1; public static int upscalerType = 1; public static int upscalerQuality = 1; public static int denoiserMode = 1; + public static boolean hdrOutput = false; + public static boolean dlssFrameGeneration = false; + public static boolean outputScale2x = false; + public static boolean simplifiedIndirect = false; + public static boolean reflexEnabled = false; + public static boolean reflexBoost = false; + public static boolean vrrMode = false; public static int rayBounces = 4; - public static int chunkBuildingBatchSize = 12; - public static int chunkBuildingTotalBatches = 12; + public static int chunkBuildingBatchSize = 14; + public static int chunkBuildingTotalBatches = 16; + private static final List invalidOptionKeys = new ArrayList<>(); public static void readOptions() { + clearInvalidOptionWarnings(); Path path = RadianceClient.radianceDir.resolve(OPTION_PROPERTIES); if (!Files.exists(path)) { // System.out.println("Generating default options..."); @@ -73,18 +266,37 @@ public static void readOptions() { try (InputStream in = Files.newInputStream(path)) { props.load(in); - setMaxFps(Integer.parseInt(props.getProperty("maxFps", String.valueOf(maxFps))), false); - setInactivityFpsLimit(Integer.parseInt( - props.getProperty("inactivityFpsLimit", String.valueOf(inactivityFpsLimit))), - false); - setVsync(Boolean.parseBoolean(props.getProperty("vsync", String.valueOf(vsync))), - false); - setChunkBuildingBatchSize(Integer.parseInt(props.getProperty("chunkBuildingBatchSize", - String.valueOf(chunkBuildingBatchSize))), - false); + setMaxFps(parseIntProperty(props, "maxFps", maxFps), false); + setInactivityFpsLimit(parseIntProperty(props, "inactivityFpsLimit", + inactivityFpsLimit), false); + setVsync(parseBooleanProperty(props, "vsync", vsync), false); + setHdrOutput(parseBooleanProperty(props, "hdrOutput", hdrOutput), false); + setDlssFrameGeneration(parseBooleanProperty(props, "dlssFrameGeneration", + dlssFrameGeneration), false); + setOutputScale2x(parseBooleanProperty(props, "outputScale2x", outputScale2x), false); + setSimplifiedIndirect(parseBooleanProperty(props, "simplifiedIndirect", + simplifiedIndirect), false); + setReflexEnabled(parseBooleanProperty(props, "reflexEnabled", reflexEnabled), false); + setReflexBoost(parseBooleanProperty(props, "reflexBoost", reflexBoost), false); + setVrrMode(parseBooleanProperty(props, "vrrMode", vrrMode), false); + qualityLevel = parseIntProperty(props, "qualityLevel", qualityLevel); + dlssMode = clamp(parseIntProperty(props, "dlssMode", dlssMode), 0, 3); + upscalerType = clamp(parseIntProperty(props, "upscalerType", upscalerType), 0, 1); + upscalerQuality = clamp(parseIntProperty(props, "upscalerQuality", upscalerQuality), 0, + 3); + denoiserMode = clamp(parseIntProperty(props, "denoiserMode", denoiserMode), 0, 3); + setRayBounces(parseIntProperty(props, "rayBounces", rayBounces), false); + setChunkBuildingBatchSize(parseIntProperty(props, "chunkBuildingBatchSize", + chunkBuildingBatchSize), false); setChunkBuildingTotalBatches( - Integer.parseInt(props.getProperty("chunkBuildingTotalBatches", - String.valueOf(chunkBuildingTotalBatches))), false); + parseIntProperty(props, "chunkBuildingTotalBatches", chunkBuildingTotalBatches), + false); + + if (hasInvalidOptionValues()) { + RadianceClient.LOGGER.warn( + "Invalid values were found in {} for keys {}. Defaults were applied and the file will be rewritten.", + path, invalidOptionKeys); + } overwriteConfig(); // System.out.println("Successfully read options: " + path); @@ -93,12 +305,63 @@ public static void readOptions() { } } + public static boolean hasInvalidOptionValues() { + return !invalidOptionKeys.isEmpty(); + } + + public static List getInvalidOptionKeys() { + return List.copyOf(invalidOptionKeys); + } + + private static void clearInvalidOptionWarnings() { + invalidOptionKeys.clear(); + } + + private static void recordInvalidOption(String key, String rawValue) { + if (!invalidOptionKeys.contains(key)) { + invalidOptionKeys.add(key); + } + RadianceClient.LOGGER.warn( + "Invalid options.properties value for '{}': '{}'. Falling back to the default value.", + key, rawValue); + } + + private static int parseIntProperty(Properties props, String key, int fallback) { + String rawValue = props.getProperty(key); + if (rawValue == null || rawValue.isBlank()) { + return fallback; + } + try { + return Integer.parseInt(rawValue.trim()); + } catch (NumberFormatException e) { + recordInvalidOption(key, rawValue); + return fallback; + } + } + + private static boolean parseBooleanProperty(Properties props, String key, boolean fallback) { + String rawValue = props.getProperty(key); + if (rawValue == null || rawValue.isBlank()) { + return fallback; + } + if ("true".equalsIgnoreCase(rawValue.trim())) { + return true; + } + if ("false".equalsIgnoreCase(rawValue.trim())) { + return false; + } + recordInvalidOption(key, rawValue); + return fallback; + } + public static void overwriteConfig() { Path path = RadianceClient.radianceDir.resolve(OPTION_PROPERTIES); Properties props = new Properties(); props.setProperty("maxFps", String.valueOf(maxFps)); props.setProperty("inactivityFpsLimit", String.valueOf(inactivityFpsLimit)); props.setProperty("vsync", String.valueOf(vsync)); + props.setProperty("hdrOutput", String.valueOf(hdrOutput)); + props.setProperty("qualityLevel", String.valueOf(qualityLevel)); props.setProperty("dlssMode", String.valueOf(dlssMode)); props.setProperty("upscalerType", String.valueOf(upscalerType)); props.setProperty("upscalerQuality", String.valueOf(upscalerQuality)); @@ -106,6 +369,12 @@ public static void overwriteConfig() { props.setProperty("rayBounces", String.valueOf(rayBounces)); props.setProperty("chunkBuildingBatchSize", String.valueOf(chunkBuildingBatchSize)); props.setProperty("chunkBuildingTotalBatches", String.valueOf(chunkBuildingTotalBatches)); + props.setProperty("dlssFrameGeneration", String.valueOf(dlssFrameGeneration)); + props.setProperty("outputScale2x", String.valueOf(outputScale2x)); + props.setProperty("simplifiedIndirect", String.valueOf(simplifiedIndirect)); + props.setProperty("reflexEnabled", String.valueOf(reflexEnabled)); + props.setProperty("reflexBoost", String.valueOf(reflexBoost)); + props.setProperty("vrrMode", String.valueOf(vrrMode)); try { Files.createDirectories(path.getParent()); @@ -172,4 +441,871 @@ public static void setChunkBuildingTotalBatches(int chunkBuildingTotalBatches, b overwriteConfig(); } } + + public native static void nativeSetHdrOutput(boolean hdrOutput, boolean write); + + public static void setHdrOutput(boolean hdrOutput, boolean write) { + Options.hdrOutput = hdrOutput; + nativeSetHdrOutput(hdrOutput, write); + if (write) { + overwriteConfig(); + } + } + + public native static void nativeSetDlssFrameGeneration(boolean dlssFrameGeneration, boolean write); + public native static boolean nativeHasDlssFrameGenerationAvailable(); + public native static void nativeSetOutputScale2x(boolean enabled, boolean write); + public native static void nativeSetSimplifiedIndirect(boolean enabled, boolean write); + public native static void nativeSetReflexEnabled(boolean enabled, boolean write); + public native static void nativeSetReflexBoost(boolean enabled, boolean write); + public native static boolean nativeIsReflexSupported(); + public native static void nativeSetVrrMode(boolean enabled, boolean write); + public native static int nativeGetDisplayRefreshRate(); + + public static void setDlssFrameGeneration(boolean dlssFrameGeneration, boolean write) { + if (write && dlssFrameGeneration && !nativeHasDlssFrameGenerationAvailable()) { + RadianceClient.LOGGER.warn( + "DLSS Frame Generation was requested, but the current system/runtime does not expose it. Keeping the option disabled."); + dlssFrameGeneration = false; + } + Options.dlssFrameGeneration = dlssFrameGeneration; + nativeSetDlssFrameGeneration(dlssFrameGeneration, write); + if (write) { + overwriteConfig(); + } + } + + public static void setOutputScale2x(boolean enabled, boolean write) { + outputScale2x = enabled; + nativeSetOutputScale2x(enabled, write); + if (write) { + overwriteConfig(); + } + } + + public static void setSimplifiedIndirect(boolean enabled, boolean write) { + simplifiedIndirect = enabled; + nativeSetSimplifiedIndirect(enabled, write); + if (write) { + overwriteConfig(); + } + } + + public static boolean isReflexSupported() { + try { + return nativeIsReflexSupported(); + } catch (UnsatisfiedLinkError e) { + return false; + } + } + + public static void setReflexEnabled(boolean enabled, boolean write) { + if (write && enabled && !isReflexSupported()) { + RadianceClient.LOGGER.warn( + "NVIDIA Reflex was requested, but the current runtime does not expose Streamline Reflex support. Keeping the option disabled."); + enabled = false; + } + reflexEnabled = enabled; + nativeSetReflexEnabled(enabled, write); + if (write) { + overwriteConfig(); + } + } + + public static void setReflexBoost(boolean enabled, boolean write) { + reflexBoost = enabled; + nativeSetReflexBoost(enabled, write); + if (write) { + overwriteConfig(); + } + } + + public static void setVrrMode(boolean enabled, boolean write) { + vrrMode = enabled; + nativeSetVrrMode(enabled, write); + if (write) { + overwriteConfig(); + } + } + + public static int getDisplayRefreshRate() { + try { + return nativeGetDisplayRefreshRate(); + } catch (UnsatisfiedLinkError e) { + return 0; + } + } + + public native static void nativeSetRayBounces(int rayBounces, boolean write); + + public static void setRayBounces(int rayBounces, boolean write) { + Options.rayBounces = rayBounces; + nativeSetRayBounces(rayBounces, write); + if (write) { + overwriteConfig(); + } + } + + public static void setDlssMode(int dlssMode, boolean write) { + Options.dlssMode = Math.max(0, Math.min(3, dlssMode)); + String mappedMode = switch (Options.dlssMode) { + case 0 -> "render_pipeline.module.dlss.attribute.mode.performance"; + case 1 -> "render_pipeline.module.dlss.attribute.mode.balanced"; + case 2 -> "render_pipeline.module.dlss.attribute.mode.quality"; + default -> "render_pipeline.module.dlss.attribute.mode.dlaa"; + }; + setAttr(DLSS_MODULE, DLSS_MODE_ATTRIBUTE, mappedMode); + if (write) { + overwriteConfig(); + } + } + + public static void applyQualityProfile(boolean rebuildPipeline) { + applyQualityProfile(QualityLevel.fromId(qualityLevel), rebuildPipeline, false); + } + + public static void setQualityLevel(QualityLevel qualityLevel, boolean write) { + applyQualityProfile(qualityLevel, true, write); + } + + private static void applyQualityProfile(QualityLevel level, boolean rebuildPipeline, + boolean writeOptions) { + QualityLevel resolvedLevel = level == null ? QualityLevel.BALANCED : level; + Options.qualityLevel = resolvedLevel.getId(); + + boolean pipelineChanged = false; + if (Pipeline.INSTANCE.getModuleEntries() != null) { + boolean shouldSwitchPreset = writeOptions || Pipeline.INSTANCE.getModules().isEmpty(); + if (shouldSwitchPreset) { + pipelineChanged |= preparePresetForQuality(resolvedLevel); + } + if (!Pipeline.INSTANCE.getModules().isEmpty()) { + pipelineChanged |= applyPipelineQualityProfile(resolvedLevel); + } + } + + if (writeOptions) { + overwriteConfig(); + } + if (pipelineChanged && rebuildPipeline) { + Pipeline.savePipeline(); + Pipeline.build(); + } + } + + private static boolean applyPipelineQualityProfile(QualityLevel level) { + return switch (level) { + case FLUENT -> applyFluentQualityProfile(); + case PERFORMANCE -> applyPerformanceQualityProfile(); + case BALANCED -> applyBalancedQualityProfile(); + case HIGH -> applyHighQualityProfile(); + case ULTRA -> applyUltraQualityProfile(); + case EXTREME -> applyExtremeQualityProfile(); + }; + } + + private static boolean preparePresetForQuality(QualityLevel level) { + String presetName = selectPresetForQuality(level); + if (presetName == null) { + return false; + } + + String activePreset = Pipeline.processPresetName(Pipeline.getActivePreset()); + if (Pipeline.getPipelineMode() == Pipeline.PipelineMode.PRESET + && Objects.equals(activePreset, presetName) + && !Pipeline.INSTANCE.getModules().isEmpty()) { + return false; + } + + Pipeline.preparePresetMode(presetName); + return true; + } + + private static String selectPresetForQuality(QualityLevel level) { + return switch (level) { + case HIGH, ULTRA, EXTREME -> firstAvailablePreset( + Presets.RT_DLSSRR.key, + Presets.RT_NRD_XESS.key, + Presets.RT_NRD_FSR.key, + Presets.RT_NRD.key); + default -> firstAvailablePreset( + Presets.RT_NRD_FSR.key, + Presets.RT_NRD_XESS.key, + Presets.RT_NRD.key, + Presets.RT_DLSSRR.key); + }; + } + + public static String getPreferredPresetForCurrentQuality() { + return selectPresetForQuality(QualityLevel.fromId(qualityLevel)); + } + + public static boolean shouldUseDlssPresetForCurrentQuality() { + return Objects.equals(getPreferredPresetForCurrentQuality(), Presets.RT_DLSSRR.key); + } + + private static String firstAvailablePreset(String... presetNames) { + for (String presetName : presetNames) { + if (Pipeline.isPresetAvailable(presetName)) { + return presetName; + } + } + return null; + } + + private static boolean applyFluentQualityProfile() { + setRayBounces(2, false); + setChunkBuildingBatchSize(18, false); + setChunkBuildingTotalBatches(20, false); + dlssMode = 1; + upscalerQuality = 3; + denoiserMode = 2; + + boolean changed = false; + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_MESHING_MODE, + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GREEDY_MERGE_MAX_SPAN, "24"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_GEOMETRY_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.simplified_shell"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_START_DISTANCE_CHUNKS, "32"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.flat_surface"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GLASS_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FOLIAGE_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DECORATION_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLAS_INCLUSION_MODE, + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_only"); + changed |= setAttr(RAY_TRACING_MODULE, RT_REFLECTION_RAY_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.water_glass_metal"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIFFUSE_GI_MODE, + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.low_cost_hybrid"); + changed |= setAttr(RAY_TRACING_MODULE, RT_CLOUD_VOLUME_MODE, + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.native"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_UPDATE_INTERVAL_FRAMES, "4"); + changed |= setAttr(RAY_TRACING_MODULE, RT_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES, "2"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_UPDATE_INTERVAL_FRAMES, "3"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TRANSPARENT_SPLIT_MODE, + "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.deterministic"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW_STRENGTH, "0.42"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH, "0.20"); + changed |= setAttr(RAY_TRACING_MODULE, RT_NUM_RAY_BOUNCES, "2"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_JITTER, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PBR_SAMPLING_MODE, + "render_pipeline.module.ray_tracing.attribute.pbr_sampling.bilinear"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_SHARC, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES, + "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BASIC_RADIANCE, "5.8"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIRECT_LIGHT_STRENGTH, "1.0"); + changed |= setAttr(RAY_TRACING_MODULE, RT_INDIRECT_LIGHT_STRENGTH, "16.5"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SIGMA_SCALE, "4.6"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SENSITIVITY, "3.6"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_ROUGHNESS_THRESHOLD, "0.16"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_MIN_ACCUMULATED_FRAME_NUM, "1"); + changed |= setAttr(NRD_MODULE, NRD_MAX_ACCUMULATED_FRAME_NUM, "24"); + changed |= setAttr(NRD_MODULE, NRD_MAX_FAST_ACCUMULATED_FRAME_NUM, "1"); + changed |= setAttr(NRD_MODULE, NRD_MAX_STABILIZED_FRAME_NUM, "28"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_FRAME_NUM, "1"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_BASE_PIXEL_STRIDE, "10"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_ALTERNATE_PIXEL_STRIDE, "12"); + changed |= setAttr(NRD_MODULE, NRD_FAST_HISTORY_CLAMPING_SIGMA_SCALE, "1.35"); + changed |= setAttr(NRD_MODULE, NRD_DIFFUSE_PREPASS_BLUR_RADIUS, "18.0"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PREPASS_BLUR_RADIUS, "26.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_HIT_DISTANCE_WEIGHT, "0.12"); + changed |= setAttr(NRD_MODULE, NRD_MIN_BLUR_RADIUS, "1.2"); + changed |= setAttr(NRD_MODULE, NRD_MAX_BLUR_RADIUS, "48.0"); + changed |= setAttr(NRD_MODULE, NRD_LOBE_ANGLE_FRACTION, "0.19"); + changed |= setAttr(NRD_MODULE, NRD_ROUGHNESS_FRACTION, "0.18"); + changed |= setAttr(NRD_MODULE, NRD_PLANE_DISTANCE_SENSITIVITY, "0.040"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MIN, "0.44"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MAX, "0.82"); + changed |= setAttr(NRD_MODULE, NRD_FIREFLY_SUPPRESSOR_MIN_RELATIVE_SCALE, "2.4"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_DIFFUSE, "5.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_SPECULAR, "5.0"); + changed |= setAttr(NRD_MODULE, NRD_HIT_DISTANCE_RECONSTRUCTION_MODE, "5x5"); + changed |= setAttr(NRD_MODULE, NRD_ENABLE_ANTI_FIREFLY, "render_pipeline.true"); + changed |= setAttr(FSR3_MODULE, FSR3_QUALITY_MODE, + "render_pipeline.module.fsr_upscaler.attribute.quality_mode.performance"); + changed |= setAttr(FSR3_MODULE, FSR3_SHARPNESS, "0.60"); + changed |= setAttr(XESS_MODULE, XESS_QUALITY_MODE, + "render_pipeline.module.xess_sr.attribute.quality_mode.performance"); + changed |= setAttr(XESS_MODULE, XESS_PRE_EXPOSURE, "1.00"); + changed |= setAttr(DLSS_MODULE, DLSS_MODE_ATTRIBUTE, + "render_pipeline.module.dlss.attribute.mode.performance"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_METHOD, + "render_pipeline.module.tone_mapping.attribute.method.pbr_neutral"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIDDLE_GREY, "0.20"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_UP_SPEED, "9.5"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_DOWN_SPEED, "8.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MIN, "-11.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MAX, "5.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOW_PERCENT, "0.010"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_HIGH_PERCENT, "0.985"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIN_EXPOSURE, "0.03"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MAX_EXPOSURE, "1.7"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_ENABLE_AUTO_EXPOSURE, "render_pipeline.true"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_METERING_MODE, + "render_pipeline.module.tone_mapping.attribute.exposure_metering_mode.global"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CENTER_METERING_PERCENT, "26.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MANUAL_EXPOSURE, "1.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_BIAS, "-0.10"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_SATURATION, "0.97"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_WHITE_POINT, "24.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CLAMP_OUTPUT, "render_pipeline.true"); + return changed; + } + + private static boolean applyPerformanceQualityProfile() { + setRayBounces(3, false); + setChunkBuildingBatchSize(18, false); + setChunkBuildingTotalBatches(20, false); + dlssMode = 0; + upscalerQuality = 2; + denoiserMode = 2; + + boolean changed = false; + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_MESHING_MODE, + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GREEDY_MERGE_MAX_SPAN, "28"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_GEOMETRY_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.simplified_shell"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_START_DISTANCE_CHUNKS, "40"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.flat_surface"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GLASS_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FOLIAGE_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DECORATION_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLAS_INCLUSION_MODE, + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_and_shadow"); + changed |= setAttr(RAY_TRACING_MODULE, RT_REFLECTION_RAY_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.water_glass_metal"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIFFUSE_GI_MODE, + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.low_cost_hybrid"); + changed |= setAttr(RAY_TRACING_MODULE, RT_CLOUD_VOLUME_MODE, + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.efficient_volume"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_UPDATE_INTERVAL_FRAMES, "3"); + changed |= setAttr(RAY_TRACING_MODULE, RT_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES, "2"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_UPDATE_INTERVAL_FRAMES, "2"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TRANSPARENT_SPLIT_MODE, + "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.deterministic"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW_STRENGTH, "0.50"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH, "0.26"); + changed |= setAttr(RAY_TRACING_MODULE, RT_NUM_RAY_BOUNCES, "3"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_JITTER, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PBR_SAMPLING_MODE, + "render_pipeline.module.ray_tracing.attribute.pbr_sampling.bilinear"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_SHARC, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES, + "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BASIC_RADIANCE, "5.55"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIRECT_LIGHT_STRENGTH, "1.0"); + changed |= setAttr(RAY_TRACING_MODULE, RT_INDIRECT_LIGHT_STRENGTH, "17.0"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SIGMA_SCALE, "4.2"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SENSITIVITY, "3.3"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_ROUGHNESS_THRESHOLD, "0.12"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_MIN_ACCUMULATED_FRAME_NUM, "1"); + changed |= setAttr(NRD_MODULE, NRD_MAX_ACCUMULATED_FRAME_NUM, "32"); + changed |= setAttr(NRD_MODULE, NRD_MAX_FAST_ACCUMULATED_FRAME_NUM, "1"); + changed |= setAttr(NRD_MODULE, NRD_MAX_STABILIZED_FRAME_NUM, "36"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_FRAME_NUM, "1"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_BASE_PIXEL_STRIDE, "12"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_ALTERNATE_PIXEL_STRIDE, "12"); + changed |= setAttr(NRD_MODULE, NRD_FAST_HISTORY_CLAMPING_SIGMA_SCALE, "1.40"); + changed |= setAttr(NRD_MODULE, NRD_DIFFUSE_PREPASS_BLUR_RADIUS, "20.0"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PREPASS_BLUR_RADIUS, "30.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_HIT_DISTANCE_WEIGHT, "0.11"); + changed |= setAttr(NRD_MODULE, NRD_MIN_BLUR_RADIUS, "1.1"); + changed |= setAttr(NRD_MODULE, NRD_MAX_BLUR_RADIUS, "60.0"); + changed |= setAttr(NRD_MODULE, NRD_LOBE_ANGLE_FRACTION, "0.18"); + changed |= setAttr(NRD_MODULE, NRD_ROUGHNESS_FRACTION, "0.17"); + changed |= setAttr(NRD_MODULE, NRD_PLANE_DISTANCE_SENSITIVITY, "0.034"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MIN, "0.46"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MAX, "0.84"); + changed |= setAttr(NRD_MODULE, NRD_FIREFLY_SUPPRESSOR_MIN_RELATIVE_SCALE, "2.3"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_DIFFUSE, "4.5"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_SPECULAR, "4.5"); + changed |= setAttr(NRD_MODULE, NRD_HIT_DISTANCE_RECONSTRUCTION_MODE, "5x5"); + changed |= setAttr(NRD_MODULE, NRD_ENABLE_ANTI_FIREFLY, "render_pipeline.true"); + changed |= setAttr(FSR3_MODULE, FSR3_QUALITY_MODE, + "render_pipeline.module.fsr_upscaler.attribute.quality_mode.balanced"); + changed |= setAttr(FSR3_MODULE, FSR3_SHARPNESS, "0.66"); + changed |= setAttr(XESS_MODULE, XESS_QUALITY_MODE, + "render_pipeline.module.xess_sr.attribute.quality_mode.balanced"); + changed |= setAttr(XESS_MODULE, XESS_PRE_EXPOSURE, "1.00"); + changed |= setAttr(DLSS_MODULE, DLSS_MODE_ATTRIBUTE, + "render_pipeline.module.dlss.attribute.mode.performance"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_METHOD, + "render_pipeline.module.tone_mapping.attribute.method.pbr_neutral"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIDDLE_GREY, "0.19"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_UP_SPEED, "9.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_DOWN_SPEED, "8.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MIN, "-11.5"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MAX, "5.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOW_PERCENT, "0.008"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_HIGH_PERCENT, "0.988"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIN_EXPOSURE, "0.025"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MAX_EXPOSURE, "1.9"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_ENABLE_AUTO_EXPOSURE, "render_pipeline.true"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_METERING_MODE, + "render_pipeline.module.tone_mapping.attribute.exposure_metering_mode.global"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CENTER_METERING_PERCENT, "24.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MANUAL_EXPOSURE, "1.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_BIAS, "-0.05"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_SATURATION, "0.99"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_WHITE_POINT, "26.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CLAMP_OUTPUT, "render_pipeline.true"); + return changed; + } + + private static boolean applyBalancedQualityProfile() { + setRayBounces(4, false); + setChunkBuildingBatchSize(16, false); + setChunkBuildingTotalBatches(18, false); + dlssMode = 1; + upscalerQuality = 1; + denoiserMode = 2; + + boolean changed = false; + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_MESHING_MODE, + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GREEDY_MERGE_MAX_SPAN, "30"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_GEOMETRY_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.exact_chunks"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_START_DISTANCE_CHUNKS, "48"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.full_pbr"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GLASS_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FOLIAGE_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DECORATION_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLAS_INCLUSION_MODE, + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_and_shadow"); + changed |= setAttr(RAY_TRACING_MODULE, RT_REFLECTION_RAY_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.water_glass_metal"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIFFUSE_GI_MODE, + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.low_cost_hybrid"); + changed |= setAttr(RAY_TRACING_MODULE, RT_CLOUD_VOLUME_MODE, + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.efficient_volume"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TRANSPARENT_SPLIT_MODE, + "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.deterministic"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW_STRENGTH, "0.60"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH, "0.34"); + changed |= setAttr(RAY_TRACING_MODULE, RT_NUM_RAY_BOUNCES, "4"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_JITTER, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PBR_SAMPLING_MODE, + "render_pipeline.module.ray_tracing.attribute.pbr_sampling.bilinear"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_SHARC, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES, + "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BASIC_RADIANCE, "5.2"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIRECT_LIGHT_STRENGTH, "1.0"); + changed |= setAttr(RAY_TRACING_MODULE, RT_INDIRECT_LIGHT_STRENGTH, "17.6"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SIGMA_SCALE, "3.9"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SENSITIVITY, "3.1"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_ROUGHNESS_THRESHOLD, "0.06"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_MIN_ACCUMULATED_FRAME_NUM, "2"); + changed |= setAttr(NRD_MODULE, NRD_MAX_ACCUMULATED_FRAME_NUM, "64"); + changed |= setAttr(NRD_MODULE, NRD_MAX_FAST_ACCUMULATED_FRAME_NUM, "4"); + changed |= setAttr(NRD_MODULE, NRD_MAX_STABILIZED_FRAME_NUM, "68"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_FRAME_NUM, "2"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_BASE_PIXEL_STRIDE, "14"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_ALTERNATE_PIXEL_STRIDE, "14"); + changed |= setAttr(NRD_MODULE, NRD_FAST_HISTORY_CLAMPING_SIGMA_SCALE, "1.7"); + changed |= setAttr(NRD_MODULE, NRD_DIFFUSE_PREPASS_BLUR_RADIUS, "26.0"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PREPASS_BLUR_RADIUS, "42.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_HIT_DISTANCE_WEIGHT, "0.10"); + changed |= setAttr(NRD_MODULE, NRD_MIN_BLUR_RADIUS, "1.0"); + changed |= setAttr(NRD_MODULE, NRD_MAX_BLUR_RADIUS, "92.0"); + changed |= setAttr(NRD_MODULE, NRD_LOBE_ANGLE_FRACTION, "0.17"); + changed |= setAttr(NRD_MODULE, NRD_ROUGHNESS_FRACTION, "0.16"); + changed |= setAttr(NRD_MODULE, NRD_PLANE_DISTANCE_SENSITIVITY, "0.023"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MIN, "0.48"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MAX, "0.86"); + changed |= setAttr(NRD_MODULE, NRD_FIREFLY_SUPPRESSOR_MIN_RELATIVE_SCALE, "2.2"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_DIFFUSE, "4.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_SPECULAR, "4.0"); + changed |= setAttr(NRD_MODULE, NRD_HIT_DISTANCE_RECONSTRUCTION_MODE, "5x5"); + changed |= setAttr(NRD_MODULE, NRD_ENABLE_ANTI_FIREFLY, "render_pipeline.true"); + changed |= setAttr(FSR3_MODULE, FSR3_QUALITY_MODE, + "render_pipeline.module.fsr_upscaler.attribute.quality_mode.quality"); + changed |= setAttr(FSR3_MODULE, FSR3_SHARPNESS, "0.74"); + changed |= setAttr(XESS_MODULE, XESS_QUALITY_MODE, + "render_pipeline.module.xess_sr.attribute.quality_mode.quality"); + changed |= setAttr(XESS_MODULE, XESS_PRE_EXPOSURE, "1.02"); + changed |= setAttr(DLSS_MODULE, DLSS_MODE_ATTRIBUTE, + "render_pipeline.module.dlss.attribute.mode.balanced"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_METHOD, + "render_pipeline.module.tone_mapping.attribute.method.pbr_neutral"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIDDLE_GREY, "0.18"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_UP_SPEED, "8.5"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_DOWN_SPEED, "7.5"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MIN, "-12.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MAX, "4.5"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOW_PERCENT, "0.006"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_HIGH_PERCENT, "0.990"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIN_EXPOSURE, "0.02"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MAX_EXPOSURE, "2.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_ENABLE_AUTO_EXPOSURE, "render_pipeline.true"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_METERING_MODE, + "render_pipeline.module.tone_mapping.attribute.exposure_metering_mode.center"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CENTER_METERING_PERCENT, "22.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MANUAL_EXPOSURE, "1.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_BIAS, "0.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_SATURATION, "1.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_WHITE_POINT, "29.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CLAMP_OUTPUT, "render_pipeline.true"); + return changed; + } + + private static boolean applyHighQualityProfile() { + setRayBounces(6, false); + setChunkBuildingBatchSize(14, false); + setChunkBuildingTotalBatches(16, false); + dlssMode = 2; + upscalerQuality = 0; + denoiserMode = 2; + + boolean changed = false; + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_MESHING_MODE, + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GREEDY_MERGE_MAX_SPAN, "34"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_GEOMETRY_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.exact_chunks"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_START_DISTANCE_CHUNKS, "64"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.full_pbr"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GLASS_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FOLIAGE_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DECORATION_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLAS_INCLUSION_MODE, + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_and_shadow"); + changed |= setAttr(RAY_TRACING_MODULE, RT_REFLECTION_RAY_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.all_materials"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIFFUSE_GI_MODE, + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.full_ray_tracing"); + changed |= setAttr(RAY_TRACING_MODULE, RT_CLOUD_VOLUME_MODE, + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.realistic_volume"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_UPDATE_INTERVAL_FRAMES, "2"); + changed |= setAttr(RAY_TRACING_MODULE, RT_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TRANSPARENT_SPLIT_MODE, + "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.stochastic"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW_STRENGTH, "0.72"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH, "0.44"); + changed |= setAttr(RAY_TRACING_MODULE, RT_NUM_RAY_BOUNCES, "6"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_JITTER, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PBR_SAMPLING_MODE, + "render_pipeline.module.ray_tracing.attribute.pbr_sampling.bilinear"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_SHARC, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES, + "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BASIC_RADIANCE, "4.95"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIRECT_LIGHT_STRENGTH, "1.0"); + changed |= setAttr(RAY_TRACING_MODULE, RT_INDIRECT_LIGHT_STRENGTH, "18.6"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SIGMA_SCALE, "3.6"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SENSITIVITY, "2.8"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_ROUGHNESS_THRESHOLD, "0.04"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_MIN_ACCUMULATED_FRAME_NUM, "3"); + changed |= setAttr(NRD_MODULE, NRD_MAX_ACCUMULATED_FRAME_NUM, "78"); + changed |= setAttr(NRD_MODULE, NRD_MAX_FAST_ACCUMULATED_FRAME_NUM, "5"); + changed |= setAttr(NRD_MODULE, NRD_MAX_STABILIZED_FRAME_NUM, "82"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_FRAME_NUM, "3"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_BASE_PIXEL_STRIDE, "12"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_ALTERNATE_PIXEL_STRIDE, "12"); + changed |= setAttr(NRD_MODULE, NRD_FAST_HISTORY_CLAMPING_SIGMA_SCALE, "1.6"); + changed |= setAttr(NRD_MODULE, NRD_DIFFUSE_PREPASS_BLUR_RADIUS, "18.0"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PREPASS_BLUR_RADIUS, "30.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_HIT_DISTANCE_WEIGHT, "0.09"); + changed |= setAttr(NRD_MODULE, NRD_MIN_BLUR_RADIUS, "0.9"); + changed |= setAttr(NRD_MODULE, NRD_MAX_BLUR_RADIUS, "72.0"); + changed |= setAttr(NRD_MODULE, NRD_LOBE_ANGLE_FRACTION, "0.16"); + changed |= setAttr(NRD_MODULE, NRD_ROUGHNESS_FRACTION, "0.15"); + changed |= setAttr(NRD_MODULE, NRD_PLANE_DISTANCE_SENSITIVITY, "0.020"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MIN, "0.50"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MAX, "0.88"); + changed |= setAttr(NRD_MODULE, NRD_FIREFLY_SUPPRESSOR_MIN_RELATIVE_SCALE, "2.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_DIFFUSE, "3.5"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_SPECULAR, "3.5"); + changed |= setAttr(NRD_MODULE, NRD_HIT_DISTANCE_RECONSTRUCTION_MODE, "5x5"); + changed |= setAttr(NRD_MODULE, NRD_ENABLE_ANTI_FIREFLY, "render_pipeline.true"); + changed |= setAttr(FSR3_MODULE, FSR3_QUALITY_MODE, + "render_pipeline.module.fsr_upscaler.attribute.quality_mode.native"); + changed |= setAttr(FSR3_MODULE, FSR3_SHARPNESS, "0.80"); + changed |= setAttr(XESS_MODULE, XESS_QUALITY_MODE, + "render_pipeline.module.xess_sr.attribute.quality_mode.ultra_quality"); + changed |= setAttr(XESS_MODULE, XESS_PRE_EXPOSURE, "1.03"); + changed |= setAttr(DLSS_MODULE, DLSS_MODE_ATTRIBUTE, + "render_pipeline.module.dlss.attribute.mode.quality"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_METHOD, + "render_pipeline.module.tone_mapping.attribute.method.pbr_neutral"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIDDLE_GREY, "0.18"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_UP_SPEED, "7.5"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_DOWN_SPEED, "6.8"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MIN, "-12.5"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MAX, "4.2"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOW_PERCENT, "0.004"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_HIGH_PERCENT, "0.992"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIN_EXPOSURE, "0.015"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MAX_EXPOSURE, "2.2"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_ENABLE_AUTO_EXPOSURE, "render_pipeline.true"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_METERING_MODE, + "render_pipeline.module.tone_mapping.attribute.exposure_metering_mode.center"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CENTER_METERING_PERCENT, "20.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MANUAL_EXPOSURE, "1.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_BIAS, "0.04"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_SATURATION, "1.02"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_WHITE_POINT, "32.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CLAMP_OUTPUT, "render_pipeline.true"); + return changed; + } + + private static boolean applyUltraQualityProfile() { + setRayBounces(8, false); + setChunkBuildingBatchSize(12, false); + setChunkBuildingTotalBatches(14, false); + dlssMode = 2; + upscalerQuality = 0; + denoiserMode = 2; + + boolean changed = false; + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_MESHING_MODE, + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GREEDY_MERGE_MAX_SPAN, "38"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_GEOMETRY_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.exact_chunks"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_START_DISTANCE_CHUNKS, "72"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.full_pbr"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GLASS_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FOLIAGE_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DECORATION_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLAS_INCLUSION_MODE, + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.all_geometry"); + changed |= setAttr(RAY_TRACING_MODULE, RT_REFLECTION_RAY_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.all_materials"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIFFUSE_GI_MODE, + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.full_ray_tracing"); + changed |= setAttr(RAY_TRACING_MODULE, RT_CLOUD_VOLUME_MODE, + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.realistic_volume"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TRANSPARENT_SPLIT_MODE, + "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.stochastic"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW_STRENGTH, "0.82"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH, "0.54"); + changed |= setAttr(RAY_TRACING_MODULE, RT_NUM_RAY_BOUNCES, "8"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_JITTER, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PBR_SAMPLING_MODE, + "render_pipeline.module.ray_tracing.attribute.pbr_sampling.bilinear"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_SHARC, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES, + "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BASIC_RADIANCE, "4.8"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIRECT_LIGHT_STRENGTH, "1.0"); + changed |= setAttr(RAY_TRACING_MODULE, RT_INDIRECT_LIGHT_STRENGTH, "19.5"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SIGMA_SCALE, "3.3"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SENSITIVITY, "2.6"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_ROUGHNESS_THRESHOLD, "0.03"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_MIN_ACCUMULATED_FRAME_NUM, "3"); + changed |= setAttr(NRD_MODULE, NRD_MAX_ACCUMULATED_FRAME_NUM, "90"); + changed |= setAttr(NRD_MODULE, NRD_MAX_FAST_ACCUMULATED_FRAME_NUM, "6"); + changed |= setAttr(NRD_MODULE, NRD_MAX_STABILIZED_FRAME_NUM, "94"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_FRAME_NUM, "3"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_BASE_PIXEL_STRIDE, "10"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_ALTERNATE_PIXEL_STRIDE, "10"); + changed |= setAttr(NRD_MODULE, NRD_FAST_HISTORY_CLAMPING_SIGMA_SCALE, "1.5"); + changed |= setAttr(NRD_MODULE, NRD_DIFFUSE_PREPASS_BLUR_RADIUS, "14.0"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PREPASS_BLUR_RADIUS, "24.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_HIT_DISTANCE_WEIGHT, "0.08"); + changed |= setAttr(NRD_MODULE, NRD_MIN_BLUR_RADIUS, "0.8"); + changed |= setAttr(NRD_MODULE, NRD_MAX_BLUR_RADIUS, "60.0"); + changed |= setAttr(NRD_MODULE, NRD_LOBE_ANGLE_FRACTION, "0.15"); + changed |= setAttr(NRD_MODULE, NRD_ROUGHNESS_FRACTION, "0.14"); + changed |= setAttr(NRD_MODULE, NRD_PLANE_DISTANCE_SENSITIVITY, "0.018"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MIN, "0.52"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MAX, "0.90"); + changed |= setAttr(NRD_MODULE, NRD_FIREFLY_SUPPRESSOR_MIN_RELATIVE_SCALE, "1.9"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_DIFFUSE, "3.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_SPECULAR, "3.0"); + changed |= setAttr(NRD_MODULE, NRD_HIT_DISTANCE_RECONSTRUCTION_MODE, "5x5"); + changed |= setAttr(NRD_MODULE, NRD_ENABLE_ANTI_FIREFLY, "render_pipeline.true"); + changed |= setAttr(FSR3_MODULE, FSR3_QUALITY_MODE, + "render_pipeline.module.fsr_upscaler.attribute.quality_mode.native"); + changed |= setAttr(FSR3_MODULE, FSR3_SHARPNESS, "0.84"); + changed |= setAttr(XESS_MODULE, XESS_QUALITY_MODE, + "render_pipeline.module.xess_sr.attribute.quality_mode.ultra_quality_plus"); + changed |= setAttr(XESS_MODULE, XESS_PRE_EXPOSURE, "1.04"); + changed |= setAttr(DLSS_MODULE, DLSS_MODE_ATTRIBUTE, + "render_pipeline.module.dlss.attribute.mode.quality"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_METHOD, + "render_pipeline.module.tone_mapping.attribute.method.pbr_neutral"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIDDLE_GREY, "0.18"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_UP_SPEED, "6.8"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_DOWN_SPEED, "6.2"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MIN, "-13.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MAX, "4.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOW_PERCENT, "0.003"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_HIGH_PERCENT, "0.994"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIN_EXPOSURE, "0.012"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MAX_EXPOSURE, "2.4"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_ENABLE_AUTO_EXPOSURE, "render_pipeline.true"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_METERING_MODE, + "render_pipeline.module.tone_mapping.attribute.exposure_metering_mode.center"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CENTER_METERING_PERCENT, "18.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MANUAL_EXPOSURE, "1.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_BIAS, "0.08"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_SATURATION, "1.03"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_WHITE_POINT, "34.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CLAMP_OUTPUT, "render_pipeline.true"); + return changed; + } + + private static boolean applyExtremeQualityProfile() { + setRayBounces(16, false); + setChunkBuildingBatchSize(10, false); + setChunkBuildingTotalBatches(12, false); + dlssMode = 3; + upscalerQuality = 0; + denoiserMode = 2; + + boolean changed = false; + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_MESHING_MODE, + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GREEDY_MERGE_MAX_SPAN, "42"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_GEOMETRY_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.exact_chunks"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_START_DISTANCE_CHUNKS, "96"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FAR_FIELD_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.full_pbr"); + changed |= setAttr(RAY_TRACING_MODULE, RT_GLASS_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_FOLIAGE_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DECORATION_PATH_MODE, + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLAS_INCLUSION_MODE, + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.all_geometry"); + changed |= setAttr(RAY_TRACING_MODULE, RT_REFLECTION_RAY_MATERIAL_MODE, + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.all_materials"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIFFUSE_GI_MODE, + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.full_ray_tracing"); + changed |= setAttr(RAY_TRACING_MODULE, RT_CLOUD_VOLUME_MODE, + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.realistic_volume"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TERRAIN_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_UPDATE_INTERVAL_FRAMES, "1"); + changed |= setAttr(RAY_TRACING_MODULE, RT_TRANSPARENT_SPLIT_MODE, + "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.stochastic"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_CRIT_GLOW_STRENGTH, "1.0"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH, "0.72"); + changed |= setAttr(RAY_TRACING_MODULE, RT_NUM_RAY_BOUNCES, "16"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_JITTER, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_PBR_SAMPLING_MODE, + "render_pipeline.module.ray_tracing.attribute.pbr_sampling.bilinear"); + changed |= setAttr(RAY_TRACING_MODULE, RT_USE_SHARC, "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES, + "render_pipeline.true"); + changed |= setAttr(RAY_TRACING_MODULE, RT_BASIC_RADIANCE, "4.6"); + changed |= setAttr(RAY_TRACING_MODULE, RT_DIRECT_LIGHT_STRENGTH, "1.0"); + changed |= setAttr(RAY_TRACING_MODULE, RT_INDIRECT_LIGHT_STRENGTH, "21.0"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SIGMA_SCALE, "3.0"); + changed |= setAttr(NRD_MODULE, NRD_ANTILAG_LUMINANCE_SENSITIVITY, "2.3"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_ROUGHNESS_THRESHOLD, "0.02"); + changed |= setAttr(NRD_MODULE, NRD_RESPONSIVE_ACCUMULATION_MIN_ACCUMULATED_FRAME_NUM, "3"); + changed |= setAttr(NRD_MODULE, NRD_MAX_ACCUMULATED_FRAME_NUM, "112"); + changed |= setAttr(NRD_MODULE, NRD_MAX_FAST_ACCUMULATED_FRAME_NUM, "8"); + changed |= setAttr(NRD_MODULE, NRD_MAX_STABILIZED_FRAME_NUM, "116"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_FRAME_NUM, "3"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_BASE_PIXEL_STRIDE, "8"); + changed |= setAttr(NRD_MODULE, NRD_HISTORY_FIX_ALTERNATE_PIXEL_STRIDE, "8"); + changed |= setAttr(NRD_MODULE, NRD_FAST_HISTORY_CLAMPING_SIGMA_SCALE, "1.4"); + changed |= setAttr(NRD_MODULE, NRD_DIFFUSE_PREPASS_BLUR_RADIUS, "10.0"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PREPASS_BLUR_RADIUS, "18.0"); + changed |= setAttr(NRD_MODULE, NRD_MIN_HIT_DISTANCE_WEIGHT, "0.07"); + changed |= setAttr(NRD_MODULE, NRD_MIN_BLUR_RADIUS, "0.7"); + changed |= setAttr(NRD_MODULE, NRD_MAX_BLUR_RADIUS, "44.0"); + changed |= setAttr(NRD_MODULE, NRD_LOBE_ANGLE_FRACTION, "0.14"); + changed |= setAttr(NRD_MODULE, NRD_ROUGHNESS_FRACTION, "0.13"); + changed |= setAttr(NRD_MODULE, NRD_PLANE_DISTANCE_SENSITIVITY, "0.016"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MIN, "0.55"); + changed |= setAttr(NRD_MODULE, NRD_SPECULAR_PROBABILITY_THRESHOLD_MAX, "0.92"); + changed |= setAttr(NRD_MODULE, NRD_FIREFLY_SUPPRESSOR_MIN_RELATIVE_SCALE, "1.8"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_DIFFUSE, "2.5"); + changed |= setAttr(NRD_MODULE, NRD_MIN_MATERIAL_FOR_SPECULAR, "2.5"); + changed |= setAttr(NRD_MODULE, NRD_HIT_DISTANCE_RECONSTRUCTION_MODE, "5x5"); + changed |= setAttr(NRD_MODULE, NRD_ENABLE_ANTI_FIREFLY, "render_pipeline.true"); + changed |= setAttr(FSR3_MODULE, FSR3_QUALITY_MODE, + "render_pipeline.module.fsr_upscaler.attribute.quality_mode.native"); + changed |= setAttr(FSR3_MODULE, FSR3_SHARPNESS, "0.88"); + changed |= setAttr(XESS_MODULE, XESS_QUALITY_MODE, + "render_pipeline.module.xess_sr.attribute.quality_mode.native"); + changed |= setAttr(XESS_MODULE, XESS_PRE_EXPOSURE, "1.05"); + changed |= setAttr(DLSS_MODULE, DLSS_MODE_ATTRIBUTE, + "render_pipeline.module.dlss.attribute.mode.dlaa"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_METHOD, + "render_pipeline.module.tone_mapping.attribute.method.pbr_neutral"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIDDLE_GREY, "0.18"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_UP_SPEED, "6.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_DOWN_SPEED, "5.6"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MIN, "-13.5"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOG2_LUMINANCE_MAX, "4.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_LOW_PERCENT, "0.002"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_HIGH_PERCENT, "0.996"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MIN_EXPOSURE, "0.010"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MAX_EXPOSURE, "2.6"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_ENABLE_AUTO_EXPOSURE, "render_pipeline.true"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_METERING_MODE, + "render_pipeline.module.tone_mapping.attribute.exposure_metering_mode.center"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CENTER_METERING_PERCENT, "16.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_MANUAL_EXPOSURE, "1.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_EXPOSURE_BIAS, "0.12"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_SATURATION, "1.04"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_WHITE_POINT, "36.0"); + changed |= setAttr(TONE_MAPPING_MODULE, TM_CLAMP_OUTPUT, "render_pipeline.true"); + return changed; + } + + private static boolean setAttr(String moduleName, String attributeName, String value) { + return Pipeline.setModuleAttributeValue(moduleName, attributeName, value); + } + + private static int clamp(int value, int min, int max) { + return Math.max(min, Math.min(max, value)); + } } diff --git a/src/main/java/com/radiance/client/option/QualityLevel.java b/src/main/java/com/radiance/client/option/QualityLevel.java new file mode 100644 index 0000000..febab8a --- /dev/null +++ b/src/main/java/com/radiance/client/option/QualityLevel.java @@ -0,0 +1,58 @@ +package com.radiance.client.option; + +import static com.radiance.client.option.Options.QUALITY_LEVEL_BALANCED; +import static com.radiance.client.option.Options.QUALITY_LEVEL_EXTREME; +import static com.radiance.client.option.Options.QUALITY_LEVEL_FLUENT; +import static com.radiance.client.option.Options.QUALITY_LEVEL_PERFORMANCE; +import static com.radiance.client.option.Options.QUALITY_LEVEL_QUALITY; +import static com.radiance.client.option.Options.QUALITY_LEVEL_ULTRA; + +import com.mojang.serialization.Codec; +import net.minecraft.util.StringIdentifiable; +import net.minecraft.util.TranslatableOption; + +public enum QualityLevel implements TranslatableOption, StringIdentifiable { + FLUENT(5, "fluent", QUALITY_LEVEL_FLUENT), + PERFORMANCE(0, "performance", QUALITY_LEVEL_PERFORMANCE), + BALANCED(1, "balanced", QUALITY_LEVEL_BALANCED), + HIGH(2, "high", QUALITY_LEVEL_QUALITY), + ULTRA(3, "ultra", QUALITY_LEVEL_ULTRA), + EXTREME(4, "extreme", QUALITY_LEVEL_EXTREME); + + public static final Codec Codec = + StringIdentifiable.createCodec(QualityLevel::values); + + private final int id; + private final String name; + private final String translationKey; + + QualityLevel(int id, String name, String translationKey) { + this.id = id; + this.name = name; + this.translationKey = translationKey; + } + + public static QualityLevel fromId(int id) { + for (QualityLevel value : values()) { + if (value.id == id) { + return value; + } + } + return BALANCED; + } + + @Override + public int getId() { + return id; + } + + @Override + public String getTranslationKey() { + return translationKey; + } + + @Override + public String asString() { + return name; + } +} diff --git a/src/main/java/com/radiance/client/pipeline/Module.java b/src/main/java/com/radiance/client/pipeline/Module.java index ac5ecdf..21d4a8c 100644 --- a/src/main/java/com/radiance/client/pipeline/Module.java +++ b/src/main/java/com/radiance/client/pipeline/Module.java @@ -23,20 +23,42 @@ public String toString() { } public ImageConfig getInputImageConfig(String name) { + ImageConfig imageConfig = findInputImageConfig(name); + if (imageConfig != null) { + return imageConfig; + } + throw new RuntimeException("No such image config: " + name); + } + + public ImageConfig findInputImageConfig(String name) { + if (inputImageConfigs == null || name == null) { + return null; + } for (ImageConfig imageConfig : inputImageConfigs) { if (imageConfig.name.equals(name)) { return imageConfig; } } - throw new RuntimeException("No such image config: " + name); + return null; } public ImageConfig getOutputImageConfig(String name) { + ImageConfig imageConfig = findOutputImageConfig(name); + if (imageConfig != null) { + return imageConfig; + } + throw new RuntimeException("No such image config: " + name); + } + + public ImageConfig findOutputImageConfig(String name) { + if (outputImageConfigs == null || name == null) { + return null; + } for (ImageConfig imageConfig : outputImageConfigs) { if (imageConfig.name.equals(name)) { return imageConfig; } } - throw new RuntimeException("No such image config: " + name); + return null; } } diff --git a/src/main/java/com/radiance/client/pipeline/ModuleEntry.java b/src/main/java/com/radiance/client/pipeline/ModuleEntry.java index abe43e4..1c09d50 100644 --- a/src/main/java/com/radiance/client/pipeline/ModuleEntry.java +++ b/src/main/java/com/radiance/client/pipeline/ModuleEntry.java @@ -8,6 +8,8 @@ import java.io.InputStream; import java.net.JarURLConnection; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; @@ -55,6 +57,7 @@ public static Map loadAllModuleEntries() throws Exception { } } else if ("jar".equals(protocol)) { JarURLConnection jarConn = (JarURLConnection) url.openConnection(); + jarConn.setUseCaches(false); try (JarFile jarFile = jarConn.getJarFile()) { Enumeration jarEntries = jarFile.entries(); @@ -96,8 +99,7 @@ public Module loadModule() { LoaderOptions options = new LoaderOptions(); Yaml yaml = new Yaml(new Constructor(Module.class, options)); - try (InputStream inputStream = getClass().getClassLoader() - .getResourceAsStream(this.resourcePath)) { + try (InputStream inputStream = openModuleStream()) { if (inputStream == null) { throw new RuntimeException("Module not found in resource: " + this.resourcePath); } @@ -114,6 +116,17 @@ public Module loadModule() { } } + private InputStream openModuleStream() throws IOException { + if (RadianceClient.radianceDir != null && resourcePath != null && !resourcePath.isBlank()) { + Path diskPath = RadianceClient.radianceDir.resolve(resourcePath); + if (Files.exists(diskPath)) { + return Files.newInputStream(diskPath); + } + } + + return getClass().getClassLoader().getResourceAsStream(this.resourcePath); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/com/radiance/client/pipeline/Pipeline.java b/src/main/java/com/radiance/client/pipeline/Pipeline.java index bd755b3..175c68d 100644 --- a/src/main/java/com/radiance/client/pipeline/Pipeline.java +++ b/src/main/java/com/radiance/client/pipeline/Pipeline.java @@ -2,6 +2,7 @@ import com.radiance.client.RadianceClient; import com.radiance.client.constant.VulkanConstants; +import com.radiance.client.option.Options; import com.radiance.client.pipeline.config.AttributeConfig; import com.radiance.client.pipeline.config.ImageConfig; @@ -45,6 +46,7 @@ public class Pipeline { private PipelineMode mode = PipelineMode.PRESET; private String activePresetName = null; + private static boolean buildRecoveryInProgress = false; private Pipeline() { } @@ -197,7 +199,8 @@ public static void connect(ImageConfig src, ImageConfig dst) { } public static void connectOutput(ImageConfig src) { - if (!Objects.equals(src.format, "R8G8B8A8_UNORM")) { + if (!Objects.equals(src.format, "R8G8B8A8_UNORM") + && !Objects.equals(src.format, "R16G16B16A16_SFLOAT")) { throw new RuntimeException("Invalid output format."); } src.finalOutput = true; @@ -205,101 +208,124 @@ public static void connectOutput(ImageConfig src) { public static void build() { try { - Map dstTosrcMap = new HashMap<>(); - for (Map.Entry> entry : INSTANCE.moduleConnections.entrySet()) { - ImageConfig source = entry.getKey(); - for (ImageConfig dest : entry.getValue()) { - if (dstTosrcMap.containsKey(dest)) { - throw new RuntimeException( - "Input config '" + dest.name + "' has multiple sources connected!"); - } - dstTosrcMap.put(dest, source); + buildOnce(); + savePipeline(); + } catch (Exception initialFailure) { + RadianceClient.LOGGER.error("Failed to build render pipeline", initialFailure); + if (buildRecoveryInProgress) { + throw new IllegalStateException("Failed to build render pipeline", initialFailure); + } + + buildRecoveryInProgress = true; + try { + clear(); + assembleBestAvailablePreset( + "Pipeline build failed (" + initialFailure.getMessage() + ")."); + buildOnce(); + savePipeline(); + } catch (Exception recoveryFailure) { + RadianceClient.LOGGER.error("Fallback pipeline build also failed", + recoveryFailure); + IllegalStateException combinedFailure = new IllegalStateException( + "Fallback pipeline build also failed", recoveryFailure); + combinedFailure.addSuppressed(initialFailure); + throw combinedFailure; + } finally { + buildRecoveryInProgress = false; + } + } + } + + private static void buildOnce() { + Map dstTosrcMap = new HashMap<>(); + for (Map.Entry> entry : INSTANCE.moduleConnections.entrySet()) { + ImageConfig source = entry.getKey(); + for (ImageConfig dest : entry.getValue()) { + if (dstTosrcMap.containsKey(dest)) { + throw new RuntimeException( + "Input config '" + dest.name + "' has multiple sources connected!"); } + dstTosrcMap.put(dest, source); } + } - ImageConfig finalOutputConfig = null; - Module finalModule = null; + ImageConfig finalOutputConfig = null; + Module finalModule = null; - for (Module module : INSTANCE.modules) { - for (ImageConfig conf : module.outputImageConfigs) { - if (conf.finalOutput) { - if (finalOutputConfig != null) { - throw new RuntimeException( - "Multiple final outputs detected! Only one allows."); - } - finalOutputConfig = conf; - finalModule = module; + for (Module module : INSTANCE.modules) { + for (ImageConfig conf : module.outputImageConfigs) { + if (conf.finalOutput) { + if (finalOutputConfig != null) { + throw new RuntimeException( + "Multiple final outputs detected! Only one allows."); } + finalOutputConfig = conf; + finalModule = module; } } + } - if (finalOutputConfig == null) { - throw new RuntimeException("No final output configured."); - } + if (finalOutputConfig == null) { + throw new RuntimeException("No final output configured."); + } - // topological sort - List sortedModules = new ArrayList<>(); - Set visited = new HashSet<>(); - Set visiting = new HashSet<>(); + // topological sort + List sortedModules = new ArrayList<>(); + Set visited = new HashSet<>(); + Set visiting = new HashSet<>(); - topologicalSort(finalModule, dstTosrcMap, visited, visiting, sortedModules); + topologicalSort(finalModule, dstTosrcMap, visited, visiting, sortedModules); - // integrity check - for (Module m : sortedModules) { - for (ImageConfig inputConf : m.inputImageConfigs) { - if (!dstTosrcMap.containsKey(inputConf)) { - throw new RuntimeException( - "Module '" + m.name + "' has unconnected input: " + inputConf.name); - } + // integrity check + for (Module m : sortedModules) { + for (ImageConfig inputConf : m.inputImageConfigs) { + if (!dstTosrcMap.containsKey(inputConf)) { + throw new RuntimeException( + "Module '" + m.name + "' has unconnected input: " + inputConf.name); } } + } - // image list - List imageFormatList = new ArrayList<>(); - Map configToImageIdMap = new HashMap<>(); - - int finalFmtId = VulkanConstants.VkFormat.getVkFormatByName(finalOutputConfig.format); - imageFormatList.add(finalFmtId); - configToImageIdMap.put(finalOutputConfig, 0); - - for (Module module : sortedModules) { - for (ImageConfig outConfig : module.outputImageConfigs) { - int imgId; - if (configToImageIdMap.containsKey(outConfig)) { - imgId = configToImageIdMap.get(outConfig); - - if (imgId != 0) { - throw new RuntimeException(); - } - } else { - imgId = imageFormatList.size(); - imageFormatList.add( - VulkanConstants.VkFormat.getVkFormatByName(outConfig.format)); - configToImageIdMap.put(outConfig, imgId); - } + // image list + List imageFormatList = new ArrayList<>(); + Map configToImageIdMap = new HashMap<>(); + + int finalFmtId = VulkanConstants.VkFormat.getVkFormatByName(finalOutputConfig.format); + imageFormatList.add(finalFmtId); + configToImageIdMap.put(finalOutputConfig, 0); - List connectedInputs = INSTANCE.moduleConnections.get(outConfig); - if (connectedInputs != null && !connectedInputs.isEmpty()) { - for (ImageConfig inputConf : connectedInputs) { - configToImageIdMap.put(inputConf, imgId); - } + for (Module module : sortedModules) { + for (ImageConfig outConfig : module.outputImageConfigs) { + int imgId; + if (configToImageIdMap.containsKey(outConfig)) { + imgId = configToImageIdMap.get(outConfig); + + if (imgId != 0) { + throw new RuntimeException(); } + } else { + imgId = imageFormatList.size(); + imageFormatList.add( + VulkanConstants.VkFormat.getVkFormatByName(outConfig.format)); + configToImageIdMap.put(outConfig, imgId); } - } - List> moduleAttributes = new ArrayList<>(); - for (Module m : sortedModules) { - moduleAttributes.add( - m.attributeConfigs != null ? m.attributeConfigs : new ArrayList<>()); + List connectedInputs = INSTANCE.moduleConnections.get(outConfig); + if (connectedInputs != null && !connectedInputs.isEmpty()) { + for (ImageConfig inputConf : connectedInputs) { + configToImageIdMap.put(inputConf, imgId); + } + } } + } - buildNative(sortedModules, imageFormatList, configToImageIdMap, moduleAttributes); - } catch (Exception e) { - RadianceClient.LOGGER.error(e.toString()); - Pipeline.loadPipeline(); - } finally { - savePipeline(); + List> moduleAttributes = new ArrayList<>(); + for (Module m : sortedModules) { + moduleAttributes.add( + m.attributeConfigs != null ? m.attributeConfigs : new ArrayList<>()); } + + buildNative(sortedModules, imageFormatList, configToImageIdMap, moduleAttributes); } private static void topologicalSort(Module current, @@ -832,8 +858,136 @@ private static void assemblePresetByKeyInternal(String presetName) { public static native void collectNativeModules(); + public static native void recollectNativeModules(); + public static native boolean isNativeModuleAvailable(String name); + public static Module getModuleByName(String name) { + if (name == null) { + return null; + } + + for (Module module : INSTANCE.modules) { + if (Objects.equals(module.name, name)) { + return module; + } + } + + return null; + } + + public static String getModuleAttributeValue(String moduleName, String attributeName, + String fallback) { + Module module = getModuleByName(moduleName); + if (module == null || module.attributeConfigs == null || attributeName == null) { + return fallback; + } + + for (AttributeConfig attributeConfig : module.attributeConfigs) { + if (!Objects.equals(attributeConfig.name, attributeName)) { + continue; + } + + return attributeConfig.value != null ? attributeConfig.value : fallback; + } + + return fallback; + } + + public static String getDefaultModuleAttributeValue(String moduleName, String attributeName, + String fallback) { + if (INSTANCE.moduleEntries == null || moduleName == null || attributeName == null) { + return fallback; + } + + ModuleEntry moduleEntry = INSTANCE.moduleEntries.get(moduleName); + if (moduleEntry == null) { + return fallback; + } + + Module module = moduleEntry.loadModule(); + if (module == null || module.attributeConfigs == null) { + return fallback; + } + + for (AttributeConfig attributeConfig : module.attributeConfigs) { + if (!Objects.equals(attributeConfig.name, attributeName)) { + continue; + } + return attributeConfig.value != null ? attributeConfig.value : fallback; + } + + return fallback; + } + + public static int getModuleAttributeIntValue(String moduleName, String attributeName, + int fallback) { + String value = getModuleAttributeValue(moduleName, attributeName, null); + if (value == null) { + return fallback; + } + + try { + return Integer.parseInt(value); + } catch (NumberFormatException ignored) { + return fallback; + } + } + + public static float getModuleAttributeFloatValue(String moduleName, String attributeName, + float fallback) { + String value = getModuleAttributeValue(moduleName, attributeName, null); + if (value == null) { + return fallback; + } + + try { + return Float.parseFloat(value); + } catch (NumberFormatException ignored) { + return fallback; + } + } + + public static boolean getModuleAttributeBooleanValue(String moduleName, String attributeName, + boolean fallback) { + String value = getModuleAttributeValue(moduleName, attributeName, null); + if (value == null) { + return fallback; + } + + if (Objects.equals(value, "render_pipeline.true")) { + return true; + } + if (Objects.equals(value, "render_pipeline.false")) { + return false; + } + + return Boolean.parseBoolean(value); + } + + public static boolean setModuleAttributeValue(String moduleName, String attributeName, + String value) { + Module module = getModuleByName(moduleName); + if (module == null || module.attributeConfigs == null || attributeName == null) { + return false; + } + + for (AttributeConfig attributeConfig : module.attributeConfigs) { + if (!Objects.equals(attributeConfig.name, attributeName)) { + continue; + } + + String normalizedValue = value == null ? "" : value; + if (Objects.equals(attributeConfig.value, normalizedValue)) { + return false; + } + + attributeConfig.value = normalizedValue; + return true; + } + + return false; + } public PipelineMode getMode() { return mode; @@ -864,6 +1018,13 @@ public static void switchToPipelineMode() { } public static void switchToPresetMode(String presetName) { + preparePresetMode(presetName); + + savePipeline(); + build(); + } + + public static void preparePresetMode(String presetName) { List carryOverModules = capturePresetModules(); INSTANCE.mode = PipelineMode.PRESET; @@ -879,9 +1040,6 @@ public static void switchToPresetMode(String presetName) { } applyPresetModuleOverrides(carryOverModules); - - savePipeline(); - build(); } public static void assemblePreset(String presetName) { @@ -1104,7 +1262,7 @@ private static void writeConfigStorage(PipelineConfigStorage storage) { dumperOptions.setIndent(2); Yaml yaml = new Yaml(dumperOptions); - String yamlText = yaml.dump(storage); + String yamlText = yaml.dumpAsMap(storage); try { Files.createDirectories(PIPELINE_CONFIG_PATH.getParent()); @@ -1157,6 +1315,61 @@ private static boolean hasUnavailableStoredModules(PipelineStorage pipelineStora return false; } + private static boolean hasStoredModule(PipelineStorage pipelineStorage, String moduleName) { + if (pipelineStorage == null || pipelineStorage.modules == null || moduleName == null) { + return false; + } + + for (StoredModule storedModule : pipelineStorage.modules) { + if (storedModule == null || storedModule.entryName == null) { + continue; + } + if (Objects.equals(storedModule.entryName, moduleName)) { + return true; + } + } + + return false; + } + + private static boolean hasStaleDlssState(PipelineStorage pipelineStorage) { + boolean savedPipelineHasDlss = hasStoredModule(pipelineStorage, DLSS_MODULE_NAME); + boolean currentDlssPresetActive = Options.shouldUseDlssPresetForCurrentQuality() + && isModuleAvailable(DLSS_MODULE_NAME); + + if (savedPipelineHasDlss == currentDlssPresetActive) { + return false; + } + + RadianceClient.LOGGER.warn( + "Stored pipeline DLSS state ({}) does not match the current quality preset expectation ({}). Rebuilding the default preset.", + savedPipelineHasDlss, currentDlssPresetActive); + return true; + } + + private static boolean hasDisconnectedCurrentModuleInputs() { + Map dstToSrcMap = new HashMap<>(); + for (Map.Entry> entry : INSTANCE.moduleConnections.entrySet()) { + ImageConfig src = entry.getKey(); + for (ImageConfig dst : entry.getValue()) { + dstToSrcMap.put(dst, src); + } + } + + for (Module module : INSTANCE.modules) { + for (ImageConfig inputConf : module.inputImageConfigs) { + if (!dstToSrcMap.containsKey(inputConf)) { + RadianceClient.LOGGER.warn( + "Stored pipeline has unconnected input '{}' on module '{}'. Rebuilding default pipeline.", + inputConf.name, module.name); + return true; + } + } + } + + return false; + } + private static void applyPipelineStorage(PipelineStorage pipelineStorage) { if (pipelineStorage == null) { assembleDefault(); @@ -1165,14 +1378,22 @@ private static void applyPipelineStorage(PipelineStorage pipelineStorage) { if (hasUnavailableStoredModules(pipelineStorage)) { RadianceClient.LOGGER.warn("Stored pipeline contains unavailable modules. Falling back to NRD+FSR."); + INSTANCE.mode = PipelineMode.PRESET; assembleNRDFSR(); return; } + if (hasStaleDlssState(pipelineStorage)) { + INSTANCE.mode = PipelineMode.PRESET; + assembleDefault(); + return; + } + clear(); if (pipelineStorage.modules == null || pipelineStorage.modules.isEmpty() || pipelineStorage.finalOutputModuleId == null || pipelineStorage.finalOutputImageName == null) { + INSTANCE.mode = PipelineMode.PRESET; assembleDefault(); return; } @@ -1201,12 +1422,15 @@ private static void applyPipelineStorage(PipelineStorage pipelineStorage) { Module finalModule = idToModule.get(pipelineStorage.finalOutputModuleId); if (finalModule == null) { + INSTANCE.mode = PipelineMode.PRESET; assembleDefault(); return; } - ImageConfig finalImageConfig = finalModule.getOutputImageConfig(pipelineStorage.finalOutputImageName); + ImageConfig finalImageConfig = finalModule.findOutputImageConfig( + pipelineStorage.finalOutputImageName); if (finalImageConfig == null) { + INSTANCE.mode = PipelineMode.PRESET; assembleDefault(); return; } @@ -1232,8 +1456,10 @@ private static void applyPipelineStorage(PipelineStorage pipelineStorage) { continue; } - ImageConfig srcImageConfig = srcModule.getOutputImageConfig(storedConnection.srcImageName); - ImageConfig dstImageConfig = dstModule.getInputImageConfig(storedConnection.dstImageName); + ImageConfig srcImageConfig = srcModule.findOutputImageConfig( + storedConnection.srcImageName); + ImageConfig dstImageConfig = dstModule.findInputImageConfig( + storedConnection.dstImageName); if (srcImageConfig == null || dstImageConfig == null) { continue; @@ -1245,12 +1471,20 @@ private static void applyPipelineStorage(PipelineStorage pipelineStorage) { } public static void loadPipeline() { + try { + recollectNativeModules(); + } catch (UnsatisfiedLinkError ignored) { + collectNativeModules(); + } + PipelineConfigStorage storage = loadConfigStorage(); if (storage == null) { INSTANCE.mode = PipelineMode.PRESET; - assembleDefault(); + assemblePreset(Options.getPreferredPresetForCurrentQuality()); + Options.applyQualityProfile(false); savePipeline(); + build(); return; } @@ -1269,6 +1503,11 @@ public static void loadPipeline() { if (storage.pipeline != null) { applyPipelineStorage(storage.pipeline); + if (hasDisconnectedCurrentModuleInputs()) { + assembleDefault(); + INSTANCE.mode = PipelineMode.PRESET; + savePipeline(); + } build(); return; } diff --git a/src/main/java/com/radiance/client/proxy/vulkan/BufferProxy.java b/src/main/java/com/radiance/client/proxy/vulkan/BufferProxy.java index 3481584..d6ec9a1 100644 --- a/src/main/java/com/radiance/client/proxy/vulkan/BufferProxy.java +++ b/src/main/java/com/radiance/client/proxy/vulkan/BufferProxy.java @@ -7,11 +7,14 @@ import static org.lwjgl.system.MemoryUtil.memSet; import com.mojang.blaze3d.systems.RenderSystem; +import com.radiance.client.RadianceClient; import com.radiance.client.constant.Constants; +import com.radiance.client.option.EnvironmentRenderStyles; import com.radiance.client.texture.TextureTracker; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.BuiltBuffer; import net.minecraft.client.render.Camera; @@ -25,6 +28,8 @@ public class BufferProxy { + private static final AtomicBoolean warnedAboutTextureSlotOverflow = new AtomicBoolean(false); + public static native int allocateBuffer(); public static native void initializeBuffer(int id, int size, int usageFlags); @@ -282,7 +287,7 @@ public static void updateSkyUniform(float baseColorR, float baseColorG, float ba float horizontalColorR, float horizontalColorG, float horizontalColorB, float horizontalColorA, Vector3f sunDirection, int skyType, boolean sunRisingOrSetting, boolean skyDark, boolean hasBlindnessOrDarkness, int submersionType, int moonPhase, - float rainGradient, int sunTextureID, int moonTextureID) { + float rainGradient, float cloudLayerHeight, int sunTextureID, int moonTextureID) { try (MemoryStack stack = stackPush()) { int size = 160; ByteBuffer bb = stack.malloc(size); @@ -327,9 +332,12 @@ public static void updateSkyUniform(float baseColorR, float baseColorG, float ba bb.putFloat(baseAddr, rainGradient); baseAddr += Float.BYTES; + bb.putFloat(baseAddr, EnvironmentRenderStyles.waterSurfaceMode().shaderId()); + baseAddr += Float.BYTES; + bb.putFloat(baseAddr, EnvironmentRenderStyles.cloudVolumeMode().shaderId()); baseAddr += Float.BYTES; + bb.putFloat(baseAddr, cloudLayerHeight); baseAddr += Float.BYTES; - baseAddr += Float.BYTES; // padding // AtmosphereParams baseAddr += Float.BYTES * 4 * 3; // skip @@ -350,7 +358,7 @@ public static void updateSkyUniform(float baseColorR, float baseColorG, float ba public static void updateMapping() { try (MemoryStack stack = stackPush()) { - final int elementCount = 4096; + final int elementCount = TextureTracker.MAX_TEXTURE_SLOTS; int size = elementCount * Integer.BYTES * 3; ByteBuffer bb = stack.malloc(size); long addr = memAddress(bb); @@ -363,9 +371,7 @@ public static void updateMapping() { if (sourceID >= 0 && sourceID < elementCount) { intView.put(sourceID * 3, targetID); } else { - throw new RuntimeException( - "Specular mapping sourceID " + sourceID + " out of index [0, " + ( - elementCount - 1) + "]"); + warnTextureSlotOverflow(sourceID, elementCount); } } @@ -375,9 +381,7 @@ public static void updateMapping() { if (sourceID >= 0 && sourceID < elementCount) { intView.put(sourceID * 3 + 1, targetID); } else { - throw new RuntimeException( - "Normal mapping sourceID " + sourceID + " out of index [0, " + (elementCount - - 1) + "]"); + warnTextureSlotOverflow(sourceID, elementCount); } } @@ -387,9 +391,7 @@ public static void updateMapping() { if (sourceID >= 0 && sourceID < elementCount) { intView.put(sourceID * 3 + 2, targetID); } else { - throw new RuntimeException( - "Flag mapping sourceID " + sourceID + " out of index [0, " + (elementCount - - 1) + "]"); + warnTextureSlotOverflow(sourceID, elementCount); } } @@ -397,6 +399,14 @@ public static void updateMapping() { } } + private static void warnTextureSlotOverflow(int sourceId, int elementCount) { + if (warnedAboutTextureSlotOverflow.compareAndSet(false, true)) { + RadianceClient.LOGGER.warn( + "Texture id {} exceeded the supported mapping range [0, {}]. Auxiliary mappings for this id will be skipped.", + sourceId, elementCount - 1); + } + } + public static native void updateLightMapUniform(long ptr); public static void updateLightMapUniform(float ambientLightFactor, float skyFactor, diff --git a/src/main/java/com/radiance/client/proxy/vulkan/RendererProxy.java b/src/main/java/com/radiance/client/proxy/vulkan/RendererProxy.java index 428a5dc..fd996fe 100644 --- a/src/main/java/com/radiance/client/proxy/vulkan/RendererProxy.java +++ b/src/main/java/com/radiance/client/proxy/vulkan/RendererProxy.java @@ -2,23 +2,32 @@ import com.mojang.blaze3d.systems.RenderSystem; import com.radiance.client.constant.Constants; +import com.radiance.client.RadianceClient; +import com.radiance.client.util.HdrPngScreenshotWriter; import com.radiance.mixin_related.extensions.vulkan_render_integration.INativeImageExt; +import java.nio.ByteBuffer; +import java.nio.file.Path; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.VertexFormat; import net.minecraft.client.texture.NativeImage; import net.minecraft.client.util.Window; +import org.lwjgl.system.MemoryUtil; public class RendererProxy { private static int pipelineType = -1; + private static volatile boolean shuttingDown = false; public static native void initFolderPath(String folderPath); public static native void initRenderer(String[] glfwLibCandidates, long windowHandle); + public static native void beginShutdownNative(); + public static void initRenderer(Window window) { String mapped = System.mapLibraryName("glfw"); String[] candidates = {mapped, "libglfw.so.3", "libglfw.3.dylib", "glfw3.dll"}; + shuttingDown = false; RendererProxy.initRenderer(candidates, window.getHandle()); RenderSystem.apiDescription = "Vulkan 1.4"; } @@ -32,17 +41,61 @@ public static void initRenderer(Window window) { public static native void present(); public static void submitCommandAndPresent() { - submitCommand(); - present(); + synchronized (TextureProxy.class) { + if (shuttingDown) { + return; + } + submitCommand(); + if (shuttingDown) { + return; + } + present(); + } + } + + public static void requestShutdown() { + if (shuttingDown) { + return; + } + shuttingDown = true; + synchronized (TextureProxy.class) { + beginShutdownNative(); + } + } + + public static boolean isShuttingDown() { + return shuttingDown; + } + + public static void closeRenderer() { + shuttingDown = true; + synchronized (TextureProxy.class) { + close(); + } } public static void bindOverlayPipeline(int type) { pipelineType = type; } + public static boolean hasOverlayPipeline() { + return pipelineType >= 0; + } + + public static int getOverlayPipelineType() { + return pipelineType; + } + public static native void drawOverlay(int vertexId, int indexId, int pipelineType, int indexCount, int indexType); + public static void drawOverlay(BufferProxy.VertexIndexBufferHandle handle, int indexCount, + int pipelineType, + VertexFormat.IndexType indexType) { + drawOverlay(handle.vertexId, handle.indexId, pipelineType, indexCount, + Constants.IndexTypes.getValue(indexType)); + } + public static void drawOverlay(BufferProxy.VertexIndexBufferHandle handle, int indexCount, VertexFormat.IndexType indexType) { drawOverlay(handle.vertexId, handle.indexId, pipelineType, indexCount, @@ -53,7 +106,7 @@ public static void drawOverlay(BufferProxy.VertexIndexBufferHandle handle, int i public static native void postBlur(); - public static native void close(); + private static native void close(); public static native void shouldRenderWorld(boolean renderWorld); @@ -74,4 +127,18 @@ public static NativeImage takeScreenshotWithoutUI() { ((INativeImageExt) (Object) nativeImage).radiance$loadFromTextureImageWithoutUI(0, true); return nativeImage; } + + public static Path exportHdrScreenshot() { + MinecraftClient mc = MinecraftClient.getInstance(); + int width = mc.getWindow().getWidth(); + int height = mc.getWindow().getHeight(); + ByteBuffer buffer = MemoryUtil.memAlloc(width * height * 8); + try { + takeScreenshot(false, width, height, 8, MemoryUtil.memAddress(buffer)); + Path directory = RadianceClient.radianceDir.resolve("screenshots"); + return HdrPngScreenshotWriter.write(directory, width, height, buffer); + } finally { + MemoryUtil.memFree(buffer); + } + } } diff --git a/src/main/java/com/radiance/client/proxy/vulkan/TextureProxy.java b/src/main/java/com/radiance/client/proxy/vulkan/TextureProxy.java index 4c1c560..0285d91 100644 --- a/src/main/java/com/radiance/client/proxy/vulkan/TextureProxy.java +++ b/src/main/java/com/radiance/client/proxy/vulkan/TextureProxy.java @@ -7,6 +7,8 @@ public class TextureProxy { public synchronized static native int generateTextureId(); + public synchronized static native void releaseTextureId(int id); + public synchronized static native void prepareImage(int id, int mipLevels, int width, int height, int format); @@ -31,24 +33,26 @@ public synchronized static native void queueUpload(long srcPointer, int height, int level); + public synchronized static native void setTextureAlphaClass(int id, int alphaClass); + public static void prepareImage(NativeImage.InternalFormat internalFormat, int id, int mipLevels, int width, int height) { switch (internalFormat) { case RGBA: prepareImage(id, mipLevels, width, height, - VulkanConstants.VkFormat.VK_FORMAT_R8G8B8A8_UNORM); + VulkanConstants.VkFormat.VK_FORMAT_R8G8B8A8_SRGB); break; case RGB: prepareImage(id, mipLevels, width, height, - VulkanConstants.VkFormat.VK_FORMAT_R8G8B8_UNORM); + VulkanConstants.VkFormat.VK_FORMAT_R8G8B8_SRGB); break; case RG: prepareImage(id, mipLevels, width, height, - VulkanConstants.VkFormat.VK_FORMAT_R8G8_UNORM); + VulkanConstants.VkFormat.VK_FORMAT_R8G8_SRGB); break; case RED: prepareImage(id, mipLevels, width, height, - VulkanConstants.VkFormat.VK_FORMAT_R8_UNORM); + VulkanConstants.VkFormat.VK_FORMAT_R8_SRGB); break; } } diff --git a/src/main/java/com/radiance/client/proxy/world/AtlasSafeGreedyMesher.java b/src/main/java/com/radiance/client/proxy/world/AtlasSafeGreedyMesher.java new file mode 100644 index 0000000..61d31f1 --- /dev/null +++ b/src/main/java/com/radiance/client/proxy/world/AtlasSafeGreedyMesher.java @@ -0,0 +1,659 @@ +package com.radiance.client.proxy.world; + +import com.radiance.client.constant.Constants; +import com.radiance.client.vertex.PBRVertexFormatElements; +import com.radiance.client.vertex.PBRVertexFormats; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import net.minecraft.client.render.BuiltBuffer; +import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.util.BufferAllocator; +import org.lwjgl.system.MemoryUtil; + +final class AtlasSafeGreedyMesher { + + private static final float EPSILON = 1.0e-4f; + private static final int STRIDE = PBRVertexFormats.PBR_TRIANGLE.getVertexSizeByte(); + private static final int POS_OFFSET = + PBRVertexFormats.PBR_TRIANGLE.getOffsetsByElementId()[PBRVertexFormatElements.PBR_POS.id()]; + private static final int UV_OFFSET = PBRVertexFormats.PBR_TRIANGLE.getOffsetsByElementId()[ + PBRVertexFormatElements.PBR_TEXTURE_UV.id()]; + + private AtlasSafeGreedyMesher() { + } + + static BuiltBuffer optimize(BuiltBuffer builtBuffer, + RayTracingTuning.TerrainMeshingMode terrainMeshingMode, int maxMergeSpan) { + BuiltBuffer.DrawParameters drawParameters = builtBuffer.getDrawParameters(); + if (terrainMeshingMode == RayTracingTuning.TerrainMeshingMode.LEGACY_QUADS + || drawParameters.mode() != VertexFormat.DrawMode.QUADS + || Constants.VertexFormats.getValue(drawParameters.format()) + != Constants.VertexFormats.PBR_TRIANGLE.getValue() + || drawParameters.vertexCount() < 8) { + return builtBuffer; + } + + ByteBuffer byteBuffer = builtBuffer.getBuffer().slice().order(ByteOrder.nativeOrder()); + int quadCount = drawParameters.vertexCount() / 4; + List quads = new ArrayList<>(quadCount); + for (int quadIndex = 0; quadIndex < quadCount; quadIndex++) { + quads.add(Quad.parse(byteBuffer, quadIndex * 4 * STRIDE)); + } + + List optimizedQuads = packGreedy(quads, terrainMeshingMode, + Math.max(1, maxMergeSpan)); + if (optimizedQuads == quads) { + return builtBuffer; + } + + int totalSize = optimizedQuads.size() * 4 * STRIDE; + BufferAllocator bufferAllocator = new BufferAllocator(totalSize); + long address = bufferAllocator.allocate(totalSize); + ByteBuffer output = MemoryUtil.memByteBuffer(address, totalSize); + for (Quad quad : optimizedQuads) { + quad.write(output); + } + output.flip(); + + BufferAllocator.CloseableBuffer closeableBuffer = bufferAllocator.getAllocated(); + if (closeableBuffer == null) { + bufferAllocator.close(); + throw new IllegalStateException("Failed to allocate merged chunk buffer"); + } + + return new BuiltBuffer(closeableBuffer, new BuiltBuffer.DrawParameters( + drawParameters.format(), + optimizedQuads.size() * 4, + drawParameters.mode().getIndexCount(optimizedQuads.size() * 4), + drawParameters.mode(), + VertexFormat.IndexType.smallestFor(optimizedQuads.size() * 4))); + } + + private static List packGreedy(List quads, + RayTracingTuning.TerrainMeshingMode terrainMeshingMode, int maxMergeSpan) { + Map mergeGroups = new LinkedHashMap<>(); + List orderedOutputs = new ArrayList<>(); + for (Quad quad : quads) { + if (!quad.mergeable) { + orderedOutputs.add(quad); + continue; + } + + MergeGroupKey mergeGroupKey = quad.mergeGroupKey(); + MergeGroup mergeGroup = mergeGroups.get(mergeGroupKey); + if (mergeGroup == null) { + mergeGroup = new MergeGroup(quad); + mergeGroups.put(mergeGroupKey, mergeGroup); + orderedOutputs.add(mergeGroup); + } + mergeGroup.add(quad); + } + + boolean changed = false; + List optimizedQuads = new ArrayList<>(quads.size()); + for (Object orderedOutput : orderedOutputs) { + if (orderedOutput instanceof Quad quad) { + optimizedQuads.add(quad); + continue; + } + + MergeGroup mergeGroup = (MergeGroup) orderedOutput; + List packedQuads = mergeGroup.pack(terrainMeshingMode, maxMergeSpan); + if (packedQuads.size() < mergeGroup.quads.size()) { + changed = true; + } + optimizedQuads.addAll(packedQuads); + } + return changed ? optimizedQuads : quads; + } + + private static boolean approxEquals(float a, float b) { + return Math.abs(a - b) <= EPSILON; + } + + private static float readFloat(byte[] bytes, int offset) { + return ByteBuffer.wrap(bytes).order(ByteOrder.nativeOrder()).getFloat(offset); + } + + private static void writeFloat(byte[] bytes, int offset, float value) { + ByteBuffer.wrap(bytes).order(ByteOrder.nativeOrder()).putFloat(offset, value); + } + + private static long quantize(float value) { + return Math.round(value / EPSILON); + } + + private static boolean spanWithinLimit(float startCoord, float endCoord, int maxMergeSpan) { + return endCoord - startCoord <= maxMergeSpan + EPSILON; + } + + private static final class MergeGroup { + + private final List quads = new ArrayList<>(); + private final byte[] templateBytes; + private final byte[] attributeSignature; + private final TextureTransform textureTransform; + private final int planeAxis; + private final int uAxis; + private final int vAxis; + private final boolean flippedWinding; + private final float planeCoord; + + private MergeGroup(Quad quad) { + this.templateBytes = quad.templateBytes; + this.attributeSignature = quad.attributeSignature; + this.textureTransform = quad.textureTransform; + this.planeAxis = quad.planeAxis; + this.uAxis = quad.uAxis; + this.vAxis = quad.vAxis; + this.flippedWinding = quad.flippedWinding; + this.planeCoord = quad.planeCoord; + } + + private void add(Quad quad) { + quads.add(quad); + } + + private List pack(RayTracingTuning.TerrainMeshingMode terrainMeshingMode, + int maxMergeSpan) { + if (quads.size() < 2) { + return quads; + } + + List uCoords = new ArrayList<>(); + List vCoords = new ArrayList<>(); + for (Quad quad : quads) { + uCoords.add(quad.uMin); + uCoords.add(quad.uMax); + vCoords.add(quad.vMin); + vCoords.add(quad.vMax); + } + uCoords = compactSortedCoords(uCoords); + vCoords = compactSortedCoords(vCoords); + if (uCoords.size() < 2 || vCoords.size() < 2) { + return quads; + } + + int uCellCount = uCoords.size() - 1; + int vCellCount = vCoords.size() - 1; + boolean[][] occupied = new boolean[vCellCount][uCellCount]; + for (Quad quad : quads) { + int uStart = findCoordIndex(uCoords, quad.uMin); + int uEnd = findCoordIndex(uCoords, quad.uMax); + int vStart = findCoordIndex(vCoords, quad.vMin); + int vEnd = findCoordIndex(vCoords, quad.vMax); + if (uStart < 0 || uEnd < 0 || vStart < 0 || vEnd < 0 || uStart >= uEnd + || vStart >= vEnd) { + return quads; + } + + for (int vIndex = vStart; vIndex < vEnd; vIndex++) { + for (int uIndex = uStart; uIndex < uEnd; uIndex++) { + if (occupied[vIndex][uIndex]) { + return quads; + } + occupied[vIndex][uIndex] = true; + } + } + } + + boolean[][] used = new boolean[vCellCount][uCellCount]; + List packedQuads = new ArrayList<>(); + for (int vIndex = 0; vIndex < vCellCount; vIndex++) { + for (int uIndex = 0; uIndex < uCellCount; uIndex++) { + if (!occupied[vIndex][uIndex] || used[vIndex][uIndex]) { + continue; + } + + int width = growWidth(occupied, used, uCoords, uIndex, vIndex, maxMergeSpan); + int height = 1; + if (terrainMeshingMode == RayTracingTuning.TerrainMeshingMode.GREEDY_MESHING) { + height = growHeight(occupied, used, vCoords, uIndex, vIndex, width, + maxMergeSpan); + } else { + int verticalHeight = growVerticalStripHeight(occupied, used, vCoords, uIndex, + vIndex, maxMergeSpan); + float horizontalSpan = uCoords.get(uIndex + width) - uCoords.get(uIndex); + float verticalSpan = + vCoords.get(vIndex + verticalHeight) - vCoords.get(vIndex); + if (verticalHeight > 1 && verticalSpan > horizontalSpan + EPSILON) { + width = 1; + height = verticalHeight; + } + } + + for (int row = vIndex; row < vIndex + height; row++) { + for (int column = uIndex; column < uIndex + width; column++) { + used[row][column] = true; + } + } + + packedQuads.add(Quad.fromRectangle(templateBytes, attributeSignature, + textureTransform, planeAxis, uAxis, vAxis, flippedWinding, planeCoord, + uCoords.get(uIndex), uCoords.get(uIndex + width), vCoords.get(vIndex), + vCoords.get(vIndex + height))); + } + } + return packedQuads; + } + + private static int growWidth(boolean[][] occupied, boolean[][] used, List uCoords, + int uIndex, int vIndex, int maxMergeSpan) { + int width = 1; + while (uIndex + width < occupied[vIndex].length + && occupied[vIndex][uIndex + width] + && !used[vIndex][uIndex + width] + && spanWithinLimit(uCoords.get(uIndex), uCoords.get(uIndex + width + 1), + maxMergeSpan)) { + width++; + } + return width; + } + + private static int growHeight(boolean[][] occupied, boolean[][] used, List vCoords, + int uIndex, int vIndex, int width, int maxMergeSpan) { + int height = 1; + while (vIndex + height < occupied.length + && canExtendHeight(occupied, used, uIndex, vIndex, width, height) + && spanWithinLimit(vCoords.get(vIndex), vCoords.get(vIndex + height + 1), + maxMergeSpan)) { + height++; + } + return height; + } + + private static int growVerticalStripHeight(boolean[][] occupied, boolean[][] used, + List vCoords, int uIndex, int vIndex, int maxMergeSpan) { + int height = 1; + while (vIndex + height < occupied.length + && occupied[vIndex + height][uIndex] + && !used[vIndex + height][uIndex] + && spanWithinLimit(vCoords.get(vIndex), vCoords.get(vIndex + height + 1), + maxMergeSpan)) { + height++; + } + return height; + } + + private static boolean canExtendHeight(boolean[][] occupied, boolean[][] used, int uIndex, + int vIndex, int width, int height) { + int nextRow = vIndex + height; + for (int column = uIndex; column < uIndex + width; column++) { + if (!occupied[nextRow][column] || used[nextRow][column]) { + return false; + } + } + return true; + } + + private static List compactSortedCoords(List coords) { + coords.sort(Float::compare); + List compactCoords = new ArrayList<>(coords.size()); + for (Float coord : coords) { + if (compactCoords.isEmpty() || !approxEquals( + compactCoords.get(compactCoords.size() - 1), coord)) { + compactCoords.add(coord); + } + } + return compactCoords; + } + + private static int findCoordIndex(List coords, float coord) { + for (int index = 0; index < coords.size(); index++) { + if (approxEquals(coords.get(index), coord)) { + return index; + } + } + return -1; + } + } + + private static final class MergeGroupKey { + + private final int planeAxis; + private final int uAxis; + private final int vAxis; + private final boolean flippedWinding; + private final long planeCoordKey; + private final long uFromUKey; + private final long uFromVKey; + private final long uBiasKey; + private final long vFromUKey; + private final long vFromVKey; + private final long vBiasKey; + private final byte[] attributeSignature; + + private MergeGroupKey(Quad quad) { + this.planeAxis = quad.planeAxis; + this.uAxis = quad.uAxis; + this.vAxis = quad.vAxis; + this.flippedWinding = quad.flippedWinding; + this.planeCoordKey = quantize(quad.planeCoord); + this.uFromUKey = quantize(quad.textureTransform.uFromU()); + this.uFromVKey = quantize(quad.textureTransform.uFromV()); + this.uBiasKey = quantize(quad.textureTransform.uBias()); + this.vFromUKey = quantize(quad.textureTransform.vFromU()); + this.vFromVKey = quantize(quad.textureTransform.vFromV()); + this.vBiasKey = quantize(quad.textureTransform.vBias()); + this.attributeSignature = quad.attributeSignature; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof MergeGroupKey other)) { + return false; + } + return planeAxis == other.planeAxis + && uAxis == other.uAxis + && vAxis == other.vAxis + && flippedWinding == other.flippedWinding + && planeCoordKey == other.planeCoordKey + && uFromUKey == other.uFromUKey + && uFromVKey == other.uFromVKey + && uBiasKey == other.uBiasKey + && vFromUKey == other.vFromUKey + && vFromVKey == other.vFromVKey + && vBiasKey == other.vBiasKey + && Arrays.equals(attributeSignature, other.attributeSignature); + } + + @Override + public int hashCode() { + int result = Objects.hash(planeAxis, uAxis, vAxis, flippedWinding, planeCoordKey, + uFromUKey, uFromVKey, uBiasKey, vFromUKey, vFromVKey, vBiasKey); + return 31 * result + Arrays.hashCode(attributeSignature); + } + } + + private static final class Quad { + + private final VertexData[] corners; + private final byte[][] originalOrder; + private final byte[] attributeSignature; + private final byte[] templateBytes; + private final TextureTransform textureTransform; + private final int planeAxis; + private final int uAxis; + private final int vAxis; + private final boolean flippedWinding; + private final boolean mergeable; + private final float planeCoord; + private final float uMin; + private final float uMax; + private final float vMin; + private final float vMax; + + private Quad(VertexData[] corners, byte[][] originalOrder, byte[] attributeSignature, + byte[] templateBytes, TextureTransform textureTransform, int planeAxis, int uAxis, + int vAxis, boolean flippedWinding, boolean mergeable, float planeCoord, float uMin, + float uMax, float vMin, float vMax) { + this.corners = corners; + this.originalOrder = originalOrder; + this.attributeSignature = attributeSignature; + this.templateBytes = templateBytes; + this.textureTransform = textureTransform; + this.planeAxis = planeAxis; + this.uAxis = uAxis; + this.vAxis = vAxis; + this.flippedWinding = flippedWinding; + this.mergeable = mergeable; + this.planeCoord = planeCoord; + this.uMin = uMin; + this.uMax = uMax; + this.vMin = vMin; + this.vMax = vMax; + } + + private static Quad parse(ByteBuffer byteBuffer, int baseOffset) { + byte[][] originalOrder = new byte[4][STRIDE]; + VertexData[] vertices = new VertexData[4]; + for (int i = 0; i < 4; i++) { + byte[] bytes = new byte[STRIDE]; + int vertexOffset = baseOffset + i * STRIDE; + ByteBuffer duplicate = byteBuffer.duplicate(); + duplicate.position(vertexOffset); + duplicate.get(bytes); + originalOrder[i] = bytes; + vertices[i] = VertexData.from(bytes); + } + + int planeAxis = -1; + for (int axis = 0; axis < 3; axis++) { + float value = vertices[0].position[axis]; + boolean constantAxis = true; + for (int vertex = 1; vertex < 4; vertex++) { + if (!approxEquals(value, vertices[vertex].position[axis])) { + constantAxis = false; + break; + } + } + if (constantAxis) { + planeAxis = axis; + break; + } + } + + if (planeAxis < 0) { + return createNonMergeable(vertices, originalOrder, new byte[0], null, null, -1, -1, + -1, false, 0, 0, 0, 0, 0); + } + + int uAxis = (planeAxis + 1) % 3; + int vAxis = (planeAxis + 2) % 3; + float uMin = Float.POSITIVE_INFINITY; + float uMax = Float.NEGATIVE_INFINITY; + float vMin = Float.POSITIVE_INFINITY; + float vMax = Float.NEGATIVE_INFINITY; + for (VertexData vertexData : vertices) { + uMin = Math.min(uMin, vertexData.position[uAxis]); + uMax = Math.max(uMax, vertexData.position[uAxis]); + vMin = Math.min(vMin, vertexData.position[vAxis]); + vMax = Math.max(vMax, vertexData.position[vAxis]); + } + + VertexData[] corners = new VertexData[4]; + for (VertexData vertexData : vertices) { + int cornerIndex = -1; + if (approxEquals(vertexData.position[uAxis], uMin) + && approxEquals(vertexData.position[vAxis], vMin)) { + cornerIndex = 0; + } else if (approxEquals(vertexData.position[uAxis], uMax) + && approxEquals(vertexData.position[vAxis], vMin)) { + cornerIndex = 1; + } else if (approxEquals(vertexData.position[uAxis], uMax) + && approxEquals(vertexData.position[vAxis], vMax)) { + cornerIndex = 2; + } else if (approxEquals(vertexData.position[uAxis], uMin) + && approxEquals(vertexData.position[vAxis], vMax)) { + cornerIndex = 3; + } + + if (cornerIndex < 0 || corners[cornerIndex] != null) { + return createNonMergeable(vertices, originalOrder, new byte[0], null, null, + planeAxis, uAxis, vAxis, false, vertices[0].position[planeAxis], uMin, + uMax, vMin, vMax); + } + corners[cornerIndex] = vertexData; + } + + byte[] attributeSignature = corners[0].attributeSignature(); + boolean constantAttributes = attributeSignature.length > 0; + if (constantAttributes) { + for (int i = 1; i < corners.length; i++) { + if (!Arrays.equals(attributeSignature, corners[i].attributeSignature())) { + constantAttributes = false; + break; + } + } + } + + float cross = crossAxis(corners[0], corners[1], corners[3], planeAxis); + float originalCross = crossAxis(vertices[0], vertices[1], vertices[2], planeAxis); + boolean flippedWinding = Math.signum(cross) != Math.signum(originalCross); + TextureTransform textureTransform = null; + if (constantAttributes) { + textureTransform = TextureTransform.from(corners, uMin, uMax, vMin, vMax); + } + if (!constantAttributes || textureTransform == null) { + return createNonMergeable(vertices, originalOrder, attributeSignature, null, null, + planeAxis, uAxis, vAxis, flippedWinding, vertices[0].position[planeAxis], uMin, + uMax, vMin, vMax); + } + + return new Quad(corners, null, attributeSignature, + Arrays.copyOf(corners[0].bytes, corners[0].bytes.length), textureTransform, + planeAxis, uAxis, vAxis, flippedWinding, true, vertices[0].position[planeAxis], + uMin, uMax, vMin, vMax); + } + + private static Quad fromRectangle(byte[] templateBytes, byte[] attributeSignature, + TextureTransform textureTransform, int planeAxis, int uAxis, int vAxis, + boolean flippedWinding, float planeCoord, float uMin, float uMax, float vMin, + float vMax) { + VertexData[] corners = new VertexData[]{ + VertexData.create(templateBytes, attributeSignature, textureTransform, planeAxis, + uAxis, vAxis, planeCoord, uMin, vMin), + VertexData.create(templateBytes, attributeSignature, textureTransform, planeAxis, + uAxis, vAxis, planeCoord, uMax, vMin), + VertexData.create(templateBytes, attributeSignature, textureTransform, planeAxis, + uAxis, vAxis, planeCoord, uMax, vMax), + VertexData.create(templateBytes, attributeSignature, textureTransform, planeAxis, + uAxis, vAxis, planeCoord, uMin, vMax) + }; + return new Quad(corners, null, attributeSignature, + Arrays.copyOf(templateBytes, templateBytes.length), textureTransform, planeAxis, + uAxis, vAxis, flippedWinding, true, planeCoord, uMin, uMax, vMin, vMax); + } + + private static Quad createNonMergeable(VertexData[] corners, byte[][] originalOrder, + byte[] attributeSignature, byte[] templateBytes, TextureTransform textureTransform, + int planeAxis, int uAxis, int vAxis, boolean flippedWinding, float planeCoord, + float uMin, float uMax, float vMin, float vMax) { + return new Quad(corners, originalOrder, attributeSignature, templateBytes, + textureTransform, planeAxis, uAxis, vAxis, flippedWinding, false, planeCoord, uMin, + uMax, vMin, vMax); + } + + private MergeGroupKey mergeGroupKey() { + return new MergeGroupKey(this); + } + + private void write(ByteBuffer output) { + if (!mergeable) { + for (byte[] bytes : originalOrder) { + output.put(bytes); + } + return; + } + + int[] order = flippedWinding ? new int[]{0, 3, 2, 1} : new int[]{0, 1, 2, 3}; + for (int cornerIndex : order) { + output.put(corners[cornerIndex].bytes); + } + } + + private static float crossAxis(VertexData a, VertexData b, VertexData c, int axis) { + float ab0 = b.position[(axis + 1) % 3] - a.position[(axis + 1) % 3]; + float ab1 = b.position[(axis + 2) % 3] - a.position[(axis + 2) % 3]; + float ac0 = c.position[(axis + 1) % 3] - a.position[(axis + 1) % 3]; + float ac1 = c.position[(axis + 2) % 3] - a.position[(axis + 2) % 3]; + return ab0 * ac1 - ab1 * ac0; + } + } + + private record TextureTransform(float uFromU, float uFromV, float uBias, float vFromU, + float vFromV, float vBias) { + + private static TextureTransform from(VertexData[] corners, float uMin, float uMax, + float vMin, float vMax) { + float width = uMax - uMin; + float height = vMax - vMin; + if (Math.abs(width) <= EPSILON || Math.abs(height) <= EPSILON) { + return null; + } + + float uFromU = (corners[1].u - corners[0].u) / width; + float uFromV = (corners[3].u - corners[0].u) / height; + float uBias = corners[0].u - uFromU * uMin - uFromV * vMin; + float vFromU = (corners[1].v - corners[0].v) / width; + float vFromV = (corners[3].v - corners[0].v) / height; + float vBias = corners[0].v - vFromU * uMin - vFromV * vMin; + TextureTransform textureTransform = new TextureTransform(uFromU, uFromV, uBias, vFromU, + vFromV, vBias); + if (!approxEquals(textureTransform.mapU(uMax, vMax), corners[2].u) + || !approxEquals(textureTransform.mapV(uMax, vMax), corners[2].v) + || !approxEquals(textureTransform.mapU(uMax, vMin), corners[1].u) + || !approxEquals(textureTransform.mapV(uMax, vMin), corners[1].v) + || !approxEquals(textureTransform.mapU(uMin, vMax), corners[3].u) + || !approxEquals(textureTransform.mapV(uMin, vMax), corners[3].v)) { + return null; + } + return textureTransform; + } + + private float mapU(float uCoord, float vCoord) { + return uFromU * uCoord + uFromV * vCoord + uBias; + } + + private float mapV(float uCoord, float vCoord) { + return vFromU * uCoord + vFromV * vCoord + vBias; + } + } + + private record VertexData(byte[] bytes, float[] position, float u, float v, + byte[] attributeSignature) { + + private static VertexData from(byte[] bytes) { + float[] position = new float[]{ + readFloat(bytes, POS_OFFSET), + readFloat(bytes, POS_OFFSET + 4), + readFloat(bytes, POS_OFFSET + 8) + }; + float u = readFloat(bytes, UV_OFFSET); + float v = readFloat(bytes, UV_OFFSET + 4); + return new VertexData(bytes, position, u, v, buildAttributeSignature(bytes)); + } + + private static VertexData create(byte[] templateBytes, byte[] attributeSignature, + TextureTransform textureTransform, int planeAxis, int uAxis, int vAxis, + float planeCoord, float uCoord, float vCoord) { + byte[] bytes = Arrays.copyOf(templateBytes, templateBytes.length); + float[] position = new float[3]; + position[planeAxis] = planeCoord; + position[uAxis] = uCoord; + position[vAxis] = vCoord; + writeFloat(bytes, POS_OFFSET, position[0]); + writeFloat(bytes, POS_OFFSET + 4, position[1]); + writeFloat(bytes, POS_OFFSET + 8, position[2]); + float u = textureTransform.mapU(uCoord, vCoord); + float v = textureTransform.mapV(uCoord, vCoord); + writeFloat(bytes, UV_OFFSET, u); + writeFloat(bytes, UV_OFFSET + 4, v); + return new VertexData(bytes, position, u, v, attributeSignature); + } + + private static byte[] buildAttributeSignature(byte[] bytes) { + byte[] signature = new byte[bytes.length - 20]; + int dst = 0; + for (int src = 0; src < bytes.length; src++) { + if (src >= POS_OFFSET && src < POS_OFFSET + 12) { + continue; + } + if (src >= UV_OFFSET && src < UV_OFFSET + 8) { + continue; + } + signature[dst++] = bytes[src]; + } + return signature; + } + } +} diff --git a/src/main/java/com/radiance/client/proxy/world/ChunkProxy.java b/src/main/java/com/radiance/client/proxy/world/ChunkProxy.java index e839e70..587c596 100644 --- a/src/main/java/com/radiance/client/proxy/world/ChunkProxy.java +++ b/src/main/java/com/radiance/client/proxy/world/ChunkProxy.java @@ -4,12 +4,18 @@ import static org.lwjgl.system.MemoryUtil.memAddress; import com.mojang.blaze3d.systems.VertexSorter; +import com.mojang.logging.LogUtils; +import com.radiance.client.RadianceClient; import com.radiance.client.constant.Constants; import com.radiance.client.proxy.vulkan.BufferProxy; +import com.radiance.client.texture.TextureTracker; +import com.radiance.client.util.ChunkLightCollector; import com.radiance.mixin_related.extensions.vulkan_render_integration.IChunkBuilderBuiltChunkExt; import com.radiance.mixin_related.extensions.vulkan_render_integration.IChunkBuilderExt; +import com.radiance.mixin_related.extensions.vulkan_render_integration.IBlockColorsExt; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -17,8 +23,13 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadPoolExecutor; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.BuiltBuffer; import net.minecraft.client.render.Camera; @@ -28,7 +39,9 @@ import net.minecraft.client.render.chunk.ChunkRendererRegion; import net.minecraft.client.render.chunk.ChunkRendererRegionBuilder; import net.minecraft.client.render.chunk.SectionBuilder; +import net.minecraft.client.color.block.BlockColors; import net.minecraft.client.texture.MissingSprite; +import net.minecraft.client.util.BufferAllocator; import net.minecraft.client.texture.TextureManager; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkSectionPos; @@ -38,6 +51,11 @@ public class ChunkProxy { + private static final org.slf4j.Logger LOGGER = LogUtils.getLogger(); + + private static final long FNV64_OFFSET_BASIS = 0xcbf29ce484222325L; + private static final long FNV64_PRIME = 0x100000001b3L; + public static final ChunkBuilder.ChunkData PROCESSED = new ChunkBuilder.ChunkData() { @Override public boolean isVisibleThrough(Direction from, Direction to) { @@ -52,23 +70,41 @@ public boolean isVisibleThrough(Direction from, Direction to) { }; private static final Map rebuildQueue = new ConcurrentHashMap<>(); private static final List> rebuildTasks = new ArrayList<>(); + private static final Map specialChunkGeometry = + new ConcurrentHashMap<>(); + private static final Map builtChunkBuildOrigins = new ConcurrentHashMap<>(); + private static final Map visibleChunkFrames = new ConcurrentHashMap<>(); + private static final Map chunkTraversalData = + new ConcurrentHashMap<>(); + private static final Object specialChunkGeometryLock = new Object(); private static final int numNormalChunkRebuildThreads = 1; private static final int numImportantChunkRebuildThreads = 1; + private static final double CHUNK_BOUNDING_RADIUS_BLOCKS = 13.856406460551018; + private static final double VIEW_CONTRIBUTION_NEAR_DISTANCE_MULTIPLIER = 1.5; + private static final double VIEW_CONTRIBUTION_BASE_HALF_ANGLE_DEGREES = 100.0; private static final ExecutorService importantChunkRebuildExecutor = - Executors.newFixedThreadPool(numImportantChunkRebuildThreads, r -> { - Thread thread = new Thread(r); - thread.setPriority(Thread.NORM_PRIORITY); - return thread; - }); + new ThreadPoolExecutor(numImportantChunkRebuildThreads, numImportantChunkRebuildThreads, + 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), + r -> { + Thread thread = new Thread(r); + thread.setPriority(Thread.NORM_PRIORITY); + thread.setName("radiance-chunk-important"); + return thread; + }); private static final ThreadLocal blockBufferAllocatorStorageThreadLocal = ThreadLocal.withInitial(BlockBufferAllocatorStorage::new); public static int builtChunkNum = 0; - private static ExecutorService backgroundChunkRebuildExecutor = Executors.newFixedThreadPool( - numNormalChunkRebuildThreads, r -> { + private static long rebuildFrameCounter = 0L; + private static final long VISIBLE_REBUILD_GRACE_FRAMES = 45L; + private static ExecutorService backgroundChunkRebuildExecutor = new ThreadPoolExecutor( + numNormalChunkRebuildThreads, numNormalChunkRebuildThreads, + 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), + r -> { Thread thread = new Thread(r); thread.setPriority(Thread.NORM_PRIORITY); + thread.setName("radiance-chunk-background"); return thread; }); @@ -88,55 +124,168 @@ public static AutoCloseable scopedBlockBufferAllocatorStorage() { public static void clear() { waitImportantChunkRebuild(); - backgroundChunkRebuildExecutor.shutdown(); - try { - backgroundChunkRebuildExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - backgroundChunkRebuildExecutor = Executors.newFixedThreadPool(numNormalChunkRebuildThreads, + // Create new executor BEFORE shutting down the old one to minimize task rejection window + ThreadPoolExecutor newBackgroundExecutor = new ThreadPoolExecutor(numNormalChunkRebuildThreads, + numNormalChunkRebuildThreads, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), r -> { Thread thread = new Thread(r); thread.setPriority(Thread.NORM_PRIORITY); + thread.setName("radiance-chunk-background"); return thread; }); + ExecutorService oldExecutor = backgroundChunkRebuildExecutor; + backgroundChunkRebuildExecutor = newBackgroundExecutor; + + oldExecutor.shutdown(); + try { + if (!oldExecutor.awaitTermination(30, TimeUnit.SECONDS)) { + LOGGER.warn("Background chunk rebuild executor did not terminate in time, forcing shutdown"); + oldExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + LOGGER.warn("Interrupted while waiting for background chunk rebuild executor", e); + oldExecutor.shutdownNow(); + Thread.currentThread().interrupt(); + } + rebuildQueue.clear(); rebuildTasks.clear(); + synchronized (specialChunkGeometryLock) { + for (ChunkSpecialRenderData chunkSpecialRenderData : specialChunkGeometry.values()) { + chunkSpecialRenderData.close(); + } + specialChunkGeometry.clear(); + } + builtChunkBuildOrigins.clear(); + visibleChunkFrames.clear(); + chunkTraversalData.clear(); } public static void enqueueRebuild(ChunkBuilder.BuiltChunk chunk) { rebuildQueue.put(chunk.index, chunk); } - public static void rebuild(Camera camera) { + public static void markLightDirtySection(ChunkSectionPos sectionPos, int lightTypeOrdinal) { + markLightDirtySection(sectionPos.getSectionX(), sectionPos.getSectionY(), + sectionPos.getSectionZ(), lightTypeOrdinal); + } - BlockPos blockPos = camera.getBlockPos(); - for (ChunkBuilder.BuiltChunk builtChunk : rebuildQueue.values()) { - if (builtChunk.needsRebuild() && builtChunk.shouldBuild()) { - builtChunk.cancelRebuild(); + public static void rebuild(Camera camera, List visibleBuiltChunks) { + rebuildFrameCounter++; + if (visibleBuiltChunks != null) { + for (ChunkBuilder.BuiltChunk visibleBuiltChunk : visibleBuiltChunks) { + if (visibleBuiltChunk != null) { + visibleChunkFrames.put(visibleBuiltChunk.index, rebuildFrameCounter); + } + } + } + + BlockPos cameraBlockPos = camera.getBlockPos(); + Vec3d cameraPos = camera.getPos(); + Vec3d cameraForward = Vec3d.fromPolar(camera.getPitch(), camera.getYaw()).normalize(); + List processedChunks = new ArrayList<>(); + for (Map.Entry rebuildEntry : rebuildQueue.entrySet()) { + ChunkBuilder.BuiltChunk builtChunk = rebuildEntry.getValue(); + if (builtChunk == null) { + processedChunks.add(rebuildEntry.getKey()); + continue; + } + + if (!builtChunk.needsRebuild() || !builtChunk.shouldBuild()) { + if (!builtChunk.needsRebuild()) { + processedChunks.add(rebuildEntry.getKey()); + } + continue; + } - BlockPos - chunkCenterPos = - builtChunk.getOrigin() - .add(8, 8, 8); - boolean isImportant = chunkCenterPos.getSquaredDistance(blockPos) < 768.0 - || builtChunk.needsImportantRebuild(); + BlockPos chunkCenterPos = builtChunk.getOrigin().add(8, 8, 8); + double distanceChunks = Math.sqrt(chunkCenterPos.getSquaredDistance(cameraBlockPos)) + / 16.0; + boolean isImportant = chunkCenterPos.getSquaredDistance(cameraBlockPos) < 768.0 + || builtChunk.needsImportantRebuild(); + long chunkOriginKey = builtChunk.getOrigin().asLong(); + long lastVisibleFrame = visibleChunkFrames.getOrDefault(builtChunk.index, + Long.MIN_VALUE); + boolean recentlyVisible = lastVisibleFrame != Long.MIN_VALUE + && rebuildFrameCounter - lastVisibleFrame <= VISIBLE_REBUILD_GRACE_FRAMES; + boolean potentiallyViewContributing = isPotentiallyViewContributing(cameraPos, + cameraForward, chunkCenterPos, distanceChunks); + + if (!isImportant) { + if (!recentlyVisible && !potentiallyViewContributing) { + continue; + } + if (!potentiallyViewContributing + && chunkOriginKey == builtChunkBuildOrigins.getOrDefault(builtChunk.index, + Long.MIN_VALUE)) { + int updateInterval = RayTracingTuning.terrainUpdateIntervalFrames( + distanceChunks); + long scheduleBucket = Math.floorMod(rebuildFrameCounter + builtChunk.index, + updateInterval); + if (scheduleBucket != 0L) { + continue; + } + } + } - if (isImportant) { - Future rebuildTask = importantChunkRebuildExecutor.submit(() -> { - rebuildSingle(builtChunk, true); - }); + boolean scheduled = false; + if (isImportant) { + try { + Future rebuildTask = importantChunkRebuildExecutor.submit(() -> + rebuildSingle(builtChunk, true)); rebuildTasks.add(rebuildTask); - } else { - backgroundChunkRebuildExecutor.execute(() -> { - rebuildSingle(builtChunk, false); - }); + scheduled = true; + } catch (RejectedExecutionException e) { + RadianceClient.LOGGER.warn("Important chunk rebuild task rejected for chunk {}, will retry", + builtChunk.index); + // Do NOT add to processedChunks - chunk remains in rebuildQueue for retry + continue; + } + } else { + try { + backgroundChunkRebuildExecutor.execute(() -> rebuildSingle(builtChunk, false)); + scheduled = true; + } catch (RejectedExecutionException e) { + RadianceClient.LOGGER.warn("Background chunk rebuild task rejected for chunk {}, will retry", + builtChunk.index); + // Do NOT add to processedChunks - chunk remains in rebuildQueue for retry + continue; } } + + builtChunk.cancelRebuild(); + processedChunks.add(rebuildEntry.getKey()); } - rebuildQueue.clear(); + for (Integer processedChunk : processedChunks) { + rebuildQueue.remove(processedChunk); + } + } + + private static boolean isPotentiallyViewContributing(Vec3d cameraPos, Vec3d cameraForward, + BlockPos chunkCenterPos, double distanceChunks) { + double farFieldStartDistanceChunks = RayTracingTuning.getFarFieldStartDistanceChunks(); + if (distanceChunks <= farFieldStartDistanceChunks + * VIEW_CONTRIBUTION_NEAR_DISTANCE_MULTIPLIER) { + return true; + } + + Vec3d toChunk = new Vec3d( + chunkCenterPos.getX() - cameraPos.x, + chunkCenterPos.getY() - cameraPos.y, + chunkCenterPos.getZ() - cameraPos.z); + double distanceBlocks = toChunk.length(); + if (distanceBlocks <= 1e-6) { + return true; + } + + double angularRadiusDegrees = Math.toDegrees(Math.asin(Math.min(1.0, + CHUNK_BOUNDING_RADIUS_BLOCKS / Math.max(distanceBlocks, CHUNK_BOUNDING_RADIUS_BLOCKS)))); + double halfAngleDegrees = Math.min(179.0, + VIEW_CONTRIBUTION_BASE_HALF_ANGLE_DEGREES + angularRadiusDegrees); + double dotThreshold = Math.cos(Math.toRadians(halfAngleDegrees)); + return toChunk.multiply(1.0 / distanceBlocks).dotProduct(cameraForward) >= dotThreshold; } public static void waitImportantChunkRebuild() { @@ -167,6 +316,9 @@ private static void rebuildSingle(ChunkBuilder.BuiltChunk builtChunk, boolean im ChunkSectionPos.from(builtChunk.getSectionPos())); if (chunkRendererRegion == null) { + clearSpecialChunkGeometry(builtChunk.index); + builtChunkBuildOrigins.remove(builtChunk.index); + chunkTraversalData.remove(builtChunk.index); invalidateSingle(builtChunk.index); builtChunk.data.set(ChunkBuilder.ChunkData.EMPTY); return; @@ -188,6 +340,12 @@ private static void rebuildSingle(ChunkRendererRegion chunkRendererRegion, boolean important) { ChunkSectionPos chunkSectionPos = ChunkSectionPos.from(builtChunk.getOrigin()); + if (RayTracingTuning.shouldCaptureTraversalMetadata()) { + chunkTraversalData.put((long) builtChunk.index, + captureChunkTraversalData(chunkRendererRegion, chunkSectionPos)); + } else { + chunkTraversalData.remove(builtChunk.index); + } Vec3d vec3d = chunkBuilder.getCameraPosition(); // TODO: cancel out the sort operation in section builder @@ -207,118 +365,131 @@ private static void rebuildSingle(ChunkRendererRegion chunkRendererRegion, .build(chunkSectionPos, chunkRendererRegion, vertexSorter, storage); } - Map buffers = renderData.buffers; + Map buffers = preprocessChunkBuffers(renderData.buffers); builtChunk.setNoCullingBlockEntities(renderData.noCullingBlockEntities); + long lightStateHash = computeLightStateHash(chunkRendererRegion, chunkSectionPos); + double distanceChunks = Math.sqrt( + builtChunk.getOrigin().add(8, 8, 8).getSquaredDistance(vec3d.x, vec3d.y, vec3d.z)) + / 16.0; + + Map blasBuffers = new HashMap<>(); + List specialBuffers = new ArrayList<>(); + EntityProxy.EntityRenderData specialRenderData = new EntityProxy.EntityRenderData( + Long.hashCode(builtChunk.index), + builtChunk.getOrigin().getX(), + builtChunk.getOrigin().getY(), + builtChunk.getOrigin().getZ(), + Constants.RayTracingFlags.WORLD.getValue(), + -1, + false); - if (buffers.isEmpty()) { - ChunkBuilder.ChunkData chunkData = new ChunkBuilder.ChunkData() { - @Override - public List getBlockEntities() { - return renderData.blockEntities; - } + for (Map.Entry entry : buffers.entrySet()) { + RenderLayer renderLayer = entry.getKey(); + BuiltBuffer vertexBuffer = entry.getValue(); + RayTracingTuning.ChunkGeometryRoute chunkGeometryRoute = + RayTracingTuning.classifyChunkLayer(renderLayer, distanceChunks); + + if (chunkGeometryRoute == RayTracingTuning.ChunkGeometryRoute.BLAS) { + blasBuffers.put(renderLayer, vertexBuffer); + continue; + } - @Override - public boolean isVisibleThrough(Direction from, Direction to) { - return renderData.chunkOcclusionData.isVisibleThrough(from, to); - } + if (chunkGeometryRoute == RayTracingTuning.ChunkGeometryRoute.DROP) { + continue; + } - @Override - public boolean isEmpty(RenderLayer layer) { - return true; - } - }; - builtChunk.data.set(chunkData); - builtChunkNum++; + BuiltBuffer specialBuffer = cloneBuiltBuffer(vertexBuffer); + specialBuffers.add(specialBuffer); + specialRenderData.add(new EntityProxy.EntityRenderLayer(renderLayer, specialBuffer, + chunkGeometryRoute == RayTracingTuning.ChunkGeometryRoute.SPECIAL_REFLECTIVE)); + } - invalidateSingle(builtChunk.index); - } else { - ChunkBuilder.ChunkData chunkData = new ChunkBuilder.ChunkData() { - @Override - public List getBlockEntities() { - return renderData.blockEntities; - } + ChunkSpecialRenderData chunkSpecialRenderData = + specialRenderData.isEmpty() ? null : new ChunkSpecialRenderData(specialRenderData, + specialBuffers); + replaceSpecialChunkGeometry(builtChunk.index, chunkSpecialRenderData); - @Override - public boolean isVisibleThrough(Direction from, Direction to) { - return renderData.chunkOcclusionData.isVisibleThrough(from, to); - } + boolean hasSubmittedGeometry = !blasBuffers.isEmpty() || chunkSpecialRenderData != null; + ChunkBuilder.ChunkData chunkData = new ChunkBuilder.ChunkData() { + @Override + public List getBlockEntities() { + return renderData.blockEntities; + } - @Override - public boolean isEmpty(RenderLayer layer) { - return false; - } - }; - builtChunk.data.set(chunkData); - builtChunkNum++; + @Override + public boolean isVisibleThrough(Direction from, Direction to) { + return renderData.chunkOcclusionData.isVisibleThrough(from, to); + } + + @Override + public boolean isEmpty(RenderLayer layer) { + return !hasSubmittedGeometry; + } + }; + builtChunk.data.set(chunkData); + builtChunkBuildOrigins.put(builtChunk.index, builtChunk.getOrigin().asLong()); + builtChunkNum++; + if (blasBuffers.isEmpty()) { + invalidateSingle(builtChunk.index); + } else { ByteBuffer geometryTypeBB = null; ByteBuffer geometryGroupNameBB = null; ByteBuffer geometryTextureBB = null; ByteBuffer vertexFormatBB = null; ByteBuffer vertexCountBB = null; ByteBuffer verticesBB = null; - List geometryGroupNameBuffers = new ArrayList<>(buffers.size()); + List geometryGroupNameBuffers = new ArrayList<>(blasBuffers.size()); try { - int geometryTypeSize = buffers.size() * Integer.BYTES; + int geometryTypeSize = blasBuffers.size() * Integer.BYTES; geometryTypeBB = MemoryUtil.memAlloc(geometryTypeSize); long geometryTypeAddr = memAddress(geometryTypeBB); int geometryTypeBaseAddr = 0; - int geometryGroupNameSize = buffers.size() * Long.BYTES; + int geometryGroupNameSize = blasBuffers.size() * Long.BYTES; geometryGroupNameBB = MemoryUtil.memAlloc(geometryGroupNameSize); long geometryGroupNameAddr = memAddress(geometryGroupNameBB); int geometryGroupNameBaseAddr = 0; - int geometryTextureSize = buffers.size() * Integer.BYTES; + int geometryTextureSize = blasBuffers.size() * Integer.BYTES; geometryTextureBB = MemoryUtil.memAlloc(geometryTextureSize); long geometryTextureAddr = memAddress(geometryTextureBB); int geometryTextureBaseAddr = 0; - int vertexFormatSize = buffers.size() * Integer.BYTES; + int vertexFormatSize = blasBuffers.size() * Integer.BYTES; vertexFormatBB = MemoryUtil.memAlloc(vertexFormatSize); long vertexFormatAddr = memAddress(vertexFormatBB); int vertexFormatBaseAddr = 0; - int vertexCountSize = buffers.size() * Integer.BYTES; + int vertexCountSize = blasBuffers.size() * Integer.BYTES; vertexCountBB = MemoryUtil.memAlloc(vertexCountSize); long vertexCountAddr = memAddress(vertexCountBB); int vertexCountBaseAddr = 0; - int verticesSize = buffers.size() * Long.BYTES; + int verticesSize = blasBuffers.size() * Long.BYTES; verticesBB = MemoryUtil.memAlloc(verticesSize); long verticesAddr = memAddress(verticesBB); int verticesBaseAddr = 0; - for (Map.Entry entry : buffers.entrySet()) { + for (Map.Entry entry : blasBuffers.entrySet()) { RenderLayer renderLayer = entry.getKey(); assert renderLayer.getDrawMode() == QUADS; BuiltBuffer vertexBuffer = entry.getValue(); BufferProxy.BufferInfo vertexBufferInfo = BufferProxy.getBufferInfo( vertexBuffer.getBuffer()); - assert vertexBuffer.getDrawParameters() - .indexCount() == vertexBuffer.getDrawParameters() - .vertexCount() / 4 * 6; - - TextureManager - textureManager = - MinecraftClient.getInstance() - .getTextureManager(); - - int - geometryTypeID = - Constants.GeometryTypes.getGeometryType(renderLayer, true) - .getValue(); - int - geometryTextureID = - textureManager.getTexture( - ((RenderLayer.MultiPhase) renderLayer).phases.texture.getId() - .orElse(MissingSprite.getMissingSpriteId())) - .getGlId(); + assert vertexBuffer.getDrawParameters().indexCount() + == vertexBuffer.getDrawParameters().vertexCount() / 4 * 6; + + TextureManager textureManager = MinecraftClient.getInstance().getTextureManager(); + + int geometryTypeID = Constants.GeometryTypes.getGeometryType(renderLayer, + RayTracingTuning.shouldReflectBlasLayer(renderLayer)).getValue(); + int geometryTextureID = TextureTracker.getRenderLayerTextureGlId(renderLayer, + textureManager, MissingSprite.getMissingSpriteId()); int vertexFormatID = Constants.VertexFormats.getValue( - vertexBuffer.getDrawParameters() - .format()); + vertexBuffer.getDrawParameters().format()); geometryTypeBB.putInt(geometryTypeBaseAddr, geometryTypeID); geometryTypeBaseAddr += Integer.BYTES; @@ -336,28 +507,26 @@ public boolean isEmpty(RenderLayer layer) { vertexFormatBaseAddr += Integer.BYTES; vertexCountBB.putInt(vertexCountBaseAddr, - vertexBuffer.getDrawParameters() - .vertexCount()); + vertexBuffer.getDrawParameters().vertexCount()); vertexCountBaseAddr += Integer.BYTES; verticesBB.putLong(verticesBaseAddr, vertexBufferInfo.addr()); verticesBaseAddr += Long.BYTES; } - rebuildSingle(builtChunk.getOrigin() - .getX(), - builtChunk.getOrigin() - .getY(), - builtChunk.getOrigin() - .getZ(), + rebuildSingle( + builtChunk.getOrigin().getX(), + builtChunk.getOrigin().getY(), + builtChunk.getOrigin().getZ(), builtChunk.index, - buffers.size(), + blasBuffers.size(), geometryTypeAddr, geometryGroupNameAddr, geometryTextureAddr, vertexFormatAddr, vertexCountAddr, verticesAddr, + lightStateHash, important); } finally { if (geometryTypeBB != null) { @@ -390,6 +559,193 @@ public boolean isEmpty(RenderLayer layer) { } } + public static void queueSpecialGeometry(List builtChunks, Camera camera) { + if (builtChunks == null || builtChunks.isEmpty()) { + return; + } + + synchronized (specialChunkGeometryLock) { + EntityProxy.EntityRenderDataList entityRenderDataList = new EntityProxy.EntityRenderDataList(); + for (ChunkBuilder.BuiltChunk builtChunk : builtChunks) { + ChunkSpecialRenderData chunkSpecialRenderData = specialChunkGeometry.get( + builtChunk.index); + if (chunkSpecialRenderData == null) { + continue; + } + + entityRenderDataList.add(chunkSpecialRenderData.entityRenderData); + } + + if (entityRenderDataList.isEmpty()) { + return; + } + + EntityProxy.queueBuildWithoutClose(entityRenderDataList); + } + } + + private static void replaceSpecialChunkGeometry(long index, + ChunkSpecialRenderData chunkSpecialRenderData) { + synchronized (specialChunkGeometryLock) { + ChunkSpecialRenderData previous = specialChunkGeometry.remove(index); + if (previous != null) { + previous.close(); + } + + if (chunkSpecialRenderData != null) { + specialChunkGeometry.put(index, chunkSpecialRenderData); + } + } + } + + private static void clearSpecialChunkGeometry(long index) { + synchronized (specialChunkGeometryLock) { + ChunkSpecialRenderData previous = specialChunkGeometry.remove(index); + if (previous != null) { + previous.close(); + } + } + } + + private static BuiltBuffer cloneBuiltBuffer(BuiltBuffer builtBuffer) { + ByteBuffer sourceSlice = builtBuffer.getBuffer().slice(); + BufferAllocator bufferAllocator = new BufferAllocator(sourceSlice.remaining()); + long targetAddress = bufferAllocator.allocate(sourceSlice.remaining()); + MemoryUtil.memCopy(memAddress(sourceSlice), targetAddress, sourceSlice.remaining()); + + BufferAllocator.CloseableBuffer closeableBuffer = bufferAllocator.getAllocated(); + if (closeableBuffer == null) { + bufferAllocator.close(); + throw new IllegalStateException("Failed to clone built buffer"); + } + + BuiltBuffer.DrawParameters drawParameters = builtBuffer.getDrawParameters(); + return new BuiltBuffer(closeableBuffer, + new BuiltBuffer.DrawParameters( + drawParameters.format(), + drawParameters.vertexCount(), + drawParameters.indexCount(), + drawParameters.mode(), + drawParameters.indexType())); + } + + private static ChunkTraversalData captureChunkTraversalData( + ChunkRendererRegion chunkRendererRegion, + ChunkSectionPos chunkSectionPos) { + BlockPos minPos = chunkSectionPos.getMinPos(); + boolean captureMaterialPalette = RayTracingTuning.shouldCaptureMaterialPalette(); + boolean captureFaceMask = RayTracingTuning.shouldCaptureFaceMask(); + int macrocellSize = RayTracingTuning.effectiveMacrocellSize(); + ChunkTraversalData chunkTraversalData = new ChunkTraversalData( + RayTracingTuning.getChunkDataLayout(), RayTracingTuning.getChunkTraversalMode(), + captureMaterialPalette, captureFaceMask, macrocellSize); + Map paletteLookup = captureMaterialPalette ? new HashMap<>() : null; + List paletteValues = captureMaterialPalette ? new ArrayList<>() : null; + + for (int localZ = 0; localZ < 16; localZ++) { + for (int localY = 0; localY < 16; localY++) { + for (int localX = 0; localX < 16; localX++) { + BlockPos blockPos = minPos.add(localX, localY, localZ); + BlockState blockState = chunkRendererRegion.getBlockState(blockPos); + if (blockState.isAir()) { + continue; + } + + int voxelIndex = localX | (localY << 4) | (localZ << 8); + chunkTraversalData.markOccupied(voxelIndex); + chunkTraversalData.markMacrocell(localX, localY, localZ); + + if (captureMaterialPalette) { + int stateId = Block.getRawIdFromState(blockState); + short paletteIndex = paletteLookup.computeIfAbsent(stateId, unused -> { + short nextIndex = (short) paletteValues.size(); + paletteValues.add(stateId); + return nextIndex; + }); + chunkTraversalData.materialIndices[voxelIndex] = paletteIndex; + } + + if (captureFaceMask) { + byte faceMask = 0; + for (Direction direction : Direction.values()) { + BlockState neighborState = chunkRendererRegion.getBlockState( + blockPos.offset(direction)); + if (neighborState.isAir() || !neighborState.isOpaqueFullCube()) { + faceMask |= (byte) (1 << direction.getId()); + } + } + chunkTraversalData.faceMask[voxelIndex] = faceMask; + } + } + } + } + + if (captureMaterialPalette) { + chunkTraversalData.materialPalette = paletteValues.stream() + .mapToInt(Integer::intValue) + .toArray(); + } + return chunkTraversalData; + } + + private static long computeLightStateHash(ChunkRendererRegion chunkRendererRegion, + ChunkSectionPos chunkSectionPos) { + BlockColors blockColors = MinecraftClient.getInstance().getBlockColors(); + IBlockColorsExt blockColorsExt = + blockColors instanceof IBlockColorsExt ext ? ext : null; + return ChunkLightCollector.collectHash(chunkRendererRegion, chunkSectionPos, blockColorsExt); + } + + private static long mixLightHash(long hash, int value) { + hash ^= (value & 0xFF); + hash *= FNV64_PRIME; + hash ^= ((value >>> 8) & 0xFF); + hash *= FNV64_PRIME; + hash ^= ((value >>> 16) & 0xFF); + hash *= FNV64_PRIME; + hash ^= ((value >>> 24) & 0xFF); + hash *= FNV64_PRIME; + return hash; + } + + private static Map preprocessChunkBuffers( + Map buffers) { + if (!RayTracingTuning.shouldAttemptTerrainMeshingOptimization() || buffers.isEmpty()) { + return buffers; + } + + RayTracingTuning.TerrainMeshingMode terrainMeshingMode = + RayTracingTuning.getEffectiveTerrainMeshingMode(); + int greedyMergeMaxSpan = RayTracingTuning.terrainGreedyMergeMaxSpan(); + if (RayTracingTuning.getWorldRepresentationMode() + == RayTracingTuning.WorldRepresentationMode.CHUNK_AABB) { + greedyMergeMaxSpan = Math.min(64, greedyMergeMaxSpan * 2); + } + switch (RayTracingTuning.getChunkTraversalMode()) { + case BRICK -> greedyMergeMaxSpan = Math.min(64, greedyMergeMaxSpan * 2); + case MACROCELL -> greedyMergeMaxSpan = 64; + default -> { + } + } + switch (RayTracingTuning.getChunkMacrocellSize()) { + case SIZE_4 -> greedyMergeMaxSpan = Math.min(64, greedyMergeMaxSpan * 2); + case SIZE_8 -> greedyMergeMaxSpan = 64; + case DISABLED -> { + } + } + Map optimizedBuffers = new HashMap<>(buffers.size()); + for (Map.Entry entry : buffers.entrySet()) { + BuiltBuffer originalBuffer = entry.getValue(); + BuiltBuffer optimizedBuffer = AtlasSafeGreedyMesher.optimize(originalBuffer, + terrainMeshingMode, greedyMergeMaxSpan); + if (optimizedBuffer != originalBuffer) { + originalBuffer.close(); + } + optimizedBuffers.put(entry.getKey(), optimizedBuffer); + } + return optimizedBuffers; + } + private static native void rebuildSingle(int originX, int originY, int originZ, @@ -401,6 +757,7 @@ private static native void rebuildSingle(int originX, long vertexFormats, long vertexCounts, long vertices, + long lightStateHash, boolean important); public static native boolean isChunkReady(long index); @@ -410,4 +767,73 @@ public static boolean isChunkReady(ChunkBuilder.BuiltChunk builtChunk) { } public static native void invalidateSingle(long index); + + public static native void markLightDirtySection(int sectionX, int sectionY, int sectionZ, + int lightType); + + private static final class ChunkSpecialRenderData { + + private final EntityProxy.EntityRenderData entityRenderData; + private final List ownedBuffers; + + private ChunkSpecialRenderData(EntityProxy.EntityRenderData entityRenderData, + List ownedBuffers) { + this.entityRenderData = entityRenderData; + this.ownedBuffers = ownedBuffers; + } + + private void close() { + for (BuiltBuffer ownedBuffer : ownedBuffers) { + ownedBuffer.close(); + } + } + } + + /** + * Java-side staging data for route-B traversal. Native consumption is still pending, but the + * chunk rebuild now materializes occupancy, palette, face-mask, and macrocell masks so the + * data path exists instead of living only on paper. + */ + private static final class ChunkTraversalData { + + private final long[] occupancyBitmask = new long[64]; + private final byte[] faceMask = new byte[16 * 16 * 16]; + private final short[] materialIndices = new short[16 * 16 * 16]; + private final int dataLayoutOrdinal; + private final int traversalModeOrdinal; + private final boolean capturesMaterialPalette; + private final boolean capturesFaceMask; + private final int macrocellSize; + private int[] materialPalette = new int[0]; + private long macrocell4Mask = 0L; + private byte macrocell8Mask = 0; + private int occupiedVoxelCount = 0; + + private ChunkTraversalData(RayTracingTuning.ChunkDataLayout chunkDataLayout, + RayTracingTuning.ChunkTraversalMode chunkTraversalMode, + boolean capturesMaterialPalette, + boolean capturesFaceMask, + int macrocellSize) { + this.dataLayoutOrdinal = chunkDataLayout.ordinal(); + this.traversalModeOrdinal = chunkTraversalMode.ordinal(); + this.capturesMaterialPalette = capturesMaterialPalette; + this.capturesFaceMask = capturesFaceMask; + this.macrocellSize = macrocellSize; + } + + private void markOccupied(int voxelIndex) { + occupancyBitmask[voxelIndex >> 6] |= 1L << (voxelIndex & 63); + occupiedVoxelCount++; + } + + private void markMacrocell(int localX, int localY, int localZ) { + if (macrocellSize == 4) { + macrocell4Mask |= 1L << ( + (localX >> 2) | ((localY >> 2) << 2) | ((localZ >> 2) << 4)); + } else if (macrocellSize == 8) { + macrocell8Mask |= (byte) (1 << ( + (localX >> 3) | ((localY >> 3) << 1) | ((localZ >> 3) << 2))); + } + } + } } diff --git a/src/main/java/com/radiance/client/proxy/world/EntityProxy.java b/src/main/java/com/radiance/client/proxy/world/EntityProxy.java index b82d78e..6a993c2 100644 --- a/src/main/java/com/radiance/client/proxy/world/EntityProxy.java +++ b/src/main/java/com/radiance/client/proxy/world/EntityProxy.java @@ -9,6 +9,9 @@ import com.radiance.client.constant.Constants; import com.radiance.client.constant.Constants.RayTracingFlags; import com.radiance.client.proxy.vulkan.BufferProxy; +import com.radiance.client.texture.TextureTracker; +import com.radiance.client.util.LightSourceDef; +import com.radiance.client.util.LightSourceRegistry; import com.radiance.client.vertex.PBRVertexConsumer; import com.radiance.client.vertex.StorageVertexConsumerProvider; import com.radiance.mixin_related.extensions.vulkan_render_integration.IHeldItemRendererExt; @@ -16,7 +19,11 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Queue; @@ -25,7 +32,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Stream; import net.minecraft.block.BlockState; import net.minecraft.block.ShapeContext; import net.minecraft.block.entity.BlockEntity; @@ -57,6 +63,7 @@ import net.minecraft.client.render.model.ModelBaker; import net.minecraft.client.texture.MissingSprite; import net.minecraft.client.texture.TextureManager; +import net.minecraft.client.util.BufferAllocator; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; @@ -65,7 +72,6 @@ import net.minecraft.entity.projectile.FishingBobberEntity; import net.minecraft.util.Colors; import net.minecraft.util.Identifier; -import net.minecraft.util.Pair; import net.minecraft.util.crash.CrashException; import net.minecraft.util.crash.CrashReport; import net.minecraft.util.crash.CrashReportSection; @@ -82,12 +88,38 @@ public class EntityProxy { public static final ConcurrentMap, AtomicInteger> PARTICLE_COUNTERS = new ConcurrentHashMap<>(); + private static final int DEFAULT_WORLD_ENTITY_BUFFER_SIZE = 786432; + private static final int BLOCK_CRUMBLING_BUFFER_SIZE = 65536; + private static final ThreadLocal BUILD_SCRATCH = + ThreadLocal.withInitial(BuildScratch::new); + private static final ConcurrentMap GEOMETRY_GROUP_NAME_CACHE = + new ConcurrentHashMap<>(); + private static EntityReplayCache worldEntityReplayCache = null; + private static final EnumMap + blockEntityReplayCaches = new EnumMap<>(BlockEntityUpdateBucket.class); + private static final EnumMap + particleReplayCaches = new EnumMap<>(ParticleUpdateBucket.class); + private static long worldEntityReplayFrameCounter = 0L; + private static long blockEntityReplayFrameCounter = 0L; + private static long particleReplayFrameCounter = 0L; private static final Identifier SUN_TEXTURE = Identifier.ofVanilla( "textures/environment/sun.png"); private static final Identifier MOON_PHASES_TEXTURE = Identifier.ofVanilla( "textures/environment/moon_phases.png"); + private enum BlockEntityUpdateBucket { + CRITICAL, + ACTIVE, + DECORATIVE + } + + private enum ParticleUpdateBucket { + CRITICAL, + GENERAL, + BACKGROUND + } + public static void processWorldEntityRenderData( StorageVertexConsumerProvider storageVertexConsumerProvider, int hashCode, @@ -170,10 +202,11 @@ private static void processEntityRenderData( continue; } + boolean layerReflect = RayTracingTuning.shouldReflectLayer(layer, reflect); if (layer.name.contains("water_mask")) { - waterMaskRenderData.add(new EntityRenderLayer(layer, buffer, reflect)); + waterMaskRenderData.add(new EntityRenderLayer(layer, buffer, layerReflect)); } else { - entityRenderData.add(new EntityRenderLayer(layer, buffer, reflect)); + entityRenderData.add(new EntityRenderLayer(layer, buffer, layerReflect)); } } @@ -192,25 +225,29 @@ public static void queueEntitiesBuild(Camera camera, RenderTickCounter tickCounter, boolean canDrawEntityOutlines) { MatrixStack matrixStack = new MatrixStack(); + worldEntityReplayFrameCounter++; MinecraftClient client = MinecraftClient.getInstance(); TickManager tickManager = Objects.requireNonNull(client.world) .getTickManager(); + int entityUpdateInterval = RayTracingTuning.entityUpdateIntervalFrames(); + Map entityReplayStates = captureWorldEntityReplayStates(camera, + renderedEntities, tickCounter, tickManager); + if (entityUpdateInterval > 1 && tryReplayWorldEntities(entityReplayStates, + entityUpdateInterval)) { + return; + } + if (entityUpdateInterval <= 1) { + clearWorldEntityReplayCache(); + } List entityStorageVertexConsumerProviders = new ArrayList<>(); EntityRenderDataList entityRenderDataList = new EntityRenderDataList(); for (Entity entity : renderedEntities) { - - if (entity.age == 0) { - entity.lastRenderX = entity.getX(); - entity.lastRenderY = entity.getY(); - entity.lastRenderZ = entity.getZ(); - } - StorageVertexConsumerProvider entityStorageVertexConsumerProvider = new StorageVertexConsumerProvider( - 786432); + DEFAULT_WORLD_ENTITY_BUFFER_SIZE); entityStorageVertexConsumerProviders.add(entityStorageVertexConsumerProvider); VertexConsumerProvider vertexConsumerProvider; @@ -230,13 +267,12 @@ public static void queueEntitiesBuild(Camera camera, vertexConsumerProvider = entityStorageVertexConsumerProvider; } - float tickDelta = tickCounter.getTickDelta(!tickManager.shouldSkipTick(entity)); - double entityPosX = MathHelper.lerp(tickDelta, entity.lastRenderX, - entity.getX()); - double entityPosY = MathHelper.lerp(tickDelta, entity.lastRenderY, - entity.getY()); - double entityPosZ = MathHelper.lerp(tickDelta, entity.lastRenderZ, - entity.getZ()); + EntityReplayState entityReplayState = entityReplayStates.get(System.identityHashCode( + entity)); + float tickDelta = entityReplayState.tickDelta(); + double entityPosX = entityReplayState.x(); + double entityPosY = entityReplayState.y(); + double entityPosZ = entityReplayState.z(); entityRenderDispatcher.render(entity, 0, @@ -247,7 +283,7 @@ public static void queueEntitiesBuild(Camera camera, vertexConsumerProvider, entityRenderDispatcher.getLight(entity, tickDelta)); - if (entity.equals(camera.getFocusedEntity())) { + if (entityReplayState.rtFlag() == Constants.RayTracingFlags.PLAYER.getValue()) { processWorldEntityRenderData(entityStorageVertexConsumerProvider, System.identityHashCode(entity), entityPosX, @@ -256,7 +292,8 @@ public static void queueEntitiesBuild(Camera camera, Constants.RayTracingFlags.PLAYER, true, entityRenderDataList); - } else if (entity instanceof FishingBobberEntity) { + } else if (entityReplayState.rtFlag() + == Constants.RayTracingFlags.FISHING_BOBBER.getValue()) { processWorldEntityRenderData(entityStorageVertexConsumerProvider, System.identityHashCode(entity), entityPosX, @@ -277,132 +314,94 @@ public static void queueEntitiesBuild(Camera camera, } } + if (entityUpdateInterval > 1 && !entityRenderDataList.isEmpty()) { + replaceWorldEntityReplayCache(createEntityReplayCache(entityRenderDataList, + entityReplayStates)); + } else { + clearWorldEntityReplayCache(); + } queueBuild(entityStorageVertexConsumerProviders, entityRenderDataList); } - public static synchronized Pair, EntityRenderDataList> queueBlockEntitiesRebuild( - BuiltChunkStorage chunks, + public static synchronized BlockEntityQueueResult queueBlockEntitiesRebuild( + List visibleBuiltChunks, Set noCullingBlockEntities, Long2ObjectMap> blockBreakingProgressions, BlockEntityRenderDispatcher blockEntityRenderDispatcher, float tickDelta) { - MatrixStack matrixStack = new MatrixStack(); - List entityStorageVertexConsumerProviders = new ArrayList<>(); - EntityRenderDataList entityRenderDataList = new EntityRenderDataList(); - - List crumblingStorageVertexConsumerProviders = new ArrayList<>(); - EntityRenderDataList crumblingRenderDataList = new EntityRenderDataList(); - for (ChunkBuilder.BuiltChunk builtChunk : chunks.chunks) { - List - list = - builtChunk.getData() - .getBlockEntities(); - if (!list.isEmpty()) { - for (BlockEntity blockEntity : list) { - StorageVertexConsumerProvider entityStorageVertexConsumerProvider = new StorageVertexConsumerProvider( - 786432); - entityStorageVertexConsumerProviders.add(entityStorageVertexConsumerProvider); - StorageVertexConsumerProvider crumblingStorageVertexConsumerProvider = new StorageVertexConsumerProvider( - 0); - crumblingStorageVertexConsumerProviders.add( - crumblingStorageVertexConsumerProvider); - - VertexConsumerProvider vertexConsumerProvider = entityStorageVertexConsumerProvider; - - BlockPos blockPos = blockEntity.getPos(); - double entityPosX = blockPos.getX(); - double entityPosY = blockPos.getY(); - double entityPosZ = blockPos.getZ(); - - matrixStack.push(); - SortedSet sortedSet = blockBreakingProgressions.get( - blockPos.asLong()); - if (sortedSet != null && !sortedSet.isEmpty()) { - int - stage = - sortedSet.last() - .getStage(); - if (stage >= 0) { - MatrixStack.Entry entry = matrixStack.peek(); - VertexConsumer - vertexConsumer = - new OverlayVertexConsumer( - crumblingStorageVertexConsumerProvider.getBuffer( - ModelBaker.BLOCK_DESTRUCTION_RENDER_LAYERS.get( - stage)), entry, 1.0F); - vertexConsumerProvider = renderLayer -> { - VertexConsumer vertexConsumer2 = entityStorageVertexConsumerProvider.getBuffer( - renderLayer); - return renderLayer.hasCrumbling() ? VertexConsumers.union( - vertexConsumer, - vertexConsumer2) : - vertexConsumer2; - }; - } - } + blockEntityReplayFrameCounter++; + List blockEntityRenderEntries = collectBlockEntityRenderEntries( + visibleBuiltChunks, noCullingBlockEntities, blockBreakingProgressions); + EnumMap> bucketEntries = + bucketBlockEntityRenderEntries(blockEntityRenderEntries); + List freshEntityStorageVertexConsumerProviders = + new ArrayList<>(); + EntityRenderDataList freshEntityRenderDataList = new EntityRenderDataList(); + EntityRenderDataList replayedEntityRenderDataList = new EntityRenderDataList(); + List freshCrumblingStorageVertexConsumerProviders = + new ArrayList<>(); + EntityRenderDataList freshCrumblingRenderDataList = new EntityRenderDataList(); + EntityRenderDataList replayedCrumblingRenderDataList = new EntityRenderDataList(); + + for (BlockEntityUpdateBucket bucket : BlockEntityUpdateBucket.values()) { + List entriesForBucket = bucketEntries.getOrDefault(bucket, + List.of()); + if (entriesForBucket.isEmpty()) { + clearBlockEntityReplayCache(bucket); + continue; + } - blockEntityRenderDispatcher.render(blockEntity, tickDelta, matrixStack, - vertexConsumerProvider); - matrixStack.pop(); + int entityUpdateInterval = blockEntityUpdateIntervalFrames(bucket); + Map blockEntityReplayStates = + captureBlockEntityReplayStates(entriesForBucket); + if (entityUpdateInterval > 1 && tryReplayBlockEntities(bucket, blockEntityReplayStates, + entityUpdateInterval, replayedEntityRenderDataList, + replayedCrumblingRenderDataList)) { + continue; + } - processWorldEntityRenderData(entityStorageVertexConsumerProvider, - System.identityHashCode(blockEntity), - entityPosX, - entityPosY, - entityPosZ, - Constants.RayTracingFlags.WORLD, - true, - entityRenderDataList); - processWorldEntityRenderData(crumblingStorageVertexConsumerProvider, - System.identityHashCode(blockEntity) + 1, - entityPosX, - entityPosY, - entityPosZ, - Constants.RayTracingFlags.WORLD, - true, - crumblingRenderDataList); - } + BlockEntityBuildBatch blockEntityBuildBatch = renderBlockEntityEntries( + entriesForBucket, blockEntityRenderDispatcher, tickDelta); + freshEntityStorageVertexConsumerProviders.addAll( + blockEntityBuildBatch.entityStorageVertexConsumerProviders()); + freshEntityRenderDataList.addAll(blockEntityBuildBatch.entityRenderDataList()); + freshCrumblingStorageVertexConsumerProviders.addAll( + blockEntityBuildBatch.crumblingStorageVertexConsumerProviders()); + freshCrumblingRenderDataList.addAll(blockEntityBuildBatch.crumblingRenderDataList()); + + if (entityUpdateInterval > 1 && (!blockEntityBuildBatch.entityRenderDataList().isEmpty() + || !blockEntityBuildBatch.crumblingRenderDataList().isEmpty())) { + replaceBlockEntityReplayCache(bucket, createBlockEntityReplayCache( + blockEntityBuildBatch.entityRenderDataList(), + blockEntityBuildBatch.crumblingRenderDataList(), blockEntityReplayStates)); + } else { + clearBlockEntityReplayCache(bucket); } } - for (BlockEntity blockEntity : noCullingBlockEntities) { - StorageVertexConsumerProvider entityStorageVertexConsumerProvider = new StorageVertexConsumerProvider( - 786432); - entityStorageVertexConsumerProviders.add(entityStorageVertexConsumerProvider); - - BlockPos blockPos = blockEntity.getPos(); - double entityPosX = blockPos.getX(); - double entityPosY = blockPos.getY(); - double entityPosZ = blockPos.getZ(); - - matrixStack.push(); - blockEntityRenderDispatcher.render(blockEntity, tickDelta, matrixStack, - entityStorageVertexConsumerProvider); - matrixStack.pop(); - - processWorldEntityRenderData(entityStorageVertexConsumerProvider, - System.identityHashCode(blockEntity), - entityPosX, - entityPosY, - entityPosZ, - Constants.RayTracingFlags.WORLD, - true, - entityRenderDataList); + if (!replayedEntityRenderDataList.isEmpty()) { + queueBuildWithoutClose(replayedEntityRenderDataList); } - - queueBuild(entityStorageVertexConsumerProviders, entityRenderDataList); - - return new Pair<>(crumblingStorageVertexConsumerProviders, crumblingRenderDataList); + if (!freshEntityRenderDataList.isEmpty()) { + queueBuild(freshEntityStorageVertexConsumerProviders, freshEntityRenderDataList); + } + return new BlockEntityQueueResult(freshCrumblingStorageVertexConsumerProviders, + freshCrumblingRenderDataList, replayedCrumblingRenderDataList); } public static void queueCrumblingRebuild(Camera camera, Long2ObjectMap> blockBreakingProgressions, BlockRenderManager blockRenderManager, ClientWorld world, - List crumblingStorageVertexConsumerProviders, - EntityRenderDataList crumblingRenderDataList) { + BlockEntityQueueResult blockEntityQueueResult) { + if (blockEntityQueueResult.isEmpty() + && blockBreakingProgressions.isEmpty()) { + return; + } + MatrixStack matrixStack = new MatrixStack(); - List blockCrumblingStorageVertexConsumerProviders = new ArrayList<>(); + List blockCrumblingStorageVertexConsumerProviders = + new ArrayList<>(Math.max(1, blockBreakingProgressions.size())); EntityRenderDataList blockCrumblingRenderDataList = new EntityRenderDataList(); Vec3d vec3d = camera.getPos(); @@ -424,9 +423,12 @@ public static void queueCrumblingRebuild(Camera camera, stage = sortedSet.last() .getStage(); + if (stage < 0 || stage >= ModelBaker.BLOCK_DESTRUCTION_RENDER_LAYERS.size()) { + continue; + } StorageVertexConsumerProvider blockCrumblingStorageVertexConsumerProvider = new StorageVertexConsumerProvider( - 786432); + BLOCK_CRUMBLING_BUFFER_SIZE); blockCrumblingStorageVertexConsumerProviders.add( blockCrumblingStorageVertexConsumerProvider); @@ -454,21 +456,25 @@ public static void queueCrumblingRebuild(Camera camera, } } - List - storageVertexConsumerProviders = - Stream.concat(crumblingStorageVertexConsumerProviders.stream(), - blockCrumblingStorageVertexConsumerProviders.stream()) - .toList(); - - EntityRenderDataList - renderDataList = - Stream.concat(crumblingRenderDataList.stream(), blockCrumblingRenderDataList.stream()) - .collect(EntityRenderDataList::new, EntityRenderDataList::add, - EntityRenderDataList::addAll); - - queueBuild(storageVertexConsumerProviders, renderDataList, 0.0f, - Constants.Coordinates.WORLD, - true); + if (!blockEntityQueueResult.replayedCrumblingRenderDataList().isEmpty()) { + queueBuildWithoutClose(blockEntityQueueResult.replayedCrumblingRenderDataList(), 0.0f, + Constants.Coordinates.WORLD, true); + } + if (!blockEntityQueueResult.freshCrumblingRenderDataList().isEmpty()) { + if (blockEntityQueueResult.freshCrumblingStorageVertexConsumerProviders().isEmpty()) { + queueBuildWithoutClose(blockEntityQueueResult.freshCrumblingRenderDataList(), 0.0f, + Constants.Coordinates.WORLD, + true); + } else { + queueBuild(blockEntityQueueResult.freshCrumblingStorageVertexConsumerProviders(), + blockEntityQueueResult.freshCrumblingRenderDataList(), 0.0f, + Constants.Coordinates.WORLD, true); + } + } + if (!blockCrumblingRenderDataList.isEmpty()) { + queueBuild(blockCrumblingStorageVertexConsumerProviders, blockCrumblingRenderDataList, + 0.0f, Constants.Coordinates.WORLD, true); + } } public static void queueHandRebuild(BufferBuilderStorage buffers, float tickDelta, @@ -520,73 +526,57 @@ public static void queueHandRebuild(BufferBuilderStorage buffers, float tickDelt } public static void queueParticleRebuild(Camera camera, float tickDelta, Frustum frustum) { - List storageVertexConsumerProviders = new ArrayList<>(); - EntityRenderDataList renderDataList = new EntityRenderDataList(); - - StorageVertexConsumerProvider postStorageVertexConsumerProvider = new StorageVertexConsumerProvider( - 0); - storageVertexConsumerProviders.add(postStorageVertexConsumerProvider); - + particleReplayFrameCounter++; ParticleManager particleManager = MinecraftClient.getInstance().particleManager; IParticleManagerExt particleManagerExt = (IParticleManagerExt) particleManager; Map> particles = particleManagerExt.radiance$getParticles(); - - for (ParticleTextureSheet particleTextureSheet : particleManagerExt.radiance$getTextureSheets()) { - Queue particleQueue = particles.get(particleTextureSheet); - if (particleQueue != null && !particleQueue.isEmpty()) { - for (Particle particle : particleQueue) { - - VertexConsumer - vertexConsumer = - postStorageVertexConsumerProvider.getBuffer( - Objects.requireNonNull( - particleTextureSheet.renderType())); - - try { - particle.render(vertexConsumer, camera, tickDelta); - } catch (Throwable var11) { - CrashReport crashReport = CrashReport.create(var11, "Rendering Particle"); - CrashReportSection crashReportSection = crashReport.addElement( - "Particle being rendered"); - crashReportSection.add("Particle", particle); - crashReportSection.add("Particle Type", particleTextureSheet); - throw new CrashException(crashReport); - } - } + EnumMap> bucketEntries = + bucketParticleRenderEntries(particleManagerExt, particles, camera, frustum); + List freshStorageVertexConsumerProviders = new ArrayList<>(); + EntityRenderDataList freshRenderDataList = new EntityRenderDataList(); + EntityRenderDataList replayedRenderDataList = new EntityRenderDataList(); + + for (ParticleUpdateBucket bucket : ParticleUpdateBucket.values()) { + List entriesForBucket = bucketEntries.getOrDefault(bucket, + List.of()); + if (entriesForBucket.isEmpty()) { + clearParticleReplayCache(bucket); + continue; } - } - - processPostEntityRenderData(postStorageVertexConsumerProvider, 0, 0, 0, 0, renderDataList); - - StorageVertexConsumerProvider storageVertexConsumerProvider = new StorageVertexConsumerProvider( - 0); - storageVertexConsumerProviders.add(storageVertexConsumerProvider); - - Queue customParticleQueue = particles.get(ParticleTextureSheet.CUSTOM); - if (customParticleQueue != null && !customParticleQueue.isEmpty()) { - for (Particle particle : customParticleQueue) { - MatrixStack matrixStack = new MatrixStack(); + int particleUpdateInterval = particleUpdateIntervalFrames(bucket); + ParticleReplayState particleReplayState = captureParticleReplayState(entriesForBucket); + if (particleUpdateInterval > 1 && tryReplayParticles(bucket, particleReplayState, + particleUpdateInterval, replayedRenderDataList)) { + continue; + } - try { - particle.renderCustom(matrixStack, storageVertexConsumerProvider, camera, - tickDelta); - } catch (Throwable var10) { - CrashReport crashReport = CrashReport.create(var10, "Rendering Particle"); - CrashReportSection crashReportSection = crashReport.addElement( - "Particle being rendered"); - crashReportSection.add("Particle", particle::toString); - crashReportSection.add("Particle Type", "Custom"); - throw new CrashException(crashReport); - } + StorageVertexConsumerProvider storageVertexConsumerProvider = + new StorageVertexConsumerProvider(0); + freshStorageVertexConsumerProviders.add(storageVertexConsumerProvider); + int bucketRenderDataStart = freshRenderDataList.size(); + renderParticles(entriesForBucket, storageVertexConsumerProvider, camera, tickDelta); + processWorldEntityRenderData(storageVertexConsumerProvider, particleBucketHash(bucket), + 0, 0, 0, Constants.RayTracingFlags.PARTICLE, true, freshRenderDataList); + EntityRenderDataList bucketRenderDataList = sliceEntityRenderDataList( + freshRenderDataList, bucketRenderDataStart); + + if (particleUpdateInterval > 1 && !bucketRenderDataList.isEmpty()) { + replaceParticleReplayCache(bucket, createParticleReplayCache(bucketRenderDataList, + particleReplayState)); + } else { + clearParticleReplayCache(bucket); } } - processWorldEntityRenderData(storageVertexConsumerProvider, 0, 0, 0, 0, - Constants.RayTracingFlags.PARTICLE, true, renderDataList); - - queueBuild(storageVertexConsumerProviders, renderDataList, 0.0f, - Constants.Coordinates.CAMERA_SHIFT, false); + if (!replayedRenderDataList.isEmpty()) { + queueBuildWithoutClose(replayedRenderDataList, 0.0f, + Constants.Coordinates.CAMERA_SHIFT, false); + } + if (!freshRenderDataList.isEmpty()) { + queueBuild(freshStorageVertexConsumerProviders, freshRenderDataList, 0.0f, + Constants.Coordinates.CAMERA_SHIFT, false); + } } public static void queueTargetBlockOutlineRebuild(Camera camera, ClientWorld world) { @@ -682,222 +672,716 @@ public static void queueWeatherBuild(WeatherRendering weatherRendering, Constants.Coordinates.CAMERA_SHIFT, false); } - public static void queueBuild( - List storageVertexConsumerProviders, - EntityRenderDataList entityRenderDataList) { - queueBuild(storageVertexConsumerProviders, entityRenderDataList, 0.0125f, - Constants.Coordinates.WORLD, false); + public static void clearReplayCaches() { + clearWorldEntityReplayCache(); + clearBlockEntityReplayCache(); + clearParticleReplayCache(); + worldEntityReplayFrameCounter = 0L; + blockEntityReplayFrameCounter = 0L; + particleReplayFrameCounter = 0L; } - public static void queueBuild( - List storageVertexConsumerProviders, - EntityRenderDataList entityRenderDataList, - float lineWidth, - Constants.Coordinates coordinate, - boolean normalOffset) { - TextureManager - textureManager = - MinecraftClient.getInstance() - .getTextureManager(); - - int entityHashCodeSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityHashCodeBB = MemoryUtil.memAlloc(entityHashCodeSize); - long entityHashCodeAddr = memAddress(entityHashCodeBB); - int entityHashCodeBaseAddr = 0; - - int entityPosXSize = entityRenderDataList.getTotalEntityCount() * Double.BYTES; - ByteBuffer entityPosXBB = MemoryUtil.memAlloc(entityPosXSize); - long entityPosXAddr = memAddress(entityPosXBB); - int entityPosXBaseAddr = 0; - - int entityPosYSize = entityRenderDataList.getTotalEntityCount() * Double.BYTES; - ByteBuffer entityPosYBB = MemoryUtil.memAlloc(entityPosYSize); - long entityPosYAddr = memAddress(entityPosYBB); - int entityPosYBaseAddr = 0; + private static void clearWorldEntityReplayCache() { + if (worldEntityReplayCache != null) { + worldEntityReplayCache.close(); + worldEntityReplayCache = null; + } + } - int entityPosZSize = entityRenderDataList.getTotalEntityCount() * Double.BYTES; - ByteBuffer entityPosZBB = MemoryUtil.memAlloc(entityPosZSize); - long entityPosZAddr = memAddress(entityPosZBB); - int entityPosZBaseAddr = 0; + private static void clearBlockEntityReplayCache() { + for (BlockEntityReplayCache blockEntityReplayCache : blockEntityReplayCaches.values()) { + if (blockEntityReplayCache != null) { + blockEntityReplayCache.close(); + } + } + blockEntityReplayCaches.clear(); + } - int entityRTFlagSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityRTFlagBB = MemoryUtil.memAlloc(entityRTFlagSize); - long entityRTFlagAddr = memAddress(entityRTFlagBB); - int entityRTFlagBaseAddr = 0; + private static void clearBlockEntityReplayCache(BlockEntityUpdateBucket bucket) { + BlockEntityReplayCache blockEntityReplayCache = blockEntityReplayCaches.remove(bucket); + if (blockEntityReplayCache != null) { + blockEntityReplayCache.close(); + } + } - int entityPrebuiltBLASSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityPrebuiltBLASBB = MemoryUtil.memAlloc(entityPrebuiltBLASSize); - long entityPrebuiltBLASAddr = memAddress(entityPrebuiltBLASBB); - int entityPrebuiltBLASBaseAddr = 0; + private static void clearParticleReplayCache() { + for (ParticleReplayCache particleReplayCache : particleReplayCaches.values()) { + if (particleReplayCache != null) { + particleReplayCache.close(); + } + } + particleReplayCaches.clear(); + } - int entityPostSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityPostBB = MemoryUtil.memAlloc(entityPostSize); - long entityPostAddr = memAddress(entityPostBB); - int entityPostBaseAddr = 0; + private static void clearParticleReplayCache(ParticleUpdateBucket bucket) { + ParticleReplayCache particleReplayCache = particleReplayCaches.remove(bucket); + if (particleReplayCache != null) { + particleReplayCache.close(); + } + } - int entityLayerCountSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityLayerCountBB = MemoryUtil.memAlloc(entityLayerCountSize); - long entityLayerCountAddr = memAddress(entityLayerCountBB); - int entityLayerCountBaseAddr = 0; + private static Map captureWorldEntityReplayStates(Camera camera, + List renderedEntities, + RenderTickCounter tickCounter, + TickManager tickManager) { + Map entityReplayStates = new HashMap<>(); + for (Entity entity : renderedEntities) { + if (entity.age == 0) { + entity.lastRenderX = entity.getX(); + entity.lastRenderY = entity.getY(); + entity.lastRenderZ = entity.getZ(); + } - int geometryTypeSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer geometryTypeBB = MemoryUtil.memAlloc(geometryTypeSize); - long geometryTypeAddr = memAddress(geometryTypeBB); - int geometryTypeBaseAddr = 0; + float tickDelta = tickCounter.getTickDelta(!tickManager.shouldSkipTick(entity)); + double entityPosX = MathHelper.lerp(tickDelta, entity.lastRenderX, entity.getX()); + double entityPosY = MathHelper.lerp(tickDelta, entity.lastRenderY, entity.getY()); + double entityPosZ = MathHelper.lerp(tickDelta, entity.lastRenderZ, entity.getZ()); + int rtFlag = determineWorldEntityRtFlag(camera, entity); - int geometryGroupNameSize = entityRenderDataList.getTotalLayersCount() * Long.BYTES; - ByteBuffer geometryGroupNameBB = MemoryUtil.memAlloc(geometryGroupNameSize); - long geometryGroupNameAddr = memAddress(geometryGroupNameBB); - int geometryGroupNameBaseAddr = 0; - List geometryGroupNameBuffers = new ArrayList<>( - entityRenderDataList.getTotalLayersCount()); + entityReplayStates.put(System.identityHashCode(entity), + new EntityReplayState(entityPosX, entityPosY, entityPosZ, tickDelta, rtFlag)); + } + return entityReplayStates; + } - int geometryTextureSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer geometryTextureBB = MemoryUtil.memAlloc(geometryTextureSize); - long geometryTextureAddr = memAddress(geometryTextureBB); - int geometryTextureBaseAddr = 0; + private static List collectBlockEntityRenderEntries( + List visibleBuiltChunks, + Set noCullingBlockEntities, + Long2ObjectMap> blockBreakingProgressions) { + Map blockEntityRenderEntries = new LinkedHashMap<>(); + if (visibleBuiltChunks != null) { + for (ChunkBuilder.BuiltChunk builtChunk : visibleBuiltChunks) { + List list = builtChunk.getData().getBlockEntities(); + if (list.isEmpty()) { + continue; + } + for (BlockEntity blockEntity : list) { + BlockEntityRenderEntry blockEntityRenderEntry = new BlockEntityRenderEntry( + blockEntity, + getBlockBreakingStage(blockBreakingProgressions, blockEntity.getPos()), + blockEntityRenderId(blockEntity, false), + blockEntityRenderId(blockEntity, true)); + blockEntityRenderEntries.put(blockEntityRenderEntry.mainRenderId(), + blockEntityRenderEntry); + } + } + } + for (BlockEntity blockEntity : noCullingBlockEntities) { + BlockEntityRenderEntry blockEntityRenderEntry = new BlockEntityRenderEntry(blockEntity, + -1, + blockEntityRenderId(blockEntity, false), blockEntityRenderId(blockEntity, true)); + blockEntityRenderEntries.putIfAbsent(blockEntityRenderEntry.mainRenderId(), + blockEntityRenderEntry); + } + return new ArrayList<>(blockEntityRenderEntries.values()); + } - int vertexFormatSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer vertexFormatBB = MemoryUtil.memAlloc(vertexFormatSize); - long vertexFormatAddr = memAddress(vertexFormatBB); - int vertexFormatBaseAddr = 0; + private static List collectAllChunkBlockEntityRenderEntries( + BuiltChunkStorage chunks, + Long2ObjectMap> blockBreakingProgressions) { + List blockEntityRenderEntries = new ArrayList<>(); + for (ChunkBuilder.BuiltChunk builtChunk : chunks.chunks) { + List list = builtChunk.getData().getBlockEntities(); + if (list.isEmpty()) { + continue; + } + for (BlockEntity blockEntity : list) { + blockEntityRenderEntries.add(new BlockEntityRenderEntry(blockEntity, + getBlockBreakingStage(blockBreakingProgressions, blockEntity.getPos()), + blockEntityRenderId(blockEntity, false), + blockEntityRenderId(blockEntity, true))); + } + } + return blockEntityRenderEntries; + } - int indexFormatSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer indexFormatBB = MemoryUtil.memAlloc(indexFormatSize); - long indexFormatAddr = memAddress(indexFormatBB); - int indexFormatBaseAddr = 0; + private static EnumMap> bucketBlockEntityRenderEntries( + List blockEntityRenderEntries) { + EnumMap> bucketEntries = + new EnumMap<>(BlockEntityUpdateBucket.class); + for (BlockEntityUpdateBucket bucket : BlockEntityUpdateBucket.values()) { + bucketEntries.put(bucket, new ArrayList<>()); + } + for (BlockEntityRenderEntry blockEntityRenderEntry : blockEntityRenderEntries) { + bucketEntries.get(classifyBlockEntityUpdateBucket(blockEntityRenderEntry)) + .add(blockEntityRenderEntry); + } + return bucketEntries; + } - int vertexCountSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer vertexCountBB = MemoryUtil.memAlloc(vertexCountSize); - long vertexCountAddr = memAddress(vertexCountBB); - int vertexCountBaseAddr = 0; + private static BlockEntityUpdateBucket classifyBlockEntityUpdateBucket( + BlockEntityRenderEntry blockEntityRenderEntry) { + if (blockEntityRenderEntry.crumblingStage() >= 0) { + return BlockEntityUpdateBucket.CRITICAL; + } - int verticesSize = entityRenderDataList.getTotalLayersCount() * Long.BYTES; - ByteBuffer verticesBB = MemoryUtil.memAlloc(verticesSize); - long verticesAddr = memAddress(verticesBB); - int verticesBaseAddr = 0; + String blockEntityName = + blockEntityRenderEntry.blockEntity().getClass().getSimpleName().toLowerCase( + Locale.ROOT); + if (blockEntityName.contains("chest") || blockEntityName.contains("shulker") + || blockEntityName.contains("beacon") || blockEntityName.contains("campfire") + || blockEntityName.contains("conduit") || blockEntityName.contains("bell") + || blockEntityName.contains("endgateway") || blockEntityName.contains("portal")) { + return BlockEntityUpdateBucket.CRITICAL; + } + if (blockEntityName.contains("sign") || blockEntityName.contains("banner") + || blockEntityName.contains("skull") || blockEntityName.contains("head") + || blockEntityName.contains("spawner") || blockEntityName.contains("furnace") + || blockEntityName.contains("brewing") || blockEntityName.contains("enchant") + || blockEntityName.contains("pot")) { + return BlockEntityUpdateBucket.ACTIVE; + } + return BlockEntityUpdateBucket.DECORATIVE; + } - for (EntityRenderData entityRenderData : entityRenderDataList) { - entityHashCodeBB.putInt(entityHashCodeBaseAddr, entityRenderData.hashCode); - entityHashCodeBaseAddr += Integer.BYTES; + private static int blockEntityUpdateIntervalFrames(BlockEntityUpdateBucket bucket) { + int baseInterval = RayTracingTuning.blockEntityUpdateIntervalFrames(); + if (baseInterval <= 1) { + return 1; + } + return switch (bucket) { + case CRITICAL -> Math.max(1, baseInterval - 1); + case ACTIVE -> baseInterval; + case DECORATIVE -> Math.min(6, baseInterval + 2); + }; + } - entityPosXBB.putDouble(entityPosXBaseAddr, entityRenderData.x); - entityPosXBaseAddr += Double.BYTES; + private static BlockEntityBuildBatch renderBlockEntityEntries( + List blockEntityRenderEntries, + BlockEntityRenderDispatcher blockEntityRenderDispatcher, + float tickDelta) { + MatrixStack matrixStack = new MatrixStack(); + List entityStorageVertexConsumerProviders = new ArrayList<>(); + EntityRenderDataList entityRenderDataList = new EntityRenderDataList(); + List crumblingStorageVertexConsumerProviders = + new ArrayList<>(); + EntityRenderDataList crumblingRenderDataList = new EntityRenderDataList(); - entityPosYBB.putDouble(entityPosYBaseAddr, entityRenderData.y); - entityPosYBaseAddr += Double.BYTES; + for (BlockEntityRenderEntry blockEntityRenderEntry : blockEntityRenderEntries) { + BlockEntity blockEntity = blockEntityRenderEntry.blockEntity(); + StorageVertexConsumerProvider entityStorageVertexConsumerProvider = + new StorageVertexConsumerProvider(DEFAULT_WORLD_ENTITY_BUFFER_SIZE); + entityStorageVertexConsumerProviders.add(entityStorageVertexConsumerProvider); - entityPosZBB.putDouble(entityPosZBaseAddr, entityRenderData.z); - entityPosZBaseAddr += Double.BYTES; + BlockPos blockPos = blockEntity.getPos(); + double entityPosX = blockPos.getX(); + double entityPosY = blockPos.getY(); + double entityPosZ = blockPos.getZ(); - entityRTFlagBB.putInt(entityRTFlagBaseAddr, entityRenderData.rtFlag); - entityRTFlagBaseAddr += Integer.BYTES; + matrixStack.push(); + VertexConsumerProvider vertexConsumerProvider = entityStorageVertexConsumerProvider; + StorageVertexConsumerProvider crumblingStorageVertexConsumerProvider = null; + int crumblingStage = blockEntityRenderEntry.crumblingStage(); + if (crumblingStage >= 0 + && crumblingStage < ModelBaker.BLOCK_DESTRUCTION_RENDER_LAYERS.size()) { + crumblingStorageVertexConsumerProvider = new StorageVertexConsumerProvider( + BLOCK_CRUMBLING_BUFFER_SIZE); + crumblingStorageVertexConsumerProviders.add(crumblingStorageVertexConsumerProvider); + MatrixStack.Entry entry = matrixStack.peek(); + VertexConsumer vertexConsumer = new OverlayVertexConsumer( + crumblingStorageVertexConsumerProvider.getBuffer( + ModelBaker.BLOCK_DESTRUCTION_RENDER_LAYERS.get(crumblingStage)), entry, + 1.0F); + vertexConsumerProvider = renderLayer -> { + VertexConsumer vertexConsumer2 = + entityStorageVertexConsumerProvider.getBuffer(renderLayer); + return renderLayer.hasCrumbling() ? VertexConsumers.union(vertexConsumer, + vertexConsumer2) : vertexConsumer2; + }; + } - entityPrebuiltBLASBB.putInt(entityPrebuiltBLASBaseAddr, entityRenderData.prebuiltBLAS); - entityPrebuiltBLASBaseAddr += Integer.BYTES; + blockEntityRenderDispatcher.render(blockEntity, tickDelta, matrixStack, + vertexConsumerProvider); + matrixStack.pop(); - entityPostBB.putInt(entityPostBaseAddr, entityRenderData.post ? 1 : 0); - entityPostBaseAddr += Integer.BYTES; + processWorldEntityRenderData(entityStorageVertexConsumerProvider, + blockEntityRenderEntry.mainRenderId(), entityPosX, entityPosY, entityPosZ, + Constants.RayTracingFlags.WORLD, true, entityRenderDataList); + if (crumblingStorageVertexConsumerProvider != null) { + processWorldEntityRenderData(crumblingStorageVertexConsumerProvider, + blockEntityRenderEntry.crumblingRenderId(), entityPosX, entityPosY, entityPosZ, + Constants.RayTracingFlags.WORLD, true, crumblingRenderDataList); + } + } - entityLayerCountBB.putInt(entityLayerCountBaseAddr, entityRenderData.size()); - entityLayerCountBaseAddr += Integer.BYTES; + return new BlockEntityBuildBatch(entityStorageVertexConsumerProviders, entityRenderDataList, + crumblingStorageVertexConsumerProviders, crumblingRenderDataList); + } - for (EntityRenderLayer entityRenderLayer : entityRenderData) { - RenderLayer renderLayer = entityRenderLayer.renderLayer; - BuiltBuffer vertexBuffer = entityRenderLayer.builtBuffer; + private static Map captureBlockEntityReplayStates( + List blockEntityRenderEntries) { + Map blockEntityReplayStates = new LinkedHashMap<>(); + for (BlockEntityRenderEntry blockEntityRenderEntry : blockEntityRenderEntries) { + BlockEntity blockEntity = blockEntityRenderEntry.blockEntity(); + BlockPos blockPos = blockEntity.getPos(); + double entityPosX = blockPos.getX(); + double entityPosY = blockPos.getY(); + double entityPosZ = blockPos.getZ(); + int blockStateHash = Objects.hashCode(blockEntity.getCachedState()); + blockEntityReplayStates.put(blockEntityRenderEntry.mainRenderId(), + new BlockEntityReplayState(entityPosX, entityPosY, entityPosZ, blockStateHash)); + if (blockEntityRenderEntry.crumblingStage() >= 0) { + blockEntityReplayStates.put(blockEntityRenderEntry.crumblingRenderId(), + new BlockEntityReplayState(entityPosX, entityPosY, entityPosZ, + 31 * blockStateHash + blockEntityRenderEntry.crumblingStage() + 1)); + } + } + return blockEntityReplayStates; + } - Identifier - identifier = - ((RenderLayer.MultiPhase) renderLayer).phases.texture.getId() - .orElse(MissingSprite.getMissingSpriteId()); - int - geometryTypeID = - Constants.GeometryTypes.getGeometryType(renderLayer, entityRenderLayer.reflect) - .getValue(); - int - geometryTextureID = - textureManager.getTexture(identifier) - .getGlId(); - int - vertexFormatID = - Constants.VertexFormats.getValue(vertexBuffer.getDrawParameters() - .format()); - int - indexFormatID = - Constants.DrawModes.getValue(vertexBuffer.getDrawParameters() - .mode()); + private static int getBlockBreakingStage( + Long2ObjectMap> blockBreakingProgressions, + BlockPos blockPos) { + SortedSet sortedSet = blockBreakingProgressions.get(blockPos.asLong()); + if (sortedSet == null || sortedSet.isEmpty()) { + return -1; + } + return sortedSet.last().getStage(); + } - BufferProxy.BufferInfo vertexBufferInfo = BufferProxy.getBufferInfo( - vertexBuffer.getBuffer()); - assert vertexBuffer.getDrawParameters() - .indexCount() == vertexBuffer.getDrawParameters() - .vertexCount() / 4 * 6; + private static EnumMap> bucketParticleRenderEntries( + IParticleManagerExt particleManagerExt, + Map> particles, + Camera camera, + Frustum frustum) { + EnumMap> bucketEntries = + new EnumMap<>(ParticleUpdateBucket.class); + for (ParticleUpdateBucket bucket : ParticleUpdateBucket.values()) { + bucketEntries.put(bucket, new ArrayList<>()); + } - geometryTypeBB.putInt(geometryTypeBaseAddr, geometryTypeID); - geometryTypeBaseAddr += Integer.BYTES; + for (ParticleTextureSheet particleTextureSheet : particleManagerExt.radiance$getTextureSheets()) { + Queue particleQueue = particles.get(particleTextureSheet); + if (particleQueue == null || particleQueue.isEmpty()) { + continue; + } - ByteBuffer geometryGroupNameBuffer = MemoryUtil.memUTF8(renderLayer.name, true); - geometryGroupNameBuffers.add(geometryGroupNameBuffer); - geometryGroupNameBB.putLong(geometryGroupNameBaseAddr, memAddress(geometryGroupNameBuffer)); - geometryGroupNameBaseAddr += Long.BYTES; + for (Particle particle : particleQueue) { + if (isParticleDefinitelyInvisible(particle, camera, frustum)) { + continue; + } + ParticleRenderEntry particleRenderEntry = new ParticleRenderEntry(particle, + particleTextureSheet, false); + bucketEntries.get(classifyParticleUpdateBucket(particleRenderEntry)) + .add(particleRenderEntry); + } + } - geometryTextureBB.putInt(geometryTextureBaseAddr, geometryTextureID); - geometryTextureBaseAddr += Integer.BYTES; + Queue customParticleQueue = particles.get(ParticleTextureSheet.CUSTOM); + if (customParticleQueue != null && !customParticleQueue.isEmpty()) { + for (Particle particle : customParticleQueue) { + if (isParticleDefinitelyInvisible(particle, camera, frustum)) { + continue; + } + ParticleRenderEntry particleRenderEntry = new ParticleRenderEntry(particle, + ParticleTextureSheet.CUSTOM, true); + bucketEntries.get(classifyParticleUpdateBucket(particleRenderEntry)) + .add(particleRenderEntry); + } + } + return bucketEntries; + } - vertexFormatBB.putInt(vertexFormatBaseAddr, vertexFormatID); - vertexFormatBaseAddr += Integer.BYTES; + private static boolean isParticleDefinitelyInvisible(Particle particle, Camera camera, + Frustum frustum) { + if (particle == null) { + return true; + } + if (frustum == null) { + return false; + } - indexFormatBB.putInt(indexFormatBaseAddr, indexFormatID); - indexFormatBaseAddr += Integer.BYTES; + try { + return !frustum.isVisible(particle.getBoundingBox()); + } catch (Throwable ignored) { + return false; + } + } - vertexCountBB.putInt(vertexCountBaseAddr, - vertexBuffer.getDrawParameters() - .vertexCount()); - vertexCountBaseAddr += Integer.BYTES; + private static ParticleUpdateBucket classifyParticleUpdateBucket( + ParticleRenderEntry particleRenderEntry) { + String particleName = particleRenderEntry.particle().getClass().getSimpleName() + .toLowerCase(Locale.ROOT); + if (particleRenderEntry.custom()) { + return ParticleUpdateBucket.GENERAL; + } + if (particleName.contains("crit") || particleName.contains("sweep") + || particleName.contains("firework") || particleName.contains("explosion") + || particleName.contains("flame") || particleName.contains("lava") + || particleName.contains("campfire") || particleName.contains("portal") + || particleName.contains("endrod") || particleName.contains("end_rod") + || particleName.contains("lightning") || particleName.contains("rain") + || particleName.contains("snow")) { + return ParticleUpdateBucket.CRITICAL; + } + if (particleName.contains("smoke") || particleName.contains("poof") + || particleName.contains("dust") || particleName.contains("spell") + || particleName.contains("effect") || particleName.contains("cloud") + || particleName.contains("ash") || particleName.contains("sculk")) { + return ParticleUpdateBucket.GENERAL; + } + return ParticleUpdateBucket.BACKGROUND; + } - verticesBB.putLong(verticesBaseAddr, vertexBufferInfo.addr()); - verticesBaseAddr += Long.BYTES; + private static int particleUpdateIntervalFrames(ParticleUpdateBucket bucket) { + int baseInterval = RayTracingTuning.particleUpdateIntervalFrames(); + if (baseInterval <= 1) { + return 1; + } + return switch (bucket) { + case CRITICAL -> Math.max(1, baseInterval - 1); + case GENERAL -> baseInterval; + case BACKGROUND -> Math.min(6, baseInterval + 2); + }; + } + + private static int particleBucketHash(ParticleUpdateBucket bucket) { + return 0x5000 + bucket.ordinal(); + } + + private static void renderParticles(List particleRenderEntries, + StorageVertexConsumerProvider storageVertexConsumerProvider, + Camera camera, + float tickDelta) { + for (ParticleRenderEntry particleRenderEntry : particleRenderEntries) { + Particle particle = particleRenderEntry.particle(); + if (!particleRenderEntry.custom()) { + ParticleVertexConsumerProvider particleVertexConsumerProvider = + new ParticleVertexConsumerProvider(storageVertexConsumerProvider, particle); + VertexConsumer vertexConsumer = particleVertexConsumerProvider.getBuffer( + Objects.requireNonNull(particleRenderEntry.textureSheet().renderType())); + try { + particle.render(vertexConsumer, camera, tickDelta); + } catch (Throwable throwable) { + CrashReport crashReport = CrashReport.create(throwable, "Rendering Particle"); + CrashReportSection crashReportSection = crashReport.addElement( + "Particle being rendered"); + crashReportSection.add("Particle", particle); + crashReportSection.add("Particle Type", particleRenderEntry.textureSheet()); + throw new CrashException(crashReport); + } + continue; + } + + MatrixStack matrixStack = new MatrixStack(); + ParticleVertexConsumerProvider particleVertexConsumerProvider = + new ParticleVertexConsumerProvider(storageVertexConsumerProvider, particle); + try { + particle.renderCustom(matrixStack, particleVertexConsumerProvider, camera, + tickDelta); + } catch (Throwable throwable) { + CrashReport crashReport = CrashReport.create(throwable, "Rendering Particle"); + CrashReportSection crashReportSection = crashReport.addElement( + "Particle being rendered"); + crashReportSection.add("Particle", particle::toString); + crashReportSection.add("Particle Type", "Custom"); + throw new CrashException(crashReport); } } + } - queueBuild(lineWidth, - coordinate.getValue(), - normalOffset, - entityRenderDataList.getTotalEntityCount(), - entityHashCodeAddr, - entityPosXAddr, - entityPosYAddr, - entityPosZAddr, - entityRTFlagAddr, - entityPrebuiltBLASAddr, - entityPostAddr, - entityLayerCountAddr, - geometryTypeAddr, - geometryGroupNameAddr, - geometryTextureAddr, - vertexFormatAddr, - indexFormatAddr, - vertexCountAddr, - verticesAddr); - - // free - MemoryUtil.memFree(entityPosXBB); - MemoryUtil.memFree(entityPosYBB); - MemoryUtil.memFree(entityPosZBB); - MemoryUtil.memFree(entityRTFlagBB); - MemoryUtil.memFree(entityPrebuiltBLASBB); - MemoryUtil.memFree(entityPostBB); - MemoryUtil.memFree(entityLayerCountBB); - MemoryUtil.memFree(geometryTypeBB); - MemoryUtil.memFree(geometryGroupNameBB); - for (ByteBuffer geometryGroupNameBuffer : geometryGroupNameBuffers) { - MemoryUtil.memFree(geometryGroupNameBuffer); - } - MemoryUtil.memFree(geometryTextureBB); - MemoryUtil.memFree(vertexFormatBB); - MemoryUtil.memFree(indexFormatBB); - MemoryUtil.memFree(vertexCountBB); - MemoryUtil.memFree(verticesBB); + private static ParticleReplayState captureParticleReplayState( + List particleRenderEntries) { + long signature = 0xcbf29ce484222325L; + int regularParticleCount = 0; + int customParticleCount = 0; + for (ParticleRenderEntry particleRenderEntry : particleRenderEntries) { + signature = mixReplaySignature(signature, + System.identityHashCode(particleRenderEntry.textureSheet())); + signature = mixReplaySignature(signature, + System.identityHashCode(particleRenderEntry.particle())); + signature = mixReplaySignature(signature, + particleRenderEntry.particle().getClass().hashCode()); + if (particleRenderEntry.custom()) { + customParticleCount++; + } else { + regularParticleCount++; + } + } + return new ParticleReplayState(regularParticleCount + customParticleCount, + customParticleCount, signature); + } + + private static float particleEmissionStrength(Particle particle) { + String particleName = particle.getClass().getSimpleName().toLowerCase(Locale.ROOT); + LightSourceDef registeredSource = LightSourceRegistry.findParticle(particleName); + if (registeredSource != null) { + return LightSourceRegistry.resolveStrength(registeredSource, 0.0f); + } + if (particleName.contains("soul")) { + return 1.6f; + } + if (particleName.contains("flame") || particleName.contains("lava") + || particleName.contains("campfire")) { + return 1.35f; + } + if (particleName.contains("endrod") || particleName.contains("end_rod") + || particleName.contains("glow") || particleName.contains("firework") + || particleName.contains("spark") || particleName.contains("electric")) { + return 1.15f; + } + if (particleName.contains("portal") || particleName.contains("dragon") + || particleName.contains("sculk")) { + return 0.85f; + } + if (particleName.contains("spell") || particleName.contains("effect") + || particleName.contains("potion")) { + return 0.55f; + } + if (particleName.contains("crit") || particleName.contains("ench")) { + return RayTracingTuning.critParticlesGlowStrength(); + } + if (particleName.contains("poof") || particleName.contains("smoke")) { + return RayTracingTuning.deathSmokeParticlesGlowStrength(); + } + if (particleName.contains("redstone") || particleName.contains("dust")) { + return 0.28f; + } + if (particleName.contains("wax") || particleName.contains("nautilus") + || particleName.contains("composter")) { + return 0.18f; + } + return 0.0f; + } + + private static int determineWorldEntityRtFlag(Camera camera, Entity entity) { + if (entity.equals(camera.getFocusedEntity())) { + return Constants.RayTracingFlags.PLAYER.getValue(); + } + if (entity instanceof FishingBobberEntity) { + return Constants.RayTracingFlags.FISHING_BOBBER.getValue(); + } + return Constants.RayTracingFlags.WORLD.getValue(); + } + + private static boolean tryReplayWorldEntities(Map entityReplayStates, + int entityUpdateInterval) { + if (worldEntityReplayCache == null || entityReplayStates.isEmpty()) { + return false; + } + + if (!canReplayThisFrame(worldEntityReplayFrameCounter, entityUpdateInterval)) { + return false; + } + + if (worldEntityReplayCache.entityStates.size() != entityReplayStates.size()) { + return false; + } + + for (Map.Entry entityReplayStateEntry : entityReplayStates.entrySet()) { + EntityReplayState cachedEntityReplayState = worldEntityReplayCache.entityStates.get( + entityReplayStateEntry.getKey()); + if (cachedEntityReplayState == null || cachedEntityReplayState.rtFlag() + != entityReplayStateEntry.getValue().rtFlag()) { + return false; + } + } + + applyReplayPositions(worldEntityReplayCache.entityRenderDataList, entityReplayStates); + queueBuildWithoutClose(worldEntityReplayCache.entityRenderDataList); + return true; + } + + private static boolean tryReplayBlockEntities(BlockEntityUpdateBucket bucket, + Map blockEntityReplayStates, + int entityUpdateInterval, + EntityRenderDataList entityRenderDataList, + EntityRenderDataList crumblingRenderDataList) { + BlockEntityReplayCache blockEntityReplayCache = blockEntityReplayCaches.get(bucket); + if (blockEntityReplayCache == null || blockEntityReplayStates.isEmpty()) { + return false; + } + if (!canReplayThisFrame(blockEntityReplayFrameCounter, entityUpdateInterval)) { + return false; + } + if (blockEntityReplayCache.renderStates.size() != blockEntityReplayStates.size()) { + return false; + } + + for (Map.Entry blockEntityReplayStateEntry : blockEntityReplayStates.entrySet()) { + BlockEntityReplayState cachedBlockEntityReplayState = blockEntityReplayCache.renderStates.get( + blockEntityReplayStateEntry.getKey()); + if (cachedBlockEntityReplayState == null || cachedBlockEntityReplayState.signature() + != blockEntityReplayStateEntry.getValue().signature()) { + return false; + } + } + + applyReplayPositions(blockEntityReplayCache.entityRenderDataList, blockEntityReplayStates); + applyReplayPositions(blockEntityReplayCache.crumblingRenderDataList, + blockEntityReplayStates); + entityRenderDataList.addAll(blockEntityReplayCache.entityRenderDataList); + crumblingRenderDataList.addAll(blockEntityReplayCache.crumblingRenderDataList); + return true; + } + + private static boolean tryReplayParticles(ParticleUpdateBucket bucket, + ParticleReplayState particleReplayState, + int entityUpdateInterval, + EntityRenderDataList entityRenderDataList) { + ParticleReplayCache particleReplayCache = particleReplayCaches.get(bucket); + if (particleReplayCache == null || particleReplayState.totalParticleCount() == 0) { + return false; + } + if (!canReplayThisFrame(particleReplayFrameCounter, entityUpdateInterval)) { + return false; + } + if (!particleReplayCache.particleReplayState.equals(particleReplayState)) { + return false; + } + + entityRenderDataList.addAll(particleReplayCache.entityRenderDataList); + return true; + } + + private static EntityReplayCache createEntityReplayCache(EntityRenderDataList entityRenderDataList, + Map entityReplayStates) { + List ownedBuffers = new ArrayList<>(); + EntityRenderDataList clonedRenderDataList = cloneEntityRenderDataList(entityRenderDataList, + ownedBuffers); + return new EntityReplayCache(clonedRenderDataList, new HashMap<>(entityReplayStates), + ownedBuffers); + } + + private static void replaceWorldEntityReplayCache(EntityReplayCache entityReplayCache) { + clearWorldEntityReplayCache(); + worldEntityReplayCache = entityReplayCache; + } + + private static BlockEntityReplayCache createBlockEntityReplayCache( + EntityRenderDataList entityRenderDataList, + EntityRenderDataList crumblingRenderDataList, + Map blockEntityReplayStates) { + List ownedBuffers = new ArrayList<>(); + EntityRenderDataList clonedEntityRenderDataList = cloneEntityRenderDataList( + entityRenderDataList, ownedBuffers); + EntityRenderDataList clonedCrumblingRenderDataList = cloneEntityRenderDataList( + crumblingRenderDataList, ownedBuffers); + return new BlockEntityReplayCache(clonedEntityRenderDataList, + clonedCrumblingRenderDataList, new LinkedHashMap<>(blockEntityReplayStates), + ownedBuffers); + } + + private static void replaceBlockEntityReplayCache(BlockEntityUpdateBucket bucket, + BlockEntityReplayCache newBlockEntityReplayCache) { + clearBlockEntityReplayCache(bucket); + blockEntityReplayCaches.put(bucket, newBlockEntityReplayCache); + } + + private static ParticleReplayCache createParticleReplayCache( + EntityRenderDataList entityRenderDataList, + ParticleReplayState particleReplayState) { + List ownedBuffers = new ArrayList<>(); + EntityRenderDataList clonedRenderDataList = cloneEntityRenderDataList(entityRenderDataList, + ownedBuffers); + return new ParticleReplayCache(clonedRenderDataList, particleReplayState, ownedBuffers); + } + + private static void replaceParticleReplayCache(ParticleUpdateBucket bucket, + ParticleReplayCache newParticleReplayCache) { + clearParticleReplayCache(bucket); + particleReplayCaches.put(bucket, newParticleReplayCache); + } + + private static EntityRenderDataList cloneEntityRenderDataList( + EntityRenderDataList sourceEntityRenderDataList, + List ownedBuffers) { + EntityRenderDataList clonedRenderDataList = new EntityRenderDataList(); + for (EntityRenderData entityRenderData : sourceEntityRenderDataList) { + EntityRenderData clonedRenderData = new EntityRenderData(entityRenderData.hashCode, + entityRenderData.x, entityRenderData.y, entityRenderData.z, + entityRenderData.rtFlag, entityRenderData.prebuiltBLAS, entityRenderData.post); + for (EntityRenderLayer entityRenderLayer : entityRenderData) { + BuiltBuffer clonedBuiltBuffer = cloneBuiltBuffer(entityRenderLayer.builtBuffer); + ownedBuffers.add(clonedBuiltBuffer); + clonedRenderData.add(new EntityRenderLayer(entityRenderLayer.renderLayer, + clonedBuiltBuffer, entityRenderLayer.reflect)); + } + clonedRenderDataList.add(clonedRenderData); + } + return clonedRenderDataList; + } + + private static EntityRenderDataList sliceEntityRenderDataList( + EntityRenderDataList sourceEntityRenderDataList, + int fromIndex) { + EntityRenderDataList slicedRenderDataList = new EntityRenderDataList(); + for (int i = fromIndex; i < sourceEntityRenderDataList.size(); i++) { + slicedRenderDataList.add(sourceEntityRenderDataList.get(i)); + } + return slicedRenderDataList; + } + + private static boolean canReplayThisFrame(long frameCounter, int entityUpdateInterval) { + return entityUpdateInterval > 1 + && Math.floorMod(frameCounter - 1L, entityUpdateInterval) != 0L; + } + + private static void applyReplayPositions(EntityRenderDataList entityRenderDataList, + Map replayStates) { + for (EntityRenderData entityRenderData : entityRenderDataList) { + PositionedReplayState replayState = replayStates.get(entityRenderData.hashCode); + if (replayState == null) { + continue; + } + entityRenderData.setX(replayState.x()); + entityRenderData.setY(replayState.y()); + entityRenderData.setZ(replayState.z()); + } + } + + private static int blockEntityRenderId(BlockEntity blockEntity, boolean crumbling) { + return System.identityHashCode(blockEntity) * 31 + (crumbling ? 1 : 0); + } + + private static long mixReplaySignature(long signature, int value) { + long mixedSignature = signature ^ Integer.toUnsignedLong(value); + return mixedSignature * 0x100000001b3L; + } + + private static BuiltBuffer cloneBuiltBuffer(BuiltBuffer builtBuffer) { + ByteBuffer sourceSlice = builtBuffer.getBuffer().slice(); + BufferAllocator bufferAllocator = new BufferAllocator(sourceSlice.remaining()); + long targetAddress = bufferAllocator.allocate(sourceSlice.remaining()); + MemoryUtil.memCopy(memAddress(sourceSlice), targetAddress, sourceSlice.remaining()); + + BufferAllocator.CloseableBuffer closeableBuffer = bufferAllocator.getAllocated(); + if (closeableBuffer == null) { + bufferAllocator.close(); + throw new IllegalStateException("Failed to clone built buffer"); + } + + BuiltBuffer.DrawParameters drawParameters = builtBuffer.getDrawParameters(); + return new BuiltBuffer(closeableBuffer, + new BuiltBuffer.DrawParameters( + drawParameters.format(), + drawParameters.vertexCount(), + drawParameters.indexCount(), + drawParameters.mode(), + drawParameters.indexType())); + } + + public static void queueBuild( + List storageVertexConsumerProviders, + EntityRenderDataList entityRenderDataList) { + queueBuild(storageVertexConsumerProviders, entityRenderDataList, 0.0125f, + Constants.Coordinates.WORLD, false); + } + + public static void queueBuild( + List storageVertexConsumerProviders, + EntityRenderDataList entityRenderDataList, + float lineWidth, + Constants.Coordinates coordinate, + boolean normalOffset) { + if (entityRenderDataList.isEmpty()) { + for (StorageVertexConsumerProvider storageVertexConsumerProvider : storageVertexConsumerProviders) { + storageVertexConsumerProvider.close(); + } + return; + } + + dispatchBuild(entityRenderDataList, lineWidth, coordinate, normalOffset); for (EntityRenderData entityRenderData : entityRenderDataList) { for (EntityRenderLayer entityRenderLayer : entityRenderData) { @@ -919,86 +1403,82 @@ public static void queueBuildWithoutClose(EntityRenderDataList entityRenderDataL float lineWidth, Constants.Coordinates coordinate, boolean normalOffset) { - TextureManager - textureManager = - MinecraftClient.getInstance() - .getTextureManager(); - - int entityHashCodeSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityHashCodeBB = MemoryUtil.memAlloc(entityHashCodeSize); - long entityHashCodeAddr = memAddress(entityHashCodeBB); - int entityHashCodeBaseAddr = 0; + if (entityRenderDataList.isEmpty()) { + return; + } - int entityPosXSize = entityRenderDataList.getTotalEntityCount() * Double.BYTES; - ByteBuffer entityPosXBB = MemoryUtil.memAlloc(entityPosXSize); - long entityPosXAddr = memAddress(entityPosXBB); - int entityPosXBaseAddr = 0; + dispatchBuild(entityRenderDataList, lineWidth, coordinate, normalOffset); + } - int entityPosYSize = entityRenderDataList.getTotalEntityCount() * Double.BYTES; - ByteBuffer entityPosYBB = MemoryUtil.memAlloc(entityPosYSize); - long entityPosYAddr = memAddress(entityPosYBB); - int entityPosYBaseAddr = 0; + private static void dispatchBuild(EntityRenderDataList entityRenderDataList, + float lineWidth, + Constants.Coordinates coordinate, + boolean normalOffset) { + BuildSubmissionData submission = prepareBuildSubmission(entityRenderDataList, + MinecraftClient.getInstance().getTextureManager()); - int entityPosZSize = entityRenderDataList.getTotalEntityCount() * Double.BYTES; - ByteBuffer entityPosZBB = MemoryUtil.memAlloc(entityPosZSize); - long entityPosZAddr = memAddress(entityPosZBB); - int entityPosZBaseAddr = 0; + queueBuild(lineWidth, + coordinate.getValue(), + normalOffset, + submission.entityCount(), + submission.entityHashCodes(), + submission.entityPosXs(), + submission.entityPosYs(), + submission.entityPosZs(), + submission.entityRTFlags(), + submission.entityPrebuiltBLASs(), + submission.entityPosts(), + submission.entityLayerCounts(), + submission.geometryTypes(), + submission.geometryGroupNames(), + submission.geometryTextures(), + submission.vertexFormats(), + submission.indexFormats(), + submission.vertexCounts(), + submission.vertices()); + } - int entityRTFlagSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityRTFlagBB = MemoryUtil.memAlloc(entityRTFlagSize); - long entityRTFlagAddr = memAddress(entityRTFlagBB); - int entityRTFlagBaseAddr = 0; + private static BuildSubmissionData prepareBuildSubmission(EntityRenderDataList entityRenderDataList, + TextureManager textureManager) { + int entityCount = entityRenderDataList.getTotalEntityCount(); + int layerCount = entityRenderDataList.getTotalLayersCount(); + BuildScratch scratch = BUILD_SCRATCH.get(); + + ByteBuffer entityHashCodeBB = scratch.acquire("entityHashCodes", + entityCount * Integer.BYTES); + ByteBuffer entityPosXBB = scratch.acquire("entityPosXs", entityCount * Double.BYTES); + ByteBuffer entityPosYBB = scratch.acquire("entityPosYs", entityCount * Double.BYTES); + ByteBuffer entityPosZBB = scratch.acquire("entityPosZs", entityCount * Double.BYTES); + ByteBuffer entityRTFlagBB = scratch.acquire("entityRTFlags", entityCount * Integer.BYTES); + ByteBuffer entityPrebuiltBLASBB = scratch.acquire("entityPrebuiltBLASs", + entityCount * Integer.BYTES); + ByteBuffer entityPostBB = scratch.acquire("entityPosts", entityCount * Integer.BYTES); + ByteBuffer entityLayerCountBB = scratch.acquire("entityLayerCounts", + entityCount * Integer.BYTES); + ByteBuffer geometryTypeBB = scratch.acquire("geometryTypes", layerCount * Integer.BYTES); + ByteBuffer geometryGroupNameBB = scratch.acquire("geometryGroupNames", + layerCount * Long.BYTES); + ByteBuffer geometryTextureBB = scratch.acquire("geometryTextures", + layerCount * Integer.BYTES); + ByteBuffer vertexFormatBB = scratch.acquire("vertexFormats", layerCount * Integer.BYTES); + ByteBuffer indexFormatBB = scratch.acquire("indexFormats", layerCount * Integer.BYTES); + ByteBuffer vertexCountBB = scratch.acquire("vertexCounts", layerCount * Integer.BYTES); + ByteBuffer verticesBB = scratch.acquire("vertices", layerCount * Long.BYTES); - int entityPrebuiltBLASSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityPrebuiltBLASBB = MemoryUtil.memAlloc(entityPrebuiltBLASSize); - long entityPrebuiltBLASAddr = memAddress(entityPrebuiltBLASBB); + int entityHashCodeBaseAddr = 0; + int entityPosXBaseAddr = 0; + int entityPosYBaseAddr = 0; + int entityPosZBaseAddr = 0; + int entityRTFlagBaseAddr = 0; int entityPrebuiltBLASBaseAddr = 0; - - int entityPostSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityPostBB = MemoryUtil.memAlloc(entityPostSize); - long entityPostAddr = memAddress(entityPostBB); int entityPostBaseAddr = 0; - - int entityLayerCountSize = entityRenderDataList.getTotalEntityCount() * Integer.BYTES; - ByteBuffer entityLayerCountBB = MemoryUtil.memAlloc(entityLayerCountSize); - long entityLayerCountAddr = memAddress(entityLayerCountBB); int entityLayerCountBaseAddr = 0; - - int geometryTypeSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer geometryTypeBB = MemoryUtil.memAlloc(geometryTypeSize); - long geometryTypeAddr = memAddress(geometryTypeBB); int geometryTypeBaseAddr = 0; - - int geometryGroupNameSize = entityRenderDataList.getTotalLayersCount() * Long.BYTES; - ByteBuffer geometryGroupNameBB = MemoryUtil.memAlloc(geometryGroupNameSize); - long geometryGroupNameAddr = memAddress(geometryGroupNameBB); int geometryGroupNameBaseAddr = 0; - List geometryGroupNameBuffers = new ArrayList<>( - entityRenderDataList.getTotalLayersCount()); - - int geometryTextureSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer geometryTextureBB = MemoryUtil.memAlloc(geometryTextureSize); - long geometryTextureAddr = memAddress(geometryTextureBB); int geometryTextureBaseAddr = 0; - - int vertexFormatSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer vertexFormatBB = MemoryUtil.memAlloc(vertexFormatSize); - long vertexFormatAddr = memAddress(vertexFormatBB); int vertexFormatBaseAddr = 0; - - int indexFormatSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer indexFormatBB = MemoryUtil.memAlloc(indexFormatSize); - long indexFormatAddr = memAddress(indexFormatBB); int indexFormatBaseAddr = 0; - - int vertexCountSize = entityRenderDataList.getTotalLayersCount() * Integer.BYTES; - ByteBuffer vertexCountBB = MemoryUtil.memAlloc(vertexCountSize); - long vertexCountAddr = memAddress(vertexCountBB); int vertexCountBaseAddr = 0; - - int verticesSize = entityRenderDataList.getTotalLayersCount() * Long.BYTES; - ByteBuffer verticesBB = MemoryUtil.memAlloc(verticesSize); - long verticesAddr = memAddress(verticesBB); int verticesBaseAddr = 0; for (EntityRenderData entityRenderData : entityRenderDataList) { @@ -1030,39 +1510,25 @@ public static void queueBuildWithoutClose(EntityRenderDataList entityRenderDataL RenderLayer renderLayer = entityRenderLayer.renderLayer; BuiltBuffer vertexBuffer = entityRenderLayer.builtBuffer; - Identifier - identifier = - ((RenderLayer.MultiPhase) renderLayer).phases.texture.getId() - .orElse(MissingSprite.getMissingSpriteId()); - int - geometryTypeID = - Constants.GeometryTypes.getGeometryType(renderLayer, entityRenderLayer.reflect) - .getValue(); - int - geometryTextureID = - textureManager.getTexture(identifier) - .getGlId(); - int - vertexFormatID = - Constants.VertexFormats.getValue(vertexBuffer.getDrawParameters() - .format()); - int - indexFormatID = - Constants.DrawModes.getValue(vertexBuffer.getDrawParameters() - .mode()); + int geometryTypeID = Constants.GeometryTypes.getGeometryType(renderLayer, + entityRenderLayer.reflect).getValue(); + int geometryTextureID = TextureTracker.getRenderLayerTextureGlId(renderLayer, + textureManager, MissingSprite.getMissingSpriteId()); + int vertexFormatID = Constants.VertexFormats.getValue( + vertexBuffer.getDrawParameters().format()); + int indexFormatID = Constants.DrawModes.getValue( + vertexBuffer.getDrawParameters().mode()); BufferProxy.BufferInfo vertexBufferInfo = BufferProxy.getBufferInfo( vertexBuffer.getBuffer()); - assert vertexBuffer.getDrawParameters() - .indexCount() == vertexBuffer.getDrawParameters() - .vertexCount() / 4 * 6; + assert vertexBuffer.getDrawParameters().indexCount() + == vertexBuffer.getDrawParameters().vertexCount() / 4 * 6; geometryTypeBB.putInt(geometryTypeBaseAddr, geometryTypeID); geometryTypeBaseAddr += Integer.BYTES; - ByteBuffer geometryGroupNameBuffer = MemoryUtil.memUTF8(renderLayer.name, true); - geometryGroupNameBuffers.add(geometryGroupNameBuffer); - geometryGroupNameBB.putLong(geometryGroupNameBaseAddr, memAddress(geometryGroupNameBuffer)); + geometryGroupNameBB.putLong(geometryGroupNameBaseAddr, memAddress( + cachedGeometryGroupName(renderLayer.name))); geometryGroupNameBaseAddr += Long.BYTES; geometryTextureBB.putInt(geometryTextureBaseAddr, geometryTextureID); @@ -1075,8 +1541,7 @@ public static void queueBuildWithoutClose(EntityRenderDataList entityRenderDataL indexFormatBaseAddr += Integer.BYTES; vertexCountBB.putInt(vertexCountBaseAddr, - vertexBuffer.getDrawParameters() - .vertexCount()); + vertexBuffer.getDrawParameters().vertexCount()); vertexCountBaseAddr += Integer.BYTES; verticesBB.putLong(verticesBaseAddr, vertexBufferInfo.addr()); @@ -1084,44 +1549,65 @@ public static void queueBuildWithoutClose(EntityRenderDataList entityRenderDataL } } - queueBuild(lineWidth, - coordinate.getValue(), - normalOffset, - entityRenderDataList.getTotalEntityCount(), - entityHashCodeAddr, - entityPosXAddr, - entityPosYAddr, - entityPosZAddr, - entityRTFlagAddr, - entityPrebuiltBLASAddr, - entityPostAddr, - entityLayerCountAddr, - geometryTypeAddr, - geometryGroupNameAddr, - geometryTextureAddr, - vertexFormatAddr, - indexFormatAddr, - vertexCountAddr, - verticesAddr); - - // free - MemoryUtil.memFree(entityPosXBB); - MemoryUtil.memFree(entityPosYBB); - MemoryUtil.memFree(entityPosZBB); - MemoryUtil.memFree(entityRTFlagBB); - MemoryUtil.memFree(entityPrebuiltBLASBB); - MemoryUtil.memFree(entityPostBB); - MemoryUtil.memFree(entityLayerCountBB); - MemoryUtil.memFree(geometryTypeBB); - MemoryUtil.memFree(geometryGroupNameBB); - for (ByteBuffer geometryGroupNameBuffer : geometryGroupNameBuffers) { - MemoryUtil.memFree(geometryGroupNameBuffer); - } - MemoryUtil.memFree(geometryTextureBB); - MemoryUtil.memFree(vertexFormatBB); - MemoryUtil.memFree(indexFormatBB); - MemoryUtil.memFree(vertexCountBB); - MemoryUtil.memFree(verticesBB); + return new BuildSubmissionData(entityCount, + memAddress(entityHashCodeBB), + memAddress(entityPosXBB), + memAddress(entityPosYBB), + memAddress(entityPosZBB), + memAddress(entityRTFlagBB), + memAddress(entityPrebuiltBLASBB), + memAddress(entityPostBB), + memAddress(entityLayerCountBB), + memAddress(geometryTypeBB), + memAddress(geometryGroupNameBB), + memAddress(geometryTextureBB), + memAddress(vertexFormatBB), + memAddress(indexFormatBB), + memAddress(vertexCountBB), + memAddress(verticesBB)); + } + + private static ByteBuffer cachedGeometryGroupName(String groupName) { + return GEOMETRY_GROUP_NAME_CACHE.computeIfAbsent(groupName, + key -> MemoryUtil.memUTF8(key, true)); + } + + private record BuildSubmissionData(int entityCount, long entityHashCodes, long entityPosXs, + long entityPosYs, long entityPosZs, long entityRTFlags, + long entityPrebuiltBLASs, long entityPosts, + long entityLayerCounts, long geometryTypes, + long geometryGroupNames, long geometryTextures, + long vertexFormats, long indexFormats, long vertexCounts, + long vertices) { + + } + + private static final class BuildScratch { + + private final Map buffers = new HashMap<>(); + + private ByteBuffer acquire(String key, int requiredBytes) { + int minCapacity = Math.max(1, requiredBytes); + ByteBuffer buffer = buffers.get(key); + if (buffer == null || buffer.capacity() < minCapacity) { + if (buffer != null) { + MemoryUtil.memFree(buffer); + } + buffer = MemoryUtil.memAlloc(nextCapacity(minCapacity)); + buffers.put(key, buffer); + } + buffer.clear(); + buffer.limit(requiredBytes); + return buffer; + } + + private int nextCapacity(int requiredBytes) { + int capacity = 256; + while (capacity < requiredBytes && capacity > 0) { + capacity <<= 1; + } + return capacity > 0 ? capacity : requiredBytes; + } } private static native void queueBuild(float lineWidth, @@ -1232,6 +1718,19 @@ public boolean add(EntityRenderData entityRenderData) { return super.add(entityRenderData); } + @Override + public boolean addAll(java.util.Collection entityRenderDataCollection) { + int addedLayersCount = 0; + for (EntityRenderData entityRenderData : entityRenderDataCollection) { + addedLayersCount += entityRenderData.size(); + } + boolean changed = super.addAll(entityRenderDataCollection); + if (changed) { + totalLayersCount += addedLayersCount; + } + return changed; + } + public int getTotalLayersCount() { return totalLayersCount; } @@ -1240,4 +1739,238 @@ public int getTotalEntityCount() { return this.size(); } } + + private interface PositionedReplayState { + + double x(); + + double y(); + + double z(); + } + + private record EntityReplayState(double x, double y, double z, float tickDelta, + int rtFlag) implements PositionedReplayState { + + } + + private record BlockEntityReplayState(double x, double y, double z, + int signature) implements PositionedReplayState { + + } + + private static final class ParticleVertexConsumerProvider implements VertexConsumerProvider { + + private final StorageVertexConsumerProvider delegate; + private final float emissionStrength; + private final Map wrappedConsumers = new HashMap<>(); + + private ParticleVertexConsumerProvider(StorageVertexConsumerProvider delegate, + Particle particle) { + this.delegate = delegate; + this.emissionStrength = particleEmissionStrength(particle); + } + + @Override + public VertexConsumer getBuffer(RenderLayer renderLayer) { + VertexConsumer vertexConsumer = delegate.getBuffer(renderLayer); + if (!(vertexConsumer instanceof PBRVertexConsumer pbrVertexConsumer)) { + return vertexConsumer; + } + if (emissionStrength <= 0.0f) { + pbrVertexConsumer.materialHints(0); + return pbrVertexConsumer; + } + pbrVertexConsumer.materialHints(PBRVertexConsumer.MATERIAL_HINT_FORCE_NO_PBR); + return wrappedConsumers.computeIfAbsent(renderLayer, + unused -> new ParticleGlowVertexConsumer(pbrVertexConsumer, renderLayer, + emissionStrength)); + } + } + + private static final class ParticleGlowVertexConsumer implements VertexConsumer { + + private final PBRVertexConsumer delegate; + private final float emissionStrength; + private final float layerEmissionMultiplier; + private int red = 255; + private int green = 255; + private int blue = 255; + private int alpha = 255; + private int lightU = 240; + private int lightV = 240; + + private ParticleGlowVertexConsumer(PBRVertexConsumer delegate, RenderLayer renderLayer, + float emissionStrength) { + this.delegate = delegate; + this.emissionStrength = emissionStrength; + String layerName = renderLayer.name.toLowerCase(Locale.ROOT); + if (layerName.contains("lit")) { + this.layerEmissionMultiplier = 1.35f; + } else if (layerName.contains("particle")) { + this.layerEmissionMultiplier = 1.0f; + } else { + this.layerEmissionMultiplier = 0.8f; + } + } + + @Override + public VertexConsumer vertex(float x, float y, float z) { + red = 255; + green = 255; + blue = 255; + alpha = 255; + lightU = 240; + lightV = 240; + delegate.vertex(x, y, z); + applyEmission(); + return this; + } + + @Override + public VertexConsumer color(int red, int green, int blue, int alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + delegate.color(red, green, blue, alpha); + applyEmission(); + return this; + } + + @Override + public VertexConsumer texture(float u, float v) { + delegate.texture(u, v); + return this; + } + + @Override + public VertexConsumer overlay(int u, int v) { + delegate.overlay(u, v); + return this; + } + + @Override + public VertexConsumer light(int u, int v) { + this.lightU = u; + this.lightV = v; + delegate.light(u, v); + applyEmission(); + return this; + } + + @Override + public VertexConsumer normal(float x, float y, float z) { + delegate.normal(x, y, z); + return this; + } + + private void applyEmission() { + float alphaFactor = alpha / 255.0f; + float maxChannel = Math.max(red, Math.max(green, blue)) / 255.0f; + float lightFactor = MathHelper.clamp(Math.max(lightU, lightV) / 240.0f, 0.0f, 1.0f); + float emission = emissionStrength * layerEmissionMultiplier * alphaFactor + * Math.max(0.2f, maxChannel) * (0.45f + 0.55f * lightFactor); + delegate.albedoEmission(emission); + } + } + + private record ParticleReplayState(int totalParticleCount, int customParticleCount, + long signature) { + + } + + private record BlockEntityRenderEntry(BlockEntity blockEntity, int crumblingStage, + int mainRenderId, int crumblingRenderId) { + + } + + private record BlockEntityBuildBatch( + List entityStorageVertexConsumerProviders, + EntityRenderDataList entityRenderDataList, + List crumblingStorageVertexConsumerProviders, + EntityRenderDataList crumblingRenderDataList) { + + } + + public record BlockEntityQueueResult( + List freshCrumblingStorageVertexConsumerProviders, + EntityRenderDataList freshCrumblingRenderDataList, + EntityRenderDataList replayedCrumblingRenderDataList) { + + public boolean isEmpty() { + return freshCrumblingRenderDataList.isEmpty() && replayedCrumblingRenderDataList.isEmpty(); + } + } + + private record ParticleRenderEntry(Particle particle, ParticleTextureSheet textureSheet, + boolean custom) { + + } + + private static final class EntityReplayCache { + + private final EntityRenderDataList entityRenderDataList; + private final Map entityStates; + private final List ownedBuffers; + + private EntityReplayCache(EntityRenderDataList entityRenderDataList, + Map entityStates, + List ownedBuffers) { + this.entityRenderDataList = entityRenderDataList; + this.entityStates = entityStates; + this.ownedBuffers = ownedBuffers; + } + + private void close() { + for (BuiltBuffer ownedBuffer : ownedBuffers) { + ownedBuffer.close(); + } + } + } + + private static final class BlockEntityReplayCache { + + private final EntityRenderDataList entityRenderDataList; + private final EntityRenderDataList crumblingRenderDataList; + private final Map renderStates; + private final List ownedBuffers; + + private BlockEntityReplayCache(EntityRenderDataList entityRenderDataList, + EntityRenderDataList crumblingRenderDataList, + Map renderStates, + List ownedBuffers) { + this.entityRenderDataList = entityRenderDataList; + this.crumblingRenderDataList = crumblingRenderDataList; + this.renderStates = renderStates; + this.ownedBuffers = ownedBuffers; + } + + private void close() { + for (BuiltBuffer ownedBuffer : ownedBuffers) { + ownedBuffer.close(); + } + } + } + + private static final class ParticleReplayCache { + + private final EntityRenderDataList entityRenderDataList; + private final ParticleReplayState particleReplayState; + private final List ownedBuffers; + + private ParticleReplayCache(EntityRenderDataList entityRenderDataList, + ParticleReplayState particleReplayState, + List ownedBuffers) { + this.entityRenderDataList = entityRenderDataList; + this.particleReplayState = particleReplayState; + this.ownedBuffers = ownedBuffers; + } + + private void close() { + for (BuiltBuffer ownedBuffer : ownedBuffers) { + ownedBuffer.close(); + } + } + } } diff --git a/src/main/java/com/radiance/client/proxy/world/RayTracingTuning.java b/src/main/java/com/radiance/client/proxy/world/RayTracingTuning.java new file mode 100644 index 0000000..5b6a575 --- /dev/null +++ b/src/main/java/com/radiance/client/proxy/world/RayTracingTuning.java @@ -0,0 +1,630 @@ +package com.radiance.client.proxy.world; + +import com.radiance.client.pipeline.Pipeline; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.RenderPhase; + +final class RayTracingTuning { + + private static final String RAY_TRACING_MODULE = "render_pipeline.module.ray_tracing.name"; + private static final String ATTR_WORLD_REPRESENTATION_MODE = + "render_pipeline.module.ray_tracing.attribute.world_representation_mode"; + private static final String ATTR_CHUNK_TRAVERSAL_MODE = + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode"; + private static final String ATTR_CHUNK_DATA_LAYOUT = + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout"; + private static final String ATTR_CHUNK_MACROCELL_SIZE = + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size"; + private static final String ATTR_TERRAIN_MESHING_MODE = + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode"; + private static final String ATTR_GREEDY_MERGE_MAX_SPAN = + "render_pipeline.module.ray_tracing.attribute.greedy_merge_max_span"; + private static final String ATTR_BLAS_INCLUSION_MODE = + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode"; + private static final String ATTR_GLASS_PATH_MODE = + "render_pipeline.module.ray_tracing.attribute.glass_path_mode"; + private static final String ATTR_FOLIAGE_PATH_MODE = + "render_pipeline.module.ray_tracing.attribute.foliage_path_mode"; + private static final String ATTR_DECORATION_PATH_MODE = + "render_pipeline.module.ray_tracing.attribute.decoration_path_mode"; + private static final String ATTR_FAR_FIELD_GEOMETRY_MODE = + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode"; + private static final String ATTR_FAR_FIELD_START_DISTANCE_CHUNKS = + "render_pipeline.module.ray_tracing.attribute.far_field_start_distance_chunks"; + private static final String ATTR_REFLECTION_RAY_MATERIAL_MODE = + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode"; + private static final String ATTR_DIFFUSE_GI_MODE = + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode"; + private static final String ATTR_TERRAIN_UPDATE_INTERVAL_FRAMES = + "render_pipeline.module.ray_tracing.attribute.terrain_update_interval_frames"; + private static final String ATTR_ENTITY_UPDATE_INTERVAL_FRAMES = + "render_pipeline.module.ray_tracing.attribute.entity_update_interval_frames"; + private static final String ATTR_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES = + "render_pipeline.module.ray_tracing.attribute.block_entity_update_interval_frames"; + private static final String ATTR_PARTICLE_UPDATE_INTERVAL_FRAMES = + "render_pipeline.module.ray_tracing.attribute.particle_update_interval_frames"; + private static final String ATTR_PARTICLE_CRIT_GLOW = + "render_pipeline.module.ray_tracing.attribute.particle_crit_glow"; + private static final String ATTR_PARTICLE_DEATH_SMOKE_GLOW = + "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow"; + private static final String ATTR_PARTICLE_CRIT_GLOW_STRENGTH = + "render_pipeline.module.ray_tracing.attribute.particle_crit_glow_strength"; + private static final String ATTR_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH = + "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow_strength"; + private static final String ATTR_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES = + "render_pipeline.module.ray_tracing.attribute.separate_entity_terrain_accel_structures"; + + private static final String VALUE_WORLD_REPRESENTATION_CHUNK_AABB = + "render_pipeline.module.ray_tracing.attribute.world_representation_mode.chunk_aabb"; + private static final String VALUE_TRAVERSAL_TRIANGLE_HIT = + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.triangle_hit"; + private static final String VALUE_CHUNK_LAYOUT_TRIANGLE_GEOMETRY = + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.triangle_geometry"; + private static final String VALUE_CHUNK_MACROCELL_DISABLED = + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.disabled"; + private static final String VALUE_TERRAIN_MESHING_LEGACY = + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.legacy_quads"; + private static final String VALUE_TERRAIN_MESHING_GREEDY = + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.greedy_meshing"; + private static final String VALUE_TERRAIN_MESHING_COPLANAR = + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge"; + private static final double FAR_FIELD_VISIBLE_FEATURE_PIXELS = 1.25; + private static final double FAR_FIELD_CUTOUT_FEATURE_BLOCKS = 2.0; + private static final double FAR_FIELD_DEFAULT_FEATURE_BLOCKS = 1.0; + private static final double FAR_FIELD_HALF_FOV_DEGREES = 35.0; + private static final double FAR_FIELD_INTERVAL_RATIO_MEDIUM = 1.5; + private static final double FAR_FIELD_INTERVAL_RATIO_FAR = 2.5; + private static final double FAR_FIELD_INTERVAL_RATIO_VERY_FAR = 4.0; + private static final double FAR_FIELD_INTERVAL_RATIO_EXTREME = 6.0; + + private RayTracingTuning() { + } + + static ChunkGeometryRoute classifyChunkLayer(RenderLayer renderLayer, double distanceChunks) { + if (renderLayer == null) { + return ChunkGeometryRoute.DROP; + } + + boolean opaqueLayer = isOpaqueLayer(renderLayer); + boolean farField = isFarField(distanceChunks); + boolean coarseWorldRepresentation = + getWorldRepresentationMode() == WorldRepresentationMode.CHUNK_AABB; + + BlasInclusionMode blasInclusionMode = getBlasInclusionMode(); + if (opaqueLayer) { + return ChunkGeometryRoute.BLAS; + } + + GeometryPathMode geometryPathMode = getPathMode(renderLayer); + if (geometryPathMode == GeometryPathMode.EXCLUDE) { + return ChunkGeometryRoute.DROP; + } + if (geometryPathMode == GeometryPathMode.SPECIAL_PATH + && shouldForceBlasForTranslucentTerrain(renderLayer)) { + return ChunkGeometryRoute.BLAS; + } + if (geometryPathMode == GeometryPathMode.SPECIAL_PATH + && !separatesEntityAndTerrainAccelerationStructures()) { + return ChunkGeometryRoute.BLAS; + } + + if (blasInclusionMode == BlasInclusionMode.ALL_GEOMETRY + || geometryPathMode == GeometryPathMode.BLAS) { + return ChunkGeometryRoute.BLAS; + } + + boolean preserveVisibleFarFieldSpecialGeometry = + farField && shouldPreserveVisibleFarFieldSpecialGeometry(renderLayer, distanceChunks); + if (farField && shouldDropFarFieldSpecialGeometry()) { + return preserveVisibleFarFieldSpecialGeometry ? ChunkGeometryRoute.BLAS + : ChunkGeometryRoute.DROP; + } + if (coarseWorldRepresentation && farField && !isReflectiveSpecialLayer(renderLayer)) { + return preserveVisibleFarFieldSpecialGeometry ? ChunkGeometryRoute.BLAS + : ChunkGeometryRoute.DROP; + } + + return isReflectiveSpecialLayer(renderLayer) ? ChunkGeometryRoute.SPECIAL_REFLECTIVE + : ChunkGeometryRoute.SPECIAL_MATTE; + } + + static boolean shouldReflectBlasLayer(RenderLayer renderLayer) { + return shouldReflectLayer(renderLayer, true); + } + + static boolean shouldReflectLayer(RenderLayer renderLayer, boolean defaultReflect) { + if (!defaultReflect || renderLayer == null) { + return false; + } + + DiffuseGiMode diffuseGiMode = getDiffuseGiMode(); + if (diffuseGiMode != DiffuseGiMode.FULL_RAY_TRACING) { + return isReflectiveSpecialLayer(renderLayer); + } + + ReflectionRayMaterialMode reflectionRayMaterialMode = getReflectionRayMaterialMode(); + if (reflectionRayMaterialMode == ReflectionRayMaterialMode.ALL_MATERIALS) { + return true; + } + + if (reflectionRayMaterialMode == ReflectionRayMaterialMode.REFLECTIVE_ONLY + || reflectionRayMaterialMode == ReflectionRayMaterialMode.WATER_GLASS_METAL) { + return isReflectiveSpecialLayer(renderLayer); + } + + return true; + } + + static boolean shouldCaptureTraversalMetadata() { + return getWorldRepresentationMode() == WorldRepresentationMode.CHUNK_AABB + || getChunkTraversalMode() != ChunkTraversalMode.TRIANGLE_HIT + || getChunkDataLayout() != ChunkDataLayout.TRIANGLE_GEOMETRY + || getChunkMacrocellSize() != ChunkMacrocellSize.DISABLED + || getTerrainMeshingMode() != TerrainMeshingMode.LEGACY_QUADS; + } + + static boolean shouldAttemptTerrainMeshingOptimization() { + return getEffectiveTerrainMeshingMode() != TerrainMeshingMode.LEGACY_QUADS; + } + + static TerrainMeshingMode getTerrainMeshingMode() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, + ATTR_TERRAIN_MESHING_MODE, ""); + if (VALUE_TERRAIN_MESHING_COPLANAR.equals(value) || value.endsWith(".coplanar_merge")) { + return TerrainMeshingMode.COPLANAR_MERGE; + } + if (VALUE_TERRAIN_MESHING_GREEDY.equals(value) || value.endsWith(".greedy_meshing")) { + return TerrainMeshingMode.GREEDY_MESHING; + } + return TerrainMeshingMode.LEGACY_QUADS; + } + + static TerrainMeshingMode getEffectiveTerrainMeshingMode() { + TerrainMeshingMode terrainMeshingMode = getTerrainMeshingMode(); + if (terrainMeshingMode != TerrainMeshingMode.LEGACY_QUADS) { + return terrainMeshingMode; + } + if (getWorldRepresentationMode() == WorldRepresentationMode.CHUNK_AABB + || getChunkTraversalMode() == ChunkTraversalMode.MACROCELL) { + return TerrainMeshingMode.COPLANAR_MERGE; + } + if (getChunkTraversalMode() == ChunkTraversalMode.BRICK + || getChunkDataLayout() != ChunkDataLayout.TRIANGLE_GEOMETRY) { + return TerrainMeshingMode.GREEDY_MESHING; + } + return TerrainMeshingMode.LEGACY_QUADS; + } + + static int terrainGreedyMergeMaxSpan() { + return Math.max(1, + Pipeline.getModuleAttributeIntValue(RAY_TRACING_MODULE, ATTR_GREEDY_MERGE_MAX_SPAN, + 16)); + } + + static int terrainUpdateIntervalFrames(double distanceChunks) { + int interval = Math.max(1, + Pipeline.getModuleAttributeIntValue(RAY_TRACING_MODULE, + ATTR_TERRAIN_UPDATE_INTERVAL_FRAMES, 1)); + + int farFieldStartDistanceChunks = getFarFieldStartDistanceChunks(); + FarFieldGeometryMode farFieldGeometryMode = getFarFieldGeometryMode(); + if (distanceChunks < farFieldStartDistanceChunks) { + return interval; + } + + int farFieldInterval = switch (farFieldGeometryMode) { + case EXACT_CHUNKS -> interval; + case SIMPLIFIED_SHELL -> interval * 2; + case CLIPMAP -> interval * 4; + case SHELL_AND_CLIPMAP -> interval * 6; + }; + + int coarseMultiplier = 1; + if (getWorldRepresentationMode() == WorldRepresentationMode.CHUNK_AABB) { + coarseMultiplier *= 2; + } + coarseMultiplier *= switch (getChunkTraversalMode()) { + case TRIANGLE_HIT, VOXEL_DDA -> 1; + case BRICK -> 2; + case MACROCELL -> 3; + }; + coarseMultiplier *= switch (getChunkMacrocellSize()) { + case DISABLED -> 1; + case SIZE_4 -> 2; + case SIZE_8 -> 3; + }; + int distanceMultiplier = farFieldDistanceUpdateMultiplier(distanceChunks, + farFieldStartDistanceChunks); + return Math.max(1, Math.min(1024, + farFieldInterval * coarseMultiplier * distanceMultiplier)); + } + + static int entityUpdateIntervalFrames() { + return Math.max(1, + Pipeline.getModuleAttributeIntValue(RAY_TRACING_MODULE, + ATTR_ENTITY_UPDATE_INTERVAL_FRAMES, 1)); + } + + static int blockEntityUpdateIntervalFrames() { + return Math.max(1, + Pipeline.getModuleAttributeIntValue(RAY_TRACING_MODULE, + ATTR_BLOCK_ENTITY_UPDATE_INTERVAL_FRAMES, entityUpdateIntervalFrames())); + } + + static int particleUpdateIntervalFrames() { + return Math.max(1, + Pipeline.getModuleAttributeIntValue(RAY_TRACING_MODULE, + ATTR_PARTICLE_UPDATE_INTERVAL_FRAMES, entityUpdateIntervalFrames())); + } + + static boolean critParticlesGlowEnabled() { + return Pipeline.getModuleAttributeBooleanValue(RAY_TRACING_MODULE, + ATTR_PARTICLE_CRIT_GLOW, true); + } + + static float critParticlesGlowStrength() { + if (!critParticlesGlowEnabled()) { + return 0.0f; + } + return Math.max(0.0f, + Pipeline.getModuleAttributeFloatValue(RAY_TRACING_MODULE, + ATTR_PARTICLE_CRIT_GLOW_STRENGTH, 0.55f)); + } + + static boolean deathSmokeParticlesGlowEnabled() { + return Pipeline.getModuleAttributeBooleanValue(RAY_TRACING_MODULE, + ATTR_PARTICLE_DEATH_SMOKE_GLOW, true); + } + + static float deathSmokeParticlesGlowStrength() { + if (!deathSmokeParticlesGlowEnabled()) { + return 0.0f; + } + return Math.max(0.0f, + Pipeline.getModuleAttributeFloatValue(RAY_TRACING_MODULE, + ATTR_PARTICLE_DEATH_SMOKE_GLOW_STRENGTH, 0.32f)); + } + + static int getFarFieldStartDistanceChunks() { + return Math.max(2, + Pipeline.getModuleAttributeIntValue(RAY_TRACING_MODULE, + ATTR_FAR_FIELD_START_DISTANCE_CHUNKS, 32)); + } + + static boolean separatesEntityAndTerrainAccelerationStructures() { + return Pipeline.getModuleAttributeBooleanValue(RAY_TRACING_MODULE, + ATTR_SEPARATE_ENTITY_TERRAIN_ACCEL_STRUCTURES, true); + } + + static boolean useLowCostDiffuseGi() { + return getDiffuseGiMode() != DiffuseGiMode.FULL_RAY_TRACING; + } + + static WorldRepresentationMode getWorldRepresentationMode() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, + ATTR_WORLD_REPRESENTATION_MODE, ""); + if (VALUE_WORLD_REPRESENTATION_CHUNK_AABB.equals(value) || value.endsWith( + ".chunk_aabb")) { + return WorldRepresentationMode.CHUNK_AABB; + } + return WorldRepresentationMode.TRIANGLE_BLAS; + } + + static ChunkTraversalMode getChunkTraversalMode() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, + ATTR_CHUNK_TRAVERSAL_MODE, ""); + if (value.endsWith(".voxel_dda")) { + return ChunkTraversalMode.VOXEL_DDA; + } + if (value.endsWith(".brick")) { + return ChunkTraversalMode.BRICK; + } + if (value.endsWith(".macrocell")) { + return ChunkTraversalMode.MACROCELL; + } + return ChunkTraversalMode.TRIANGLE_HIT; + } + + static ChunkDataLayout getChunkDataLayout() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, ATTR_CHUNK_DATA_LAYOUT, + ""); + if (value.endsWith(".occupancy_bitmask")) { + return ChunkDataLayout.OCCUPANCY_BITMASK; + } + if (value.endsWith(".occupancy_palette")) { + return ChunkDataLayout.OCCUPANCY_PALETTE; + } + if (value.endsWith(".occupancy_palette_face_mask")) { + return ChunkDataLayout.OCCUPANCY_PALETTE_FACE_MASK; + } + return ChunkDataLayout.TRIANGLE_GEOMETRY; + } + + static ChunkMacrocellSize getChunkMacrocellSize() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, + ATTR_CHUNK_MACROCELL_SIZE, ""); + if (value.endsWith(".size_4")) { + return ChunkMacrocellSize.SIZE_4; + } + if (value.endsWith(".size_8")) { + return ChunkMacrocellSize.SIZE_8; + } + return ChunkMacrocellSize.DISABLED; + } + + static int effectiveMacrocellSize() { + return switch (getChunkMacrocellSize()) { + case SIZE_4 -> 4; + case SIZE_8 -> 8; + case DISABLED -> switch (getChunkTraversalMode()) { + case MACROCELL -> 8; + case BRICK -> 4; + default -> 0; + }; + }; + } + + static boolean shouldCaptureMaterialPalette() { + return switch (getChunkDataLayout()) { + case OCCUPANCY_PALETTE, OCCUPANCY_PALETTE_FACE_MASK -> true; + default -> false; + }; + } + + static boolean shouldCaptureFaceMask() { + if (getChunkDataLayout() == ChunkDataLayout.OCCUPANCY_PALETTE_FACE_MASK) { + return true; + } + return switch (getChunkTraversalMode()) { + case VOXEL_DDA, BRICK, MACROCELL -> true; + default -> false; + }; + } + + static boolean shouldCaptureMacrocell4() { + return effectiveMacrocellSize() == 4; + } + + static boolean shouldCaptureMacrocell8() { + return effectiveMacrocellSize() == 8; + } + + static DiffuseGiMode getDiffuseGiMode() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, ATTR_DIFFUSE_GI_MODE, + ""); + if (value.endsWith(".probes")) { + return DiffuseGiMode.PROBES; + } + if (value.endsWith(".radiance_cache")) { + return DiffuseGiMode.RADIANCE_CACHE; + } + if (value.endsWith(".low_cost_hybrid")) { + return DiffuseGiMode.LOW_COST_HYBRID; + } + return DiffuseGiMode.FULL_RAY_TRACING; + } + + static boolean isFarField(double distanceChunks) { + return distanceChunks >= getFarFieldStartDistanceChunks(); + } + + static boolean isOpaqueLayer(RenderLayer renderLayer) { + if (!(renderLayer instanceof RenderLayer.MultiPhase multiPhase)) { + return renderLayer.name.contains("solid"); + } + + if (renderLayer.name.contains("solid")) { + return true; + } + + if (isCutoutLayer(renderLayer) || isTranslucentLayer(renderLayer)) { + return false; + } + + return RenderPhase.NO_TRANSPARENCY.equals(multiPhase.phases.transparency); + } + + static boolean isTranslucentLayer(RenderLayer renderLayer) { + if (!(renderLayer instanceof RenderLayer.MultiPhase multiPhase)) { + return renderLayer.name.contains("translucent") || renderLayer.name.contains("water"); + } + + if (renderLayer.name.contains("translucent") || renderLayer.name.contains("water")) { + return true; + } + + return multiPhase.isTranslucent() && !RenderPhase.NO_TRANSPARENCY.equals( + multiPhase.phases.transparency); + } + + static boolean isCutoutLayer(RenderLayer renderLayer) { + String name = renderLayer.name; + return name.contains("cutout") || name.contains("tripwire"); + } + + private static boolean shouldPreserveVisibleFarFieldSpecialGeometry(RenderLayer renderLayer, + double distanceChunks) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client == null || client.getWindow() == null) { + return true; + } + + double framebufferHeight = Math.max(1.0, client.getWindow().getFramebufferHeight()); + double distanceBlocks = Math.max(1.0, distanceChunks * 16.0); + double featureBlocks = isCutoutLayer(renderLayer) ? FAR_FIELD_CUTOUT_FEATURE_BLOCKS + : FAR_FIELD_DEFAULT_FEATURE_BLOCKS; + double projectedPixels = + framebufferHeight * featureBlocks / (2.0 * distanceBlocks * Math.tan( + Math.toRadians(FAR_FIELD_HALF_FOV_DEGREES))); + return projectedPixels >= FAR_FIELD_VISIBLE_FEATURE_PIXELS; + } + + private static int farFieldDistanceUpdateMultiplier(double distanceChunks, + int farFieldStartDistanceChunks) { + double distanceRatio = distanceChunks / Math.max(1.0, farFieldStartDistanceChunks); + if (distanceRatio >= FAR_FIELD_INTERVAL_RATIO_EXTREME) { + return 12; + } + if (distanceRatio >= FAR_FIELD_INTERVAL_RATIO_VERY_FAR) { + return 8; + } + if (distanceRatio >= FAR_FIELD_INTERVAL_RATIO_FAR) { + return 4; + } + if (distanceRatio >= FAR_FIELD_INTERVAL_RATIO_MEDIUM) { + return 2; + } + return 1; + } + + private static boolean shouldDropFarFieldSpecialGeometry() { + FarFieldGeometryMode farFieldGeometryMode = getFarFieldGeometryMode(); + return farFieldGeometryMode == FarFieldGeometryMode.SIMPLIFIED_SHELL + || farFieldGeometryMode == FarFieldGeometryMode.CLIPMAP + || farFieldGeometryMode == FarFieldGeometryMode.SHELL_AND_CLIPMAP; + } + + private static boolean isReflectiveSpecialLayer(RenderLayer renderLayer) { + return isTranslucentLayer(renderLayer) || renderLayer.name.contains("water") + || renderLayer.name.contains("ice"); + } + + private static boolean shouldForceBlasForTranslucentTerrain(RenderLayer renderLayer) { + String name = renderLayer.name; + if (name.contains("water_mask") + || name.contains("end_portal") + || name.contains("end_gateway") + || name.contains("cloud")) { + return false; + } + return isTranslucentLayer(renderLayer) || name.contains("ice"); + } + + private static GeometryPathMode getPathMode(RenderLayer renderLayer) { + String attributeName = ATTR_DECORATION_PATH_MODE; + if (isTranslucentLayer(renderLayer)) { + attributeName = ATTR_GLASS_PATH_MODE; + } else if (isCutoutLayer(renderLayer)) { + attributeName = ATTR_FOLIAGE_PATH_MODE; + } + + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, attributeName, ""); + if (value.endsWith(".exclude")) { + return GeometryPathMode.EXCLUDE; + } + if (value.endsWith(".special_path")) { + return GeometryPathMode.SPECIAL_PATH; + } + return GeometryPathMode.BLAS; + } + + private static BlasInclusionMode getBlasInclusionMode() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, ATTR_BLAS_INCLUSION_MODE, + ""); + if (value.endsWith(".opaque_and_shadow")) { + return BlasInclusionMode.OPAQUE_AND_SHADOW; + } + if (value.endsWith(".opaque_only")) { + return BlasInclusionMode.OPAQUE_ONLY; + } + return BlasInclusionMode.ALL_GEOMETRY; + } + + private static ReflectionRayMaterialMode getReflectionRayMaterialMode() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, + ATTR_REFLECTION_RAY_MATERIAL_MODE, ""); + if (value.endsWith(".reflective_only")) { + return ReflectionRayMaterialMode.REFLECTIVE_ONLY; + } + if (value.endsWith(".water_glass_metal")) { + return ReflectionRayMaterialMode.WATER_GLASS_METAL; + } + return ReflectionRayMaterialMode.ALL_MATERIALS; + } + + private static FarFieldGeometryMode getFarFieldGeometryMode() { + String value = Pipeline.getModuleAttributeValue(RAY_TRACING_MODULE, + ATTR_FAR_FIELD_GEOMETRY_MODE, ""); + if (value.endsWith(".simplified_shell")) { + return FarFieldGeometryMode.SIMPLIFIED_SHELL; + } + if (value.endsWith(".clipmap")) { + return FarFieldGeometryMode.CLIPMAP; + } + if (value.endsWith(".shell_and_clipmap")) { + return FarFieldGeometryMode.SHELL_AND_CLIPMAP; + } + return FarFieldGeometryMode.EXACT_CHUNKS; + } + + enum ChunkGeometryRoute { + BLAS, + SPECIAL_REFLECTIVE, + SPECIAL_MATTE, + DROP + } + + private enum BlasInclusionMode { + ALL_GEOMETRY, + OPAQUE_AND_SHADOW, + OPAQUE_ONLY + } + + private enum FarFieldGeometryMode { + EXACT_CHUNKS, + SIMPLIFIED_SHELL, + CLIPMAP, + SHELL_AND_CLIPMAP + } + + private enum ReflectionRayMaterialMode { + ALL_MATERIALS, + REFLECTIVE_ONLY, + WATER_GLASS_METAL + } + + private enum GeometryPathMode { + BLAS, + SPECIAL_PATH, + EXCLUDE + } + + enum WorldRepresentationMode { + TRIANGLE_BLAS, + CHUNK_AABB + } + + enum ChunkTraversalMode { + TRIANGLE_HIT, + VOXEL_DDA, + BRICK, + MACROCELL + } + + enum ChunkDataLayout { + TRIANGLE_GEOMETRY, + OCCUPANCY_BITMASK, + OCCUPANCY_PALETTE, + OCCUPANCY_PALETTE_FACE_MASK + } + + enum ChunkMacrocellSize { + DISABLED, + SIZE_4, + SIZE_8 + } + + enum DiffuseGiMode { + FULL_RAY_TRACING, + PROBES, + RADIANCE_CACHE, + LOW_COST_HYBRID + } + + enum TerrainMeshingMode { + LEGACY_QUADS, + GREEDY_MESHING, + COPLANAR_MERGE + } +} diff --git a/src/main/java/com/radiance/client/texture/AuxiliaryTextures.java b/src/main/java/com/radiance/client/texture/AuxiliaryTextures.java index ef1a3ce..c6fa88b 100644 --- a/src/main/java/com/radiance/client/texture/AuxiliaryTextures.java +++ b/src/main/java/com/radiance/client/texture/AuxiliaryTextures.java @@ -1,7 +1,8 @@ package com.radiance.client.texture; -import com.mojang.blaze3d.platform.TextureUtil; +import com.radiance.client.constant.VulkanConstants; import com.radiance.client.proxy.vulkan.TextureProxy; +import com.radiance.client.util.MaterialToolkit; import com.radiance.mixin_related.extensions.vanilla_resource_tracker.INativeImageExt; import java.io.IOException; import java.util.Arrays; @@ -108,14 +109,18 @@ public static void loadAndUpload(NativeImage source, INativeImageExt sourceExt, // ensure the texture exists TextureTracker.Texture texture = TextureTracker.GLID2Texture.get(targetId); + VulkanConstants.VkFormat auxiliaryFormat = texture.format().toUnorm(); if (!auxiliaryTexture.GLIDMapping.containsKey(targetId)) { auxiliaryTargetId = TextureProxy.generateTextureId(); // System.out.println( // "generate " + auxiliaryTexture.name + " texture for " + targetId + ": " // + auxiliaryTargetId); - TextureUtil.prepareImage(texture.format().getNativeImageInternalFormat(), - auxiliaryTargetId, texture.maxLayer(), texture.width(), texture.height()); + TextureProxy.prepareImage(auxiliaryTargetId, texture.maxLayer() + 1, + texture.width(), texture.height(), auxiliaryFormat); + TextureTracker.GLID2Texture.put(auxiliaryTargetId, + new TextureTracker.Texture(texture.width(), texture.height(), + texture.channel(), auxiliaryFormat, texture.maxLayer())); auxiliaryTexture.GLIDMapping.put(targetId, auxiliaryTargetId); } else { auxiliaryTargetId = auxiliaryTexture.GLIDMapping.get(targetId); @@ -124,10 +129,12 @@ public static void loadAndUpload(NativeImage source, INativeImageExt sourceExt, auxiliaryTargetId); if (texture.width() != auxiliaryTrackerTexture.width() || texture.height() != auxiliaryTrackerTexture.height() - || texture.format() != auxiliaryTrackerTexture.format()) { - TextureUtil.prepareImage(texture.format().getNativeImageInternalFormat(), - auxiliaryTargetId, texture.maxLayer(), texture.width(), - texture.height()); + || auxiliaryTrackerTexture.format() != auxiliaryFormat) { + TextureProxy.prepareImage(auxiliaryTargetId, texture.maxLayer() + 1, + texture.width(), texture.height(), auxiliaryFormat); + TextureTracker.GLID2Texture.put(auxiliaryTargetId, + new TextureTracker.Texture(texture.width(), texture.height(), + texture.channel(), auxiliaryFormat, texture.maxLayer())); } } @@ -157,7 +164,9 @@ public static void loadAndUpload(NativeImage source, INativeImageExt sourceExt, } if (!success) { - auxiliaryTemplateImage = source.applyToCopy(i -> 0); + auxiliaryTemplateImage = MaterialToolkit.isAutoPbrEnabled() + ? AutoPBRGenerator.generate(auxiliaryTexture, identifier, source) + : source.applyToCopy(i -> 0); } } diff --git a/src/main/java/com/radiance/client/texture/PbrAtlasTextureFilter.java b/src/main/java/com/radiance/client/texture/PbrAtlasTextureFilter.java new file mode 100644 index 0000000..bb385c9 --- /dev/null +++ b/src/main/java/com/radiance/client/texture/PbrAtlasTextureFilter.java @@ -0,0 +1,20 @@ +package com.radiance.client.texture; + +import net.minecraft.util.Identifier; + +public final class PbrAtlasTextureFilter { + + private PbrAtlasTextureFilter() { + } + + public static boolean shouldSkipAtlasEntry(Identifier identifier) { + String path = identifier.getPath(); + if (!(path.endsWith("_s") || path.endsWith("_n") || path.endsWith("_f"))) { + return false; + } + + return path.startsWith("block/") + || path.startsWith("item/") + || path.startsWith("entity/"); + } +} diff --git a/src/main/java/com/radiance/client/texture/TextureTracker.java b/src/main/java/com/radiance/client/texture/TextureTracker.java index 424057a..b705c88 100644 --- a/src/main/java/com/radiance/client/texture/TextureTracker.java +++ b/src/main/java/com/radiance/client/texture/TextureTracker.java @@ -1,18 +1,106 @@ package com.radiance.client.texture; import com.radiance.client.constant.VulkanConstants; -import java.util.Map; +import com.radiance.client.proxy.vulkan.TextureProxy; +import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.Map; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.texture.TextureManager; import net.minecraft.client.texture.NativeImage; import net.minecraft.util.Identifier; public class TextureTracker { + public static final int MAX_TEXTURE_SLOTS = 4096; public static Map textureID2GLID = new ConcurrentHashMap<>(); public static Map GLID2Texture = new ConcurrentHashMap<>(); public static Map GLID2SpecularGLID = new ConcurrentHashMap<>(); public static Map GLID2NormalGLID = new ConcurrentHashMap<>(); public static Map GLID2FlagGLID = new ConcurrentHashMap<>(); + private static final AtomicInteger RENDER_LAYER_TEXTURE_CACHE_EPOCH = new AtomicInteger(); + private static final Map RENDER_LAYER_TEXTURES = + new ConcurrentHashMap<>(); + + private record CachedLayerTexture(int glId, int epoch) { + + } + + public static void trackTextureRegistration(Identifier id, int glId) { + textureID2GLID.put(id, glId); + invalidateRenderLayerTextureCache(); + } + + public static void invalidateRenderLayerTextureCache() { + RENDER_LAYER_TEXTURE_CACHE_EPOCH.incrementAndGet(); + RENDER_LAYER_TEXTURES.clear(); + } + + public static void releaseTextureRegistration(int glId) { + releaseTextureRegistration(glId, new HashSet<>()); + } + + private static void releaseTextureRegistration(int glId, HashSet released) { + if (glId < 0 || !released.add(glId)) { + return; + } + + textureID2GLID.entrySet().removeIf(entry -> entry.getValue() != null && entry.getValue() == glId); + + Integer specularId = GLID2SpecularGLID.remove(glId); + Integer normalId = GLID2NormalGLID.remove(glId); + Integer flagId = GLID2FlagGLID.remove(glId); + + GLID2Texture.remove(glId); + + GLID2SpecularGLID.entrySet().removeIf(entry -> entry.getValue() != null && entry.getValue() == glId); + GLID2NormalGLID.entrySet().removeIf(entry -> entry.getValue() != null && entry.getValue() == glId); + GLID2FlagGLID.entrySet().removeIf(entry -> entry.getValue() != null && entry.getValue() == glId); + + invalidateRenderLayerTextureCache(); + + if (specularId != null) { + releaseTextureRegistration(specularId, released); + } + if (normalId != null) { + releaseTextureRegistration(normalId, released); + } + if (flagId != null) { + releaseTextureRegistration(flagId, released); + } + + TextureProxy.releaseTextureId(glId); + } + + public static int getTextureGlId(Identifier identifier, TextureManager textureManager) { + Integer trackedGlId = textureID2GLID.get(identifier); + if (trackedGlId != null) { + return trackedGlId; + } + + int glId = textureManager.getTexture(identifier).getGlId(); + textureID2GLID.put(identifier, glId); + return glId; + } + + public static int getRenderLayerTextureGlId(RenderLayer renderLayer, + TextureManager textureManager, Identifier fallbackIdentifier) { + int epoch = RENDER_LAYER_TEXTURE_CACHE_EPOCH.get(); + CachedLayerTexture cachedLayerTexture = RENDER_LAYER_TEXTURES.get(renderLayer); + if (cachedLayerTexture != null && cachedLayerTexture.epoch() == epoch) { + return cachedLayerTexture.glId(); + } + + Identifier identifier = fallbackIdentifier; + if (renderLayer instanceof RenderLayer.MultiPhase multiPhase) { + identifier = multiPhase.phases.texture.getId().orElse(fallbackIdentifier); + } + + int glId = getTextureGlId(identifier, textureManager); + RENDER_LAYER_TEXTURES.put(renderLayer, new CachedLayerTexture(glId, epoch)); + return glId; + } public record Texture(int width, int height, int channel, VulkanConstants.VkFormat format, int maxLayer) { @@ -43,10 +131,10 @@ private static int getChannel(NativeImage.InternalFormat internalFormat) { private static VulkanConstants.VkFormat getFormat( NativeImage.InternalFormat internalFormat) { return switch (internalFormat) { - case RGBA -> VulkanConstants.VkFormat.VK_FORMAT_R8G8B8A8_UNORM; - case RGB -> VulkanConstants.VkFormat.VK_FORMAT_R8G8B8_UNORM; - case RG -> VulkanConstants.VkFormat.VK_FORMAT_R8G8_UNORM; - case RED -> VulkanConstants.VkFormat.VK_FORMAT_R8_UNORM; + case RGBA -> VulkanConstants.VkFormat.VK_FORMAT_R8G8B8A8_SRGB; + case RGB -> VulkanConstants.VkFormat.VK_FORMAT_R8G8B8_SRGB; + case RG -> VulkanConstants.VkFormat.VK_FORMAT_R8G8_SRGB; + case RED -> VulkanConstants.VkFormat.VK_FORMAT_R8_SRGB; }; } } diff --git a/src/main/java/com/radiance/client/util/CategoryVideoOptionEntry.java b/src/main/java/com/radiance/client/util/CategoryVideoOptionEntry.java index adcf888..5c4ef71 100644 --- a/src/main/java/com/radiance/client/util/CategoryVideoOptionEntry.java +++ b/src/main/java/com/radiance/client/util/CategoryVideoOptionEntry.java @@ -1,6 +1,7 @@ package com.radiance.client.util; import com.google.common.collect.ImmutableList; +import com.radiance.client.gui.RadianceTheme; import java.util.List; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; @@ -8,32 +9,24 @@ import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.widget.OptionListWidget; import net.minecraft.text.Text; -import net.minecraft.util.Colors; public class CategoryVideoOptionEntry extends OptionListWidget.WidgetEntry { private final Text text; - private final int textWidth; private final MinecraftClient client; - private final OptionListWidget parent; public CategoryVideoOptionEntry(Text text, OptionListWidget parent) { super(ImmutableList.of(), null); this.client = MinecraftClient.getInstance(); - this.parent = parent; - this.text = text; - this.textWidth = this.client.textRenderer.getWidth(this.text); } @Override public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - context.drawTextWithShadow( - this.client.textRenderer, this.text, parent.getWidth() / 2 - this.textWidth / 2, - y + entryHeight - 9 - 1, Colors.WHITE - ); + RadianceTheme.drawCategoryHeader(context, this.client.textRenderer, this.text, x, y, + entryWidth, entryHeight); } @Override diff --git a/src/main/java/com/radiance/client/vertex/PBRVertexConsumer.java b/src/main/java/com/radiance/client/vertex/PBRVertexConsumer.java index 28ea831..4d55ba4 100644 --- a/src/main/java/com/radiance/client/vertex/PBRVertexConsumer.java +++ b/src/main/java/com/radiance/client/vertex/PBRVertexConsumer.java @@ -18,7 +18,9 @@ import static com.radiance.client.vertex.PBRVertexFormatElements.PBR_USE_OVERLAY; import static com.radiance.client.vertex.PBRVertexFormatElements.PBR_USE_TEXTURE; +import com.radiance.client.texture.TextureTracker; import java.nio.ByteOrder; +import java.util.Locale; import java.util.stream.Collectors; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.BuiltBuffer; @@ -30,7 +32,6 @@ import net.minecraft.client.texture.MissingSprite; import net.minecraft.client.util.BufferAllocator; import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.Identifier; import net.minecraft.util.math.Direction; import org.jetbrains.annotations.Nullable; import org.joml.Matrix3f; @@ -44,6 +45,8 @@ public class PBRVertexConsumer implements VertexConsumer { private static final int ALPHA_MODE_OPAQUE = 0; private static final int ALPHA_MODE_CUTOUT = 1; private static final int ALPHA_MODE_TRANSPARENT = 2; + public static final int MATERIAL_HINT_FORCE_NO_PBR = 1 << 0; + public static final int MATERIAL_HINT_WIND_REACTIVE = 1 << 1; private final BufferAllocator allocator; private final VertexFormat format; @@ -60,9 +63,12 @@ public class PBRVertexConsumer implements VertexConsumer { private boolean building = true; private int textureID; private final int alphaMode; + private final int defaultMaterialHints; + private int materialHints = 0; private float baseX = 0; private float baseY = 0; private float baseZ = 0; + private float pendingEmission = 0.0f; public PBRVertexConsumer(BufferAllocator allocator, RenderLayer renderLayer) { this(allocator, VertexFormat.DrawMode.QUADS, PBRVertexFormats.PBR_TRIANGLE, renderLayer); @@ -88,17 +94,13 @@ private PBRVertexConsumer(BufferAllocator allocator, VertexFormat.DrawMode drawM } if (renderLayer instanceof RenderLayer.MultiPhase) { - Identifier - identifier = - ((RenderLayer.MultiPhase) renderLayer).phases.texture.getId() - .orElse(MissingSprite.getMissingSpriteId()); - textureID = - MinecraftClient.getInstance() - .getTextureManager() - .getTexture(identifier) - .getGlId(); + textureID = TextureTracker.getRenderLayerTextureGlId(renderLayer, + MinecraftClient.getInstance().getTextureManager(), + MissingSprite.getMissingSpriteId()); } this.alphaMode = getAlphaMode(renderLayer); + this.defaultMaterialHints = getDefaultMaterialHints(renderLayer); + this.materialHints = this.defaultMaterialHints; } private static void putInt(long ptr, int v) { @@ -119,6 +121,10 @@ private static int getAlphaMode(RenderLayer renderLayer) { return ALPHA_MODE_OPAQUE; } + if (multiPhase.name.contains("particle") || multiPhase.name.contains("weather")) { + return ALPHA_MODE_TRANSPARENT; + } + if (multiPhase.name.contains("cutout")) { return ALPHA_MODE_CUTOUT; } @@ -130,6 +136,25 @@ private static int getAlphaMode(RenderLayer renderLayer) { return ALPHA_MODE_TRANSPARENT; } + private static int getDefaultMaterialHints(RenderLayer renderLayer) { + if (!(renderLayer instanceof RenderLayer.MultiPhase multiPhase)) { + return 0; + } + + String name = multiPhase.name.toLowerCase(Locale.ROOT); + boolean windReactive = + name.contains("cutout") + || name.contains("tripwire") + || name.contains("vine") + || name.contains("leaves") + || name.contains("grass") + || name.contains("plant") + || name.contains("crop") + || name.contains("sapling") + || name.contains("flower"); + return windReactive ? MATERIAL_HINT_WIND_REACTIVE : 0; + } + public VertexFormat getFormat() { return this.format; } @@ -138,6 +163,11 @@ public int getVertexCount() { return this.vertexCount; } + public PBRVertexConsumer materialHints(int materialHints) { + this.materialHints = Math.max(materialHints, 0) | this.defaultMaterialHints; + return this; + } + public void setBase(float x, float y, float z) { this.baseX = x; this.baseY = y; @@ -206,8 +236,8 @@ private long beginVertex() { MemoryUtil.memPutFloat(ptr + offBase, baseX); MemoryUtil.memPutFloat(ptr + offBase + 4L, baseY); MemoryUtil.memPutFloat(ptr + offBase + 8L, baseZ); - // Reuse the trailing padding word after postBase for alpha mode. - putInt(ptr + offBase + 12L, this.alphaMode); + // Reuse the trailing padding word after postBase for alpha mode and material hints. + putInt(ptr + offBase + 12L, this.alphaMode | (this.materialHints << 8)); } return ptr; @@ -234,8 +264,8 @@ private long beginVertex(int glintTextureID) { MemoryUtil.memPutFloat(ptr + offBase, baseX); MemoryUtil.memPutFloat(ptr + offBase + 4L, baseY); MemoryUtil.memPutFloat(ptr + offBase + 8L, baseZ); - // Reuse the trailing padding word after postBase for alpha mode. - putInt(ptr + offBase + 12L, this.alphaMode); + // Reuse the trailing padding word after postBase for alpha mode and material hints. + putInt(ptr + offBase + 12L, this.alphaMode | (this.materialHints << 8)); } if (glintTextureID != 0) { @@ -305,6 +335,10 @@ public VertexConsumer vertex(float x, float y, float z) { MemoryUtil.memPutFloat(p + 8L, z); } + if (pendingEmission > 0.0f) { + albedoEmission(pendingEmission); + } + return this; } @@ -325,6 +359,10 @@ public VertexConsumer vertex(float x, float y, float z, int glintTextureID) { MemoryUtil.memPutFloat(p + 8L, z); } + if (pendingEmission > 0.0f) { + albedoEmission(pendingEmission); + } + return this; } @@ -414,6 +452,10 @@ public VertexConsumer albedoEmission(float emission) { return this; } + public void setPendingEmission(float emission) { + this.pendingEmission = Math.max(0.0f, emission); + } + public static class GLint implements VertexConsumer { private final PBRVertexConsumer delegate; @@ -422,15 +464,9 @@ public static class GLint implements VertexConsumer { public GLint(PBRVertexConsumer delegate, RenderLayer glintRenderLayer) { this.delegate = delegate; if (glintRenderLayer instanceof RenderLayer.MultiPhase) { - Identifier - identifier = - ((RenderLayer.MultiPhase) glintRenderLayer).phases.texture.getId() - .orElse(MissingSprite.getMissingSpriteId()); - glintTextureID = - MinecraftClient.getInstance() - .getTextureManager() - .getTexture(identifier) - .getGlId(); + glintTextureID = TextureTracker.getRenderLayerTextureGlId(glintRenderLayer, + MinecraftClient.getInstance().getTextureManager(), + MissingSprite.getMissingSpriteId()); } } @@ -499,15 +535,9 @@ public GLintOverlay(PBRVertexConsumer delegate, RenderLayer glintRenderLayer, MatrixStack.Entry matrix, float textureScale) { this.delegate = delegate; if (glintRenderLayer instanceof RenderLayer.MultiPhase) { - Identifier - identifier = - ((RenderLayer.MultiPhase) glintRenderLayer).phases.texture.getId() - .orElse(MissingSprite.getMissingSpriteId()); - glintTextureID = - MinecraftClient.getInstance() - .getTextureManager() - .getTexture(identifier) - .getGlId(); + glintTextureID = TextureTracker.getRenderLayerTextureGlId(glintRenderLayer, + MinecraftClient.getInstance().getTextureManager(), + MissingSprite.getMissingSpriteId()); } this.inverseTextureMatrix = new Matrix4f(matrix.getPositionMatrix()).invert(); diff --git a/src/main/java/com/radiance/client/vertex/StorageVertexConsumerProvider.java b/src/main/java/com/radiance/client/vertex/StorageVertexConsumerProvider.java index 05223e3..fe0d4f9 100644 --- a/src/main/java/com/radiance/client/vertex/StorageVertexConsumerProvider.java +++ b/src/main/java/com/radiance/client/vertex/StorageVertexConsumerProvider.java @@ -1,8 +1,8 @@ package com.radiance.client.vertex; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.render.BufferBuilder; @@ -15,8 +15,8 @@ @Environment(EnvType.CLIENT) public class StorageVertexConsumerProvider implements VertexConsumerProvider { - protected final Map pending = new HashMap<>(); - protected final Map allocated = new HashMap<>(); + protected final Map pending = new ConcurrentHashMap<>(); + protected final Map allocated = new ConcurrentHashMap<>(); private int size = 0; diff --git a/src/main/java/com/radiance/mixin_related/MixinPlugin.java b/src/main/java/com/radiance/mixin_related/MixinPlugin.java index 8b18971..aee9032 100644 --- a/src/main/java/com/radiance/mixin_related/MixinPlugin.java +++ b/src/main/java/com/radiance/mixin_related/MixinPlugin.java @@ -1,5 +1,6 @@ package com.radiance.mixin_related; +import java.util.Collections; import java.util.List; import java.util.Set; import org.objectweb.asm.tree.ClassNode; @@ -25,7 +26,7 @@ public void acceptTargets(Set myTargets, Set otherTargets) { @Override public List getMixins() { - return null; + return Collections.emptyList(); } @Override diff --git a/src/main/java/com/radiance/mixins/vanilla_resource_tracker/NativeImageMixins.java b/src/main/java/com/radiance/mixins/vanilla_resource_tracker/NativeImageMixins.java index e32eca6..47cf2d5 100644 --- a/src/main/java/com/radiance/mixins/vanilla_resource_tracker/NativeImageMixins.java +++ b/src/main/java/com/radiance/mixins/vanilla_resource_tracker/NativeImageMixins.java @@ -1,5 +1,6 @@ package com.radiance.mixins.vanilla_resource_tracker; +import com.radiance.client.proxy.vulkan.TextureProxy; import com.radiance.client.texture.IdentifierInputStream; import com.radiance.mixin_related.extensions.vanilla_resource_tracker.INativeImageExt; import java.io.InputStream; @@ -14,6 +15,13 @@ @Mixin(NativeImage.class) public abstract class NativeImageMixins implements INativeImageExt { + @Unique + private static final int RADIANCE_ALPHA_FULLY_OPAQUE = 0; + @Unique + private static final int RADIANCE_ALPHA_FULLY_TRANSPARENT = 1; + @Unique + private static final int RADIANCE_ALPHA_MIXED = 2; + @Unique private int targetID = -1; @@ -53,6 +61,9 @@ private static void readIdentifier(NativeImage.Format format, InputStream stream @Override public void radiance$setTargetID(int id) { this.targetID = id; + if (id > 0) { + TextureProxy.setTextureAlphaClass(id, radiance$detectAlphaClass()); + } } @Override @@ -94,4 +105,38 @@ private static void readIdentifier(NativeImage.Format format, InputStream stream public void radiance$setFlagNativeImage(NativeImage image) { this.flagImage = image; } + + @Unique + private int radiance$detectAlphaClass() { + NativeImage self = (NativeImage) (Object) this; + int width = self.getWidth(); + int height = self.getHeight(); + boolean sawOpaque = false; + boolean sawTransparent = false; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int alpha = (self.getColorArgb(x, y) >>> 24) & 0xFF; + if (alpha >= 250) { + sawOpaque = true; + } else if (alpha <= 5) { + sawTransparent = true; + } else { + return RADIANCE_ALPHA_MIXED; + } + + if (sawOpaque && sawTransparent) { + return RADIANCE_ALPHA_MIXED; + } + } + } + + if (sawOpaque) { + return RADIANCE_ALPHA_FULLY_OPAQUE; + } + if (sawTransparent) { + return RADIANCE_ALPHA_FULLY_TRANSPARENT; + } + return RADIANCE_ALPHA_MIXED; + } } diff --git a/src/main/java/com/radiance/mixins/vanilla_resource_tracker/TextureManagerMixins.java b/src/main/java/com/radiance/mixins/vanilla_resource_tracker/TextureManagerMixins.java index de2506e..bceebf0 100644 --- a/src/main/java/com/radiance/mixins/vanilla_resource_tracker/TextureManagerMixins.java +++ b/src/main/java/com/radiance/mixins/vanilla_resource_tracker/TextureManagerMixins.java @@ -14,7 +14,7 @@ public abstract class TextureManagerMixins { @Inject(method = "registerTexture(Lnet/minecraft/util/Identifier;Lnet/minecraft/client/texture/AbstractTexture;)V", at = @At("HEAD")) private void profileTextureRegister(Identifier id, AbstractTexture texture, CallbackInfo ci) { - TextureTracker.textureID2GLID.put(id, texture.getGlId()); + TextureTracker.trackTextureRegistration(id, texture.getGlId()); // System.out.println("Registered texture: " + id + " (GL_ID = " + texture.getGlId() + ")"); } } diff --git a/src/main/java/com/radiance/mixins/vulkan_options/VideoOptionsScreenMixins.java b/src/main/java/com/radiance/mixins/vulkan_options/VideoOptionsScreenMixins.java index a8ee6fa..5815f8e 100644 --- a/src/main/java/com/radiance/mixins/vulkan_options/VideoOptionsScreenMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_options/VideoOptionsScreenMixins.java @@ -6,10 +6,12 @@ import com.google.common.collect.ImmutableList; import com.mojang.serialization.Codec; import com.radiance.client.gui.PotentialValuesBasedCallbacksNoValue; -import com.radiance.client.gui.RenderPipelineScreen; +import com.radiance.client.gui.RadianceSettingsScreen; import com.radiance.client.option.Options; +import com.radiance.client.option.QualityLevel; import com.radiance.client.util.CategoryVideoOptionEntry; import java.util.Arrays; +import java.util.List; import java.util.Optional; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.option.VideoOptionsScreen; @@ -133,6 +135,67 @@ public void redirectAddOptions(CallbackInfo ci) { } }); + SimpleOption enableHdrOutput = SimpleOption.ofBoolean(Options.HDR_OUTPUT_KEY, + Options.hdrOutput, value -> { + if (MinecraftClient.getInstance().getWindow() != null) { + Options.setHdrOutput(value, true); + } + }); + + SimpleOption enableDlssFrameGeneration = SimpleOption.ofBoolean( + Options.DLSS_FRAME_GENERATION_KEY, + Options.dlssFrameGeneration, + value -> { + if (MinecraftClient.getInstance().getWindow() != null) { + Options.setDlssFrameGeneration(value, true); + } + }); + + SimpleOption enableOutputScale2x = SimpleOption.ofBoolean( + Options.OUTPUT_SCALE_2X_KEY, + Options.outputScale2x, + value -> { + if (MinecraftClient.getInstance().getWindow() != null) { + Options.setOutputScale2x(value, true); + } + }); + + SimpleOption enableSimplifiedIndirect = SimpleOption.ofBoolean( + Options.SIMPLIFIED_INDIRECT_KEY, + Options.simplifiedIndirect, + value -> { + if (MinecraftClient.getInstance().getWindow() != null) { + Options.setSimplifiedIndirect(value, true); + } + }); + + SimpleOption enableReflex = SimpleOption.ofBoolean( + Options.REFLEX_ENABLED_KEY, + Options.reflexEnabled, + value -> { + if (MinecraftClient.getInstance().getWindow() != null) { + Options.setReflexEnabled(value, true); + } + }); + + SimpleOption enableReflexBoost = SimpleOption.ofBoolean( + Options.REFLEX_BOOST_KEY, + Options.reflexBoost, + value -> { + if (MinecraftClient.getInstance().getWindow() != null) { + Options.setReflexBoost(value, true); + } + }); + + SimpleOption enableVrrMode = SimpleOption.ofBoolean( + Options.VRR_MODE_KEY, + Options.vrrMode, + value -> { + if (MinecraftClient.getInstance().getWindow() != null) { + Options.setVrrMode(value, true); + } + }); + SimpleOption chunkBuildingBatchSize = new SimpleOption<>(Options.CHUNK_BUILDING_BATCH_SIZE_KEY, @@ -159,16 +222,29 @@ public void redirectAddOptions(CallbackInfo ci) { Options.setChunkBuildingTotalBatches(value, true); }); - SimpleOption pipelineSettings = new SimpleOption<>(Options.PIPELINE_SETUP_KEY, + SimpleOption pipelineSettings = new SimpleOption<>("radiance.settings.title", SimpleOption.emptyTooltip(), (optionText, value) -> optionText, BOOLEAN_NO_KEY, false, value -> { MinecraftClient.getInstance() - .setScreen(new RenderPipelineScreen((VideoOptionsScreen) (Object) this)); + .setScreen(new RadianceSettingsScreen((VideoOptionsScreen) (Object) this)); }); + SimpleOption qualityLevel = new SimpleOption<>(Options.QUALITY_LEVEL_KEY, + SimpleOption.emptyTooltip(), + SimpleOption.enumValueText(), + new SimpleOption.PotentialValuesBasedCallbacks<>(List.of( + QualityLevel.FLUENT, + QualityLevel.PERFORMANCE, + QualityLevel.BALANCED, + QualityLevel.HIGH, + QualityLevel.ULTRA, + QualityLevel.EXTREME), QualityLevel.Codec), + QualityLevel.fromId(Options.qualityLevel), + value -> Options.setQualityLevel(value, true)); + // Adding categories and options this.body.addEntry( new CategoryVideoOptionEntry(Text.translatable(Options.CATEGORY_GAMEPLAY), body)); @@ -200,6 +276,7 @@ public void redirectAddOptions(CallbackInfo ci) { maxFps, // inactivityFpsLimit, // enableVsync, // + enableHdrOutput, // gameOptions.getFullscreen(), // }; this.body.addAll(optionsWindow); @@ -212,6 +289,13 @@ public void redirectAddOptions(CallbackInfo ci) { this.body.addEntry( new CategoryVideoOptionEntry(Text.translatable(Options.CATEGORY_PIPELINE), body)); + this.body.addSingleOptionEntry(qualityLevel); + this.body.addSingleOptionEntry(enableDlssFrameGeneration); + this.body.addSingleOptionEntry(enableOutputScale2x); + this.body.addSingleOptionEntry(enableSimplifiedIndirect); + this.body.addSingleOptionEntry(enableReflex); + this.body.addSingleOptionEntry(enableReflexBoost); + this.body.addSingleOptionEntry(enableVrrMode); this.body.addSingleOptionEntry(pipelineSettings); ci.cancel(); diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/AbstractTextureMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/AbstractTextureMixins.java index 2be180d..b03dcee 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/AbstractTextureMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/AbstractTextureMixins.java @@ -3,6 +3,7 @@ import com.mojang.blaze3d.platform.TextureUtil; import com.radiance.client.constant.VulkanConstants; import com.radiance.client.proxy.vulkan.TextureProxy; +import com.radiance.client.texture.TextureTracker; import com.radiance.mixin_related.extensions.vulkan_render_integration.IAbstractTextureExt; import net.minecraft.client.texture.AbstractTexture; import org.spongepowered.asm.mixin.Mixin; @@ -47,6 +48,12 @@ public void redirectSetClamp(boolean clamp, CallbackInfo ci) { @Inject(method = "clearGlId()V", at = @At(value = "HEAD"), cancellable = true) public void cancelClearGlId(CallbackInfo ci) { + synchronized (AbstractTextureMixins.class) { + if (this.glId != -1) { + TextureTracker.releaseTextureRegistration(this.glId); + this.glId = -1; + } + } ci.cancel(); } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/BlockColorsMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/BlockColorsMixins.java index 99aae9e..55ddf6c 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/BlockColorsMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/BlockColorsMixins.java @@ -1,6 +1,10 @@ package com.radiance.mixins.vulkan_render_integration; import com.radiance.client.util.BlockColorEmissionProvider; +import com.radiance.client.util.EmissiveBlock; +import com.radiance.client.util.LightSourceDef; +import com.radiance.client.util.LightSourceRegistry; +import com.radiance.client.util.MaterialToolkit; import com.radiance.mixin_related.extensions.vulkan_render_integration.IBlockColorsExt; import net.minecraft.block.BlockState; import net.minecraft.client.color.block.BlockColorProvider; @@ -43,9 +47,20 @@ public class BlockColorsMixins implements IBlockColorsExt { BlockColorProvider blockColorProvider = this.providers.get( Registries.BLOCK.getRawId(state.getBlock())); if (blockColorProvider instanceof BlockColorEmissionProvider blockColorEmissionProvider) { - return blockColorEmissionProvider.getEmission(state, world, pos, tintIndex); - } else { - return 0.0F; + float providedEmission = blockColorEmissionProvider.getEmission(state, world, pos, + tintIndex); + if (providedEmission > 0.0F) { + return providedEmission; + } } + + String blockKey = Registries.BLOCK.getId(state.getBlock()).toString(); + EmissiveBlock emissiveBlock = MaterialToolkit.findEmissiveBlock(blockKey); + if (emissiveBlock != null && emissiveBlock.strength != null) { + return emissiveBlock.strength; + } + + LightSourceDef lightSourceDef = LightSourceRegistry.findBlock(blockKey); + return LightSourceRegistry.resolveStrength(lightSourceDef, 0.0f); } } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/BlockModelRendererMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/BlockModelRendererMixins.java index 926b8f2..6e6f931 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/BlockModelRendererMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/BlockModelRendererMixins.java @@ -20,6 +20,11 @@ @Mixin(BlockModelRenderer.class) public class BlockModelRendererMixins { + private static final ThreadLocal BRIGHTNESS_BUFFER = ThreadLocal.withInitial( + () -> new float[4]); + private static final ThreadLocal LIGHT_BUFFER = ThreadLocal.withInitial( + () -> new int[4]); + @Final @Shadow private BlockColors colors; @@ -51,36 +56,52 @@ public void redirectRenderQuad(BlockRenderView world, float f; float g; float h; - float emission; + float emission = ((IBlockColorsExt) this.colors).radiance$getEmission(state, world, pos, + quad.getTintIndex()); if (quad.hasTint()) { int i = this.colors.getColor(state, world, pos, quad.getTintIndex()); f = (i >> 16 & 0xFF) / 255.0F; g = (i >> 8 & 0xFF) / 255.0F; h = (i & 0xFF) / 255.0F; - - emission = ((IBlockColorsExt) this.colors).radiance$getEmission(state, world, pos, - quad.getTintIndex()); } else { f = 1.0F; g = 1.0F; h = 1.0F; + } - emission = 0.0F; + PBRVertexConsumer pbrVertexConsumer = null; + if (vertexConsumer instanceof PBRVertexConsumer pbr) { + pbrVertexConsumer = pbr; + pbrVertexConsumer.setPendingEmission(emission); } - vertexConsumer.quad(matrixEntry, - quad, - new float[]{brightness0, brightness1, brightness2, brightness3}, - f, - g, - h, - 1.0F, - new int[]{light0, light1, light2, light3}, - overlay, - true); + float[] brightness = BRIGHTNESS_BUFFER.get(); + brightness[0] = brightness0; + brightness[1] = brightness1; + brightness[2] = brightness2; + brightness[3] = brightness3; + + int[] lights = LIGHT_BUFFER.get(); + lights[0] = light0; + lights[1] = light1; + lights[2] = light2; + lights[3] = light3; - if (vertexConsumer instanceof PBRVertexConsumer pbrVertexConsumer) { - pbrVertexConsumer.albedoEmission(emission); + try { + vertexConsumer.quad(matrixEntry, + quad, + brightness, + f, + g, + h, + 1.0F, + lights, + overlay, + true); + } finally { + if (pbrVertexConsumer != null) { + pbrVertexConsumer.setPendingEmission(0.0F); + } } ci.cancel(); diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/BufferRendererMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/BufferRendererMixins.java index b899ec2..9acddb0 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/BufferRendererMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/BufferRendererMixins.java @@ -4,6 +4,8 @@ import com.radiance.client.proxy.vulkan.RendererProxy; import net.minecraft.client.render.BufferRenderer; import net.minecraft.client.render.BuiltBuffer; +import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.VertexFormats; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -16,6 +18,16 @@ public class BufferRendererMixins { at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;assertOnRenderThread()V", shift = At.Shift.AFTER, remap = false), cancellable = true) private static void rewriteDrawWithGlobalProgram(BuiltBuffer buffer, CallbackInfo ci) { + int pipelineType = resolveOverlayPipelineType(buffer); + if (pipelineType < 0 && RendererProxy.hasOverlayPipeline()) { + pipelineType = RendererProxy.getOverlayPipelineType(); + } + if (pipelineType < 0) { + return; + } + + RendererProxy.bindOverlayPipeline(pipelineType); + BufferProxy.VertexIndexBufferHandle handle = BufferProxy.createAndUploadVertexIndexBuffer( buffer); @@ -24,6 +36,7 @@ private static void rewriteDrawWithGlobalProgram(BuiltBuffer buffer, CallbackInf RendererProxy.drawOverlay(handle, buffer.getDrawParameters() .indexCount(), + pipelineType, buffer.getDrawParameters() .indexType()); @@ -31,4 +44,29 @@ private static void rewriteDrawWithGlobalProgram(BuiltBuffer buffer, CallbackInf ci.cancel(); } + + private static int resolveOverlayPipelineType(BuiltBuffer buffer) { + VertexFormat format = buffer.getDrawParameters().format(); + if (format == VertexFormats.POSITION_TEXTURE_COLOR) { + return 2; + } + if (format == VertexFormats.POSITION_COLOR) { + return 1; + } + if (format == VertexFormats.POSITION_TEXTURE) { + return 0; + } + if (format == VertexFormats.POSITION_COLOR_TEXTURE_LIGHT + || format == VertexFormats.POSITION_TEXTURE_LIGHT_COLOR) { + return 3; + } + if (format == VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL) { + return 4; + } + if (format == VertexFormats.POSITION) { + return 7; + } + + return -1; + } } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/BuiltChunkStorageMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/BuiltChunkStorageMixins.java index 6cf215d..4e4e821 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/BuiltChunkStorageMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/BuiltChunkStorageMixins.java @@ -2,7 +2,9 @@ import com.radiance.client.proxy.world.ChunkProxy; import net.minecraft.client.render.BuiltChunkStorage; +import net.minecraft.client.render.chunk.ChunkBuilder; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyVariable; @@ -11,6 +13,9 @@ @Mixin(BuiltChunkStorage.class) public class BuiltChunkStorageMixins { + @Shadow + public ChunkBuilder.BuiltChunk[] chunks; + @Inject(method = "clear()V", at = @At(value = "HEAD")) public void clearChunkProxy(CallbackInfo ci) { ChunkProxy.clear(); @@ -21,4 +26,17 @@ private int initChunkRebuildGrid(int i) { ChunkProxy.init(i); return i; } + + @Inject(method = "createChunks(Lnet/minecraft/client/render/chunk/ChunkBuilder;)V", at = @At("TAIL")) + private void enqueueInitialChunkRebuilds(CallbackInfo ci) { + if (this.chunks == null) { + return; + } + + for (ChunkBuilder.BuiltChunk builtChunk : this.chunks) { + if (builtChunk != null) { + builtChunk.scheduleRebuild(false); + } + } + } } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/ClientChunkManagerMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/ClientChunkManagerMixins.java index 0bd6097..4d220c5 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/ClientChunkManagerMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/ClientChunkManagerMixins.java @@ -1,5 +1,6 @@ package com.radiance.mixins.vulkan_render_integration; +import com.radiance.client.proxy.world.ChunkProxy; import net.minecraft.client.world.ClientChunkManager; import net.minecraft.util.math.ChunkSectionPos; import net.minecraft.world.LightType; @@ -14,6 +15,7 @@ public class ClientChunkManagerMixins { @Inject(method = "onLightUpdate(Lnet/minecraft/world/LightType;Lnet/minecraft/util/math/ChunkSectionPos;)V", at = @At(value = "HEAD"), cancellable = true) public void cancelLightUpdate(LightType type, ChunkSectionPos pos, CallbackInfo ci) { + ChunkProxy.markLightDirtySection(pos, type.ordinal()); ci.cancel(); } } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/CloudRendererMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/CloudRendererMixins.java index 498bd64..e10377b 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/CloudRendererMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/CloudRendererMixins.java @@ -96,55 +96,18 @@ public void redirectCloudRendering(int color, Vec3d cameraPos, float ticks, CallbackInfo ci) { - if (this.cells != null) { - float f = (float) (cloudHeight - cameraPos.y); - float g = f + 4.0F; - CloudRenderer.ViewMode viewMode; - if (g < 0.0F) { - viewMode = CloudRenderer.ViewMode.ABOVE_CLOUDS; - } else if (f > 0.0F) { - viewMode = CloudRenderer.ViewMode.BELOW_CLOUDS; - } else { - viewMode = CloudRenderer.ViewMode.INSIDE_CLOUDS; - } - - double d = cameraPos.x + ticks * 0.030000001F; - double e = cameraPos.z + 3.96F; - double h = this.cells.width() * 12.0; - double i = this.cells.height() * 12.0; - d -= MathHelper.floor(d / h) * h; - e -= MathHelper.floor(e / i) * i; - int j = MathHelper.floor(d / 12.0); - int k = MathHelper.floor(e / 12.0); - float l = (float) (d - j * 12.0F); - float m = (float) (e - k * 12.0F); - RenderLayer - renderLayer = - cloudRenderMode == CloudRenderMode.FANCY ? RenderLayer.getFastClouds() - : RenderLayer.getNoCullingClouds(); - - if (this.field_53052 || j != this.centerX || k != this.centerZ - || viewMode != this.viewMode || - cloudRenderMode != this.renderMode) { - this.field_53052 = false; - this.centerX = j; - this.centerZ = k; - this.viewMode = viewMode; - this.renderMode = cloudRenderMode; - - this.tessellateClouds(color, j, k, cloudRenderMode, viewMode, renderLayer); - } - - if (storageVertexConsumerProvider != null) { - for (EntityProxy.EntityRenderData data : entityRenderDataList) { - data.setX((float) (cameraPos.x - l)); - data.setY(cloudHeight); - data.setZ((float) (cameraPos.z - m)); + if (storageVertexConsumerProvider != null) { + if (entityRenderDataList != null) { + for (EntityProxy.EntityRenderData entityRenderData : entityRenderDataList) { + for (EntityProxy.EntityRenderLayer entityRenderLayer : entityRenderData) { + BuiltBuffer vertexBuffer = entityRenderLayer.builtBuffer(); + vertexBuffer.close(); + } } - - EntityProxy.queueBuildWithoutClose(entityRenderDataList); } - + storageVertexConsumerProvider.close(); + storageVertexConsumerProvider = null; + entityRenderDataList = null; } ci.cancel(); diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/DirectoryAtlasSourceMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/DirectoryAtlasSourceMixins.java index af218d1..6efec5c 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/DirectoryAtlasSourceMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/DirectoryAtlasSourceMixins.java @@ -1,5 +1,6 @@ package com.radiance.mixins.vulkan_render_integration; +import com.radiance.client.texture.PbrAtlasTextureFilter; import java.util.Map; import java.util.Map.Entry; import net.minecraft.client.texture.atlas.AtlasSource.SpriteRegions; @@ -38,7 +39,7 @@ public void cancelPBRLoad(ResourceManager resourceManager, SpriteRegions regions Identifier identifier2 = resourceFinder.toResourceId(identifier) .withPrefixedPath(this.prefix); - if (identifier2.getPath().endsWith("_s") || identifier2.getPath().endsWith("_n")) { + if (PbrAtlasTextureFilter.shouldSkipAtlasEntry(identifier2)) { continue; } regions.add(identifier2, resource); diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/FluidRendererMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/FluidRendererMixins.java index 833ed2c..afea01c 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/FluidRendererMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/FluidRendererMixins.java @@ -2,6 +2,8 @@ import static net.minecraft.client.render.block.FluidRenderer.shouldRenderSide; +import com.radiance.client.option.EnvironmentRenderStyles; +import com.radiance.client.vertex.PBRVertexConsumer; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.LeavesBlock; @@ -125,6 +127,13 @@ public void addNormalToVertex(BlockRenderView world, FluidState fluidState, CallbackInfo ci) { boolean isLava = fluidState.isIn(FluidTags.LAVA); + PBRVertexConsumer pbrVertexConsumer = + vertexConsumer instanceof PBRVertexConsumer pbr ? pbr : null; + if (pbrVertexConsumer != null) { + pbrVertexConsumer.setPendingEmission(isLava ? 0.0F + : EnvironmentRenderStyles.WATER_SURFACE_SENTINEL); + } + try { Sprite[] fluidSprites = isLava ? this.lavaSprites : this.waterSprites; int tintColor = isLava ? 16777215 : BiomeColors.getWaterColor(world, pos); float red = (tintColor >> 16 & 0xFF) / 255.0F; @@ -672,5 +681,10 @@ public void addNormalToVertex(BlockRenderView world, } ci.cancel(); + } finally { + if (pbrVertexConsumer != null) { + pbrVertexConsumer.setPendingEmission(0.0F); + } + } } } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/GameRendererMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/GameRendererMixins.java index d466303..9141043 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/GameRendererMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/GameRendererMixins.java @@ -63,7 +63,7 @@ public class GameRendererMixins implements IGameRendererExt { "[Lnet/minecraft/client/gl/ShaderProgramKey;)V")) public void cancelPreloadShader(ShaderLoader instance, ResourceFactory factory, ShaderProgramKey[] keys) { - + // Intentionally empty: Radiance handles shader loading through its own pipeline } @Inject(method = "renderBlur()V", at = @At(value = "HEAD"), cancellable = true) diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/MinecraftClientMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/MinecraftClientMixins.java index 80cbd3b..112b72f 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/MinecraftClientMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/MinecraftClientMixins.java @@ -6,6 +6,7 @@ import com.radiance.client.proxy.vulkan.RendererProxy; import com.radiance.client.proxy.vulkan.TextureProxy; import com.radiance.client.proxy.world.ChunkProxy; +import com.radiance.client.proxy.world.EntityProxy; import java.util.Optional; import java.util.function.Consumer; import net.minecraft.client.MinecraftClient; @@ -61,7 +62,6 @@ public void initRenderer(int debugVerbosity, boolean debugSync) { } Pipeline.loadPipeline(); - Pipeline.build(); } @Redirect(method = "(Lnet/minecraft/client/RunArgs;)V", @@ -135,10 +135,18 @@ public void cancelFramebufferBeginWrite(Framebuffer instance, boolean setViewpor @Redirect(method = "render(Z)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gl/Framebuffer;endWrite()V")) public void cancelFramebufferEndWrite(Framebuffer instance, boolean setViewport) { + if (RendererProxy.isShuttingDown()) { + return; + } ChunkProxy.waitImportantChunkRebuild(); synchronized (TextureProxy.class) { + if (RendererProxy.isShuttingDown()) { + return; + } RendererProxy.submitCommandAndPresent(); - RendererProxy.acquireContext(); + if (!RendererProxy.isShuttingDown()) { + RendererProxy.acquireContext(); + } } } @@ -173,9 +181,14 @@ public void cancelShaderLoaderClose(ShaderLoader instance) { //endregion // region + @Inject(method = "scheduleStop()V", at = @At(value = "HEAD")) + public void beginShutdown(CallbackInfo ci) { + RendererProxy.requestShutdown(); + } + @Inject(method = "scheduleStop()V", at = @At(value = "TAIL")) public void close(CallbackInfo ci) { - RendererProxy.close(); + RendererProxy.closeRenderer(); } // endregion @@ -191,6 +204,7 @@ public void cancelRenderAfterStop(MinecraftClient instance, boolean tick) { public void resetBuiltChunkNum(Screen disconnectionScreen, boolean transferring, CallbackInfo ci) { ChunkProxy.builtChunkNum = 0; + EntityProxy.clearReplayCaches(); } // endregion } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/PalettedPermutationsAtlasSourceMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/PalettedPermutationsAtlasSourceMixins.java index f3b49fe..0e99bc8 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/PalettedPermutationsAtlasSourceMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/PalettedPermutationsAtlasSourceMixins.java @@ -1,5 +1,6 @@ package com.radiance.mixins.vulkan_render_integration; +import com.radiance.client.texture.PbrAtlasTextureFilter; import net.minecraft.client.texture.atlas.AtlasSource.SpriteRegion; import net.minecraft.client.texture.atlas.AtlasSource.SpriteRegions; import net.minecraft.client.texture.atlas.PalettedPermutationsAtlasSource; @@ -16,7 +17,7 @@ public class PalettedPermutationsAtlasSourceMixins { @Redirect(method = "load(Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/client/texture/atlas/AtlasSource$SpriteRegions;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/atlas/AtlasSource$SpriteRegions;add(Lnet/minecraft/util/Identifier;Lnet/minecraft/client/texture/atlas/AtlasSource$SpriteRegion;)V")) public void cancelPBRLoad(SpriteRegions instance, Identifier identifier, SpriteRegion spriteRegion) { - if (identifier.getPath().endsWith("_s") || identifier.getPath().endsWith("_n")) { + if (PbrAtlasTextureFilter.shouldSkipAtlasEntry(identifier)) { return; } instance.add(identifier, spriteRegion); diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/RenderSystemMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/RenderSystemMixins.java index 7a204c7..87edcf3 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/RenderSystemMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/RenderSystemMixins.java @@ -2,6 +2,7 @@ import com.mojang.blaze3d.systems.RenderSystem; import com.radiance.client.proxy.vulkan.RendererProxy; +import java.util.Locale; import net.minecraft.client.gl.ShaderProgram; import net.minecraft.client.gl.ShaderProgramKey; import org.joml.Matrix4f; @@ -41,37 +42,12 @@ private static void redirectMaxSupportedTextureSize(CallbackInfoReturnable cir) { - // need formalize - int - type = - switch (shaderProgramKey.configId() - .toString()) { - case "minecraft:core/rendertype_glint", - "minecraft:core/rendertype_glint_translucent", - "minecraft:core/rendertype_entity_glint", - "minecraft:core/rendertype_armor_entity_glint" -> 0; - case "minecraft:core/position_color", "minecraft:core/rendertype_gui", - "minecraft:core/rendertype_gui_overlay", - "minecraft:core/rendertype_gui_text_highlight" -> 1; - case "minecraft:core/position_tex_color" -> 2; - case "minecraft:core/rendertype_text" -> 3; - case "minecraft:core/rendertype_entity_cutout", - "minecraft:core/rendertype_entity_cutout_no_cull", - "minecraft:core/rendertype_entity_cutout_no_cull_z_offset", - "minecraft:core/rendertype_entity_translucent", - "minecraft:core/rendertype_item_entity_translucent_cull", - "minecraft:core/rendertype_entity_solid" -> 4; - case "minecraft:core/rendertype_entity_no_outline", - "minecraft:core/rendertype_armor_cutout_no_cull", - "minecraft:core/rendertype_armor_translucent" -> 5; - case "minecraft:core/rendertype_end_portal" -> 6; - case "minecraft:core/position" -> 7; - default -> throw new IllegalStateException( - "Unexpected value: " + shaderProgramKey.configId()); - }; + int type = resolveOverlayPipelineType(shaderProgramKey.configId().toString()); + if (type < 0) { + return; + } RendererProxy.bindOverlayPipeline(type); - cir.setReturnValue(null); } @@ -86,4 +62,55 @@ private static void cancelDrawCrossAirForNow(int size, boolean drawX, boolean dr boolean drawZ) { } + + private static int resolveOverlayPipelineType(String shaderName) { + return switch (normalizeShaderName(shaderName)) { + case "rendertype_glint", "rendertype_glint_direct", "rendertype_glint_translucent", + "rendertype_entity_glint", "rendertype_entity_glint_direct", + "rendertype_armor_glint", "rendertype_armor_entity_glint", "position_tex" -> 0; + case "position_color", "rendertype_gui", "rendertype_gui_overlay", + "rendertype_gui_text_highlight", "rendertype_gui_ghost_recipe_overlay", + "rendertype_lines" -> 1; + case "position_tex_color", "position_color_tex", "rendertype_lightning" -> 2; + case "rendertype_text", "rendertype_text_background", + "rendertype_text_background_see_through", "rendertype_text_intensity", + "rendertype_text_intensity_see_through", "rendertype_text_see_through", + "particle" -> 3; + case "rendertype_entity_cutout", "rendertype_entity_cutout_no_cull", + "rendertype_entity_cutout_no_cull_z_offset", + "rendertype_entity_translucent", "rendertype_entity_translucent_cull", + "rendertype_entity_translucent_emissive", + "rendertype_item_entity_translucent_cull", "rendertype_entity_solid", + "rendertype_entity_smooth_cutout", "rendertype_entity_shadow", + "rendertype_entity_alpha", "rendertype_entity_decal", + "rendertype_energy_swirl", "rendertype_eyes", "rendertype_beacon_beam" -> 4; + case "rendertype_entity_no_outline", "rendertype_armor_cutout_no_cull", + "rendertype_armor_translucent" -> 5; + case "rendertype_end_portal", "rendertype_end_gateway" -> 6; + case "position" -> 7; + default -> -1; + }; + } + + private static String normalizeShaderName(String shaderName) { + if (shaderName == null) { + return ""; + } + + String normalized = shaderName.toLowerCase(Locale.ROOT).replace('\\', '/'); + int namespaceSeparator = normalized.indexOf(':'); + if (namespaceSeparator >= 0) { + normalized = normalized.substring(namespaceSeparator + 1); + } + if (normalized.startsWith("core/")) { + normalized = normalized.substring("core/".length()); + } + if (normalized.startsWith("minecraft/shaders/core/")) { + normalized = normalized.substring("minecraft/shaders/core/".length()); + } + if (normalized.endsWith(".json")) { + normalized = normalized.substring(0, normalized.length() - ".json".length()); + } + return normalized; + } } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/ScreenMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/ScreenMixins.java index 6387c54..dbf1cc9 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/ScreenMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/ScreenMixins.java @@ -1,10 +1,13 @@ package com.radiance.mixins.vulkan_render_integration; import net.minecraft.client.gl.Framebuffer; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Screen.class) public class ScreenMixins { @@ -13,4 +16,28 @@ public class ScreenMixins { public void cancelFrameBufferInApplyBlur(Framebuffer instance, boolean setViewport) { } + + @Inject(method = "renderDarkening(Lnet/minecraft/client/gui/DrawContext;)V", + at = @At("HEAD"), cancellable = true) + private void skipDarkeningForRadianceScreens(DrawContext context, CallbackInfo ci) { + if (((Object) this).getClass().getPackageName().startsWith("com.radiance.client.gui")) { + ci.cancel(); + } + } + + @Inject(method = "renderDarkening(Lnet/minecraft/client/gui/DrawContext;IIII)V", + at = @At("HEAD"), cancellable = true) + private void skipDarkening4ArgForRadianceScreens(DrawContext context, int x, int y, int w, + int h, CallbackInfo ci) { + if (((Object) this).getClass().getPackageName().startsWith("com.radiance.client.gui")) { + ci.cancel(); + } + } + + @Inject(method = "renderInGameBackground", at = @At("HEAD"), cancellable = true) + private void skipInGameBackgroundForRadianceScreens(DrawContext context, CallbackInfo ci) { + if (((Object) this).getClass().getPackageName().startsWith("com.radiance.client.gui")) { + ci.cancel(); + } + } } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/SingleAtlasSourceMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/SingleAtlasSourceMixins.java index 8220f43..a45a590 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/SingleAtlasSourceMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/SingleAtlasSourceMixins.java @@ -1,5 +1,6 @@ package com.radiance.mixins.vulkan_render_integration; +import com.radiance.client.texture.PbrAtlasTextureFilter; import com.llamalad7.mixinextras.sugar.Local; import java.util.Optional; import net.minecraft.client.texture.atlas.AtlasSource.SpriteRegions; @@ -17,7 +18,7 @@ public class SingleAtlasSourceMixins { @Redirect(method = "load(Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/client/texture/atlas/AtlasSource$SpriteRegions;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/atlas/AtlasSource$SpriteRegions;add(Lnet/minecraft/util/Identifier;Lnet/minecraft/resource/Resource;)V")) public void cancelPBRLoad(SpriteRegions regions, Identifier id, Resource resource) { - if (id.getPath().endsWith("_s") || id.getPath().endsWith("_n")) { + if (PbrAtlasTextureFilter.shouldSkipAtlasEntry(id)) { return; } regions.add(id, resource); diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/UnstitchAtlasSourceMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/UnstitchAtlasSourceMixins.java index 7e3f119..fba3eb8 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/UnstitchAtlasSourceMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/UnstitchAtlasSourceMixins.java @@ -1,5 +1,6 @@ package com.radiance.mixins.vulkan_render_integration; +import com.radiance.client.texture.PbrAtlasTextureFilter; import net.minecraft.client.texture.atlas.AtlasSource.SpriteRegion; import net.minecraft.client.texture.atlas.AtlasSource.SpriteRegions; import net.minecraft.client.texture.atlas.SingleAtlasSource; @@ -16,7 +17,7 @@ public class UnstitchAtlasSourceMixins { @Redirect(method = "load(Lnet/minecraft/resource/ResourceManager;Lnet/minecraft/client/texture/atlas/AtlasSource$SpriteRegions;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/atlas/AtlasSource$SpriteRegions;add(Lnet/minecraft/util/Identifier;Lnet/minecraft/client/texture/atlas/AtlasSource$SpriteRegion;)V")) public void cancelPBRLoad(SpriteRegions instance, Identifier identifier, SpriteRegion spriteRegion) { - if (identifier.getPath().endsWith("_s") || identifier.getPath().endsWith("_n")) { + if (PbrAtlasTextureFilter.shouldSkipAtlasEntry(identifier)) { return; } instance.add(identifier, spriteRegion); diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/WindowMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/WindowMixins.java index 8dd803c..0044bac 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/WindowMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/WindowMixins.java @@ -141,4 +141,9 @@ public void cancelGetMaxSupportedTextureSize(long window, int minwidth, int minh public void framebufferSizeChanged(long window, int width, int height, CallbackInfo ci) { WindowProxy.onFramebufferSizeChanged(); } + + @Inject(method = "setVsync(Z)V", at = @At("HEAD"), cancellable = true) + public void cancelSetVsync(boolean vsync, CallbackInfo ci) { + ci.cancel(); + } } diff --git a/src/main/java/com/radiance/mixins/vulkan_render_integration/WorldRendererMixins.java b/src/main/java/com/radiance/mixins/vulkan_render_integration/WorldRendererMixins.java index 657c121..f52bd9f 100644 --- a/src/main/java/com/radiance/mixins/vulkan_render_integration/WorldRendererMixins.java +++ b/src/main/java/com/radiance/mixins/vulkan_render_integration/WorldRendererMixins.java @@ -44,7 +44,6 @@ import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.player.BlockBreakingInfo; -import net.minecraft.util.Pair; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.ColorHelper; @@ -289,6 +288,14 @@ public void redirectRender(ObjectAllocator allocator, RenderTickCounter tickCoun int moonPhase = this.world.getMoonPhase(); float rainGradient = this.world.getRainGradient(tickDelta); + CloudRenderMode cloudRenderMode = this.client.options.getCloudRenderModeValue(); + float cloudLayerHeight = -16384.0F; + if (cloudRenderMode != CloudRenderMode.OFF) { + float cloudsHeight = this.world.getDimensionEffects().getCloudsHeight(); + if (!Float.isNaN(cloudsHeight)) { + cloudLayerHeight = cloudsHeight + 0.33F; + } + } int sunTextureID = textureManager.getTexture(SkyRendering.SUN_TEXTURE).getGlId(); @@ -297,7 +304,7 @@ public void redirectRender(ObjectAllocator allocator, RenderTickCounter tickCoun BufferProxy.updateSkyUniform(baseColorR, baseColorG, baseColorB, horizontalColorR, horizontalColorG, horizontalColorB, horizontalColorA, sunDirection, skyType, sunRisingOrSetting, skyDark, hasBlindnessOrDarkness, submersionType, moonPhase, - rainGradient, sunTextureID, moonTextureID); + rainGradient, cloudLayerHeight, sunTextureID, moonTextureID); BufferProxy.updateMapping(); @@ -316,12 +323,11 @@ public void redirectRender(ObjectAllocator allocator, RenderTickCounter tickCoun EntityProxy.queueEntitiesBuild(camera, renderedEntities, this.entityRenderDispatcher, tickCounter, canDrawEntityOutlines()); - Pair, EntityProxy.EntityRenderDataList> crumblingRenderData = EntityProxy.queueBlockEntitiesRebuild( - chunks, this.noCullingBlockEntities, blockBreakingProgressions, + EntityProxy.BlockEntityQueueResult crumblingRenderData = EntityProxy.queueBlockEntitiesRebuild( + this.builtChunks, this.noCullingBlockEntities, blockBreakingProgressions, blockEntityRenderDispatcher, tickDelta); EntityProxy.queueCrumblingRebuild(camera, blockBreakingProgressions, - this.client.getBlockRenderManager(), this.world, crumblingRenderData.getLeft(), - crumblingRenderData.getRight()); + this.client.getBlockRenderManager(), this.world, crumblingRenderData); EntityProxy.queueParticleRebuild(camera, tickDelta, frustum); @@ -333,7 +339,6 @@ public void redirectRender(ObjectAllocator allocator, RenderTickCounter tickCoun camera, this.ticks, tickDelta); // clouds - CloudRenderMode cloudRenderMode = this.client.options.getCloudRenderModeValue(); if (cloudRenderMode != CloudRenderMode.OFF) { float k = this.world.getDimensionEffects().getCloudsHeight(); if (!Float.isNaN(k)) { @@ -346,7 +351,8 @@ public void redirectRender(ObjectAllocator allocator, RenderTickCounter tickCoun } // Chunks - ChunkProxy.rebuild(camera); + ChunkProxy.rebuild(camera, this.builtChunks); + ChunkProxy.queueSpecialGeometry(this.builtChunks, camera); this.renderedEntities.clear(); diff --git a/src/main/resources/NvLowLatencyVk.dll b/src/main/resources/NvLowLatencyVk.dll new file mode 100644 index 0000000..043c2d4 Binary files /dev/null and b/src/main/resources/NvLowLatencyVk.dll differ diff --git a/src/main/resources/assets/radiance/lang/en_us.json b/src/main/resources/assets/radiance/lang/en_us.json index 605fcb8..f16079d 100644 --- a/src/main/resources/assets/radiance/lang/en_us.json +++ b/src/main/resources/assets/radiance/lang/en_us.json @@ -15,6 +15,13 @@ "options.video.dlss_mode.quality": "Quality Mode", "options.video.dlss_mode.dlaa": "DLAA Mode", "options.video.dlss_mode": "DLSS Mode", + "options.video.quality_level": "Quality Level", + "options.video.quality_level.fluent": "Fluent", + "options.video.quality_level.performance": "Performance", + "options.video.quality_level.balanced": "Balanced", + "options.video.quality_level.quality": "High", + "options.video.quality_level.ultra": "Ultra", + "options.video.quality_level.extreme": "Extreme", "options.video.upscaler_type": "Upscaler", "options.video.upscaler_type.native": "Native", "options.video.upscaler_type.fsr3": "FSR3", @@ -28,6 +35,13 @@ "options.video.denoiser_mode.svgf": "SVGF", "options.video.denoiser_mode.nrd": "NRD", "options.video.denoiser_mode.temporal": "Temporal Accumulation", + "options.video.hdr_output": "HDR Output", + "options.video.dlss_frame_generation": "DLSS Frame Generation (NVIDIA/NGX, Experimental)", + "options.video.output_scale_2x": "Output Scale 2x", + "options.video.simplified_indirect": "Simplified Indirect Lighting", + "options.video.reflex_enabled": "NVIDIA Reflex", + "options.video.reflex_boost": "Reflex Boost", + "options.video.vrr_mode": "VRR Frame Cap", "options.video.ray_bounces": "Ray Bounces", "options.video.chunk_building_batch_size": "Amount of chunks to build in a single submit", "options.video.chunk_building_total_batches": "Amount of building chunk batches", @@ -44,6 +58,65 @@ "render_pipeline.module.dlss.attribute.mode.quality": "Quality", "render_pipeline.module.dlss.attribute.mode.dlaa": "DLAA", "render_pipeline.module.ray_tracing.name": "Ray Tracing", + "render_pipeline.module.ray_tracing.attribute.world_representation_mode": "World Representation", + "render_pipeline.module.ray_tracing.attribute.world_representation_mode.triangle_blas": "Triangle BLAS", + "render_pipeline.module.ray_tracing.attribute.world_representation_mode.chunk_aabb": "Chunk AABB", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode": "Chunk Traversal", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.triangle_hit": "Triangle Hit", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.voxel_dda": "Voxel DDA", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.brick": "Brick Traversal", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.macrocell": "Macrocell Traversal", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout": "Chunk Data Layout", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.triangle_geometry": "Triangle Geometry", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_bitmask": "Occupancy Bitmask", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_palette": "Occupancy + Material Palette", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_palette_face_mask": "Occupancy + Palette + Face Mask", + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size": "Chunk Macrocell Size", + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.disabled": "Disabled", + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.size_4": "4³", + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.size_8": "8³", + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode": "Terrain Meshing", + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.legacy_quads": "Legacy Quads", + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.greedy_meshing": "Greedy Meshing", + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge": "Coplanar Merge", + "render_pipeline.module.ray_tracing.attribute.greedy_merge_max_span": "Greedy Merge Max Span", + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode": "BLAS Inclusion", + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.all_geometry": "All Geometry", + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_and_shadow": "Opaque + Shadow Casters", + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_only": "Opaque Only", + "render_pipeline.module.ray_tracing.attribute.glass_path_mode": "Glass Path", + "render_pipeline.module.ray_tracing.attribute.foliage_path_mode": "Foliage Path", + "render_pipeline.module.ray_tracing.attribute.decoration_path_mode": "Decoration Path", + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas": "BLAS", + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.special_path": "Special Path", + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.exclude": "Exclude", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode": "Far Field Geometry", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.exact_chunks": "Exact Chunks", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.simplified_shell": "Simplified Shell", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.clipmap": "Clipmap", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.shell_and_clipmap": "Shell + Clipmap", + "render_pipeline.module.ray_tracing.attribute.far_field_start_distance_chunks": "Far Field Start Distance (Chunks)", + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode": "Far Field Material", + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.full_pbr": "Full PBR", + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.flat_surface": "Flat Surface", + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode": "Reflection Ray Materials", + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.all_materials": "All Materials", + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.reflective_only": "Reflective Only", + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.water_glass_metal": "Water / Glass / Metal", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode": "Diffuse GI Mode", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.full_ray_tracing": "Full Ray Tracing", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.probes": "Probes", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.radiance_cache": "Radiance Cache", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.low_cost_hybrid": "Low Cost Hybrid", + "render_pipeline.module.ray_tracing.attribute.separate_entity_terrain_accel_structures": "Separate Entity / Terrain AS", + "render_pipeline.module.ray_tracing.attribute.terrain_update_interval_frames": "Terrain Update Interval (Frames)", + "render_pipeline.module.ray_tracing.attribute.entity_update_interval_frames": "Entity Update Interval (Frames)", + "render_pipeline.module.ray_tracing.attribute.block_entity_update_interval_frames": "Block Entity Update Interval (Frames)", + "render_pipeline.module.ray_tracing.attribute.particle_update_interval_frames": "Particle Update Interval (Frames)", + "render_pipeline.module.ray_tracing.attribute.particle_crit_glow": "Critical Hit Particles Glow", + "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow": "Death Smoke Glow", + "render_pipeline.module.ray_tracing.attribute.particle_crit_glow_strength": "Critical Hit Glow Strength", + "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow_strength": "Death Smoke Glow Strength", "render_pipeline.module.ray_tracing.attribute.num_ray_bounces": "Num Ray Bounces", "render_pipeline.module.ray_tracing.attribute.use_jitter": "Use Jitter", "render_pipeline.module.ray_tracing.attribute.pbr_sampling_mode": "PBR Sampling Mode", @@ -52,6 +125,13 @@ "render_pipeline.module.ray_tracing.attribute.transparent_split_mode": "Transparent Split Mode", "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.deterministic": "Deterministic", "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.stochastic": "Stochastic", + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode": "Cloud Volume Mode", + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.native": "Native", + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.efficient_volume": "Efficient Volumetric", + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.realistic_volume": "Realistic Volumetric", + "render_pipeline.module.ray_tracing.attribute.water_surface_mode": "Water Surface Style", + "render_pipeline.module.ray_tracing.attribute.water_surface_mode.native_like": "Native-like", + "render_pipeline.module.ray_tracing.attribute.water_surface_mode.sparkling": "Sparkling", "render_pipeline.module.ray_tracing.attribute.direct_light_strength": "Direct Light Strength", "render_pipeline.module.ray_tracing.attribute.indirect_light_strength": "Indirect Light Strength", "render_pipeline.module.ray_tracing.attribute.basic_radiance": "Basic Radiance", @@ -166,5 +246,125 @@ "render_pipeline.mode.pipeline": "Pipeline", "render_pipeline.mode.preset": "Preset", "render_pipeline.true": "True", - "render_pipeline.false": "False" + "render_pipeline.false": "False", + "radiance.settings.title": "Radiance Settings", + "radiance.settings.search": "Search settings", + "radiance.settings.options_warning": "options.properties contains invalid values. Defaults were used; regenerate the file if needed.", + "radiance.settings.action.run": "Open", + "radiance.settings.page.home": "Home", + "radiance.settings.page.pipeline": "Pipeline", + "radiance.settings.page.dlss": "DLSS & HDR", + "radiance.settings.page.environment": "Environment", + "radiance.settings.page.sky": "Sky", + "radiance.settings.page.sun": "Sun", + "radiance.settings.page.moon": "Moon", + "radiance.settings.page.clouds": "Clouds", + "radiance.settings.page.water": "Water", + "radiance.settings.page.exposure": "Exposure", + "radiance.settings.page.post": "Post Processing", + "radiance.settings.page.materials": "Materials", + "radiance.settings.page.emissive": "Emissive", + "radiance.settings.page.lights": "Lights", + "radiance.settings.page.recent": "Recent Tweaks", + "radiance.settings.action.pipeline_editor": "Open pipeline editor", + "radiance.settings.action.pipeline_editor.desc": "Jump to the node editor for preset and pipeline mode.", + "radiance.settings.action.dlss_help": "Open DLSS runtime guide", + "radiance.settings.action.dlss_help.desc": "Show runtime files, module availability, and frame generation status.", + "radiance.settings.action.dlss_runtime.desc": "Inspect why DLSS or frame generation is unavailable on this runtime.", + "radiance.settings.action.export_hdr": "Export HDR PNG", + "radiance.settings.action.export_hdr.desc": "Write a 16-bit PNG from the HDR world output with Radiance scale tagging.", + "radiance.settings.action.material_toolkit": "Reload material toolkit files", + "radiance.settings.action.material_toolkit.desc": "Reload materials.yaml and lights.yaml after manual edits.", + "radiance.settings.action.material_toolkit_emissive.desc": "Reload emissive defaults and block overrides.", + "radiance.settings.action.light_registry": "Reload light registry", + "radiance.settings.action.light_registry.desc": "Reload custom light source definitions and helper defaults.", + "radiance.settings.option.quality_level.desc": "Switch Radiance quality presets without leaving the unified settings center.", + "radiance.settings.option.dlss_mode.desc": "Persist the preferred DLSS reconstruction mode for preset-driven pipelines.", + "radiance.settings.option.frame_generation.desc": "Toggle DLSS frame generation when the runtime and GPU support it.", + "radiance.settings.option.output_scale_2x.desc": "Keep more internal detail for presentation and post processing.", + "radiance.settings.option.simplified_indirect.desc": "Trade some indirect-light quality for a lighter runtime path.", + "radiance.settings.option.reflex.desc": "Enable Reflex low-latency mode when supported by the runtime.", + "radiance.settings.option.reflex_boost.desc": "Allow Reflex boost to bias clocks toward lower latency.", + "radiance.settings.option.vrr.desc": "Expose variable-refresh hints for supported displays.", + "radiance.settings.option.ray_bounces.desc": "Maximum recursive ray depth for reflections and GI.", + "radiance.settings.option.chunk_batch_size.desc": "Terrain meshing work per batch.", + "radiance.settings.option.chunk_total_batches.desc": "Total terrain meshing batches the runtime may schedule.", + "radiance.settings.option.hdr_output.desc": "Request HDR swapchain output when the display pipeline allows it.", + "radiance.settings.option.basic_radiance.desc": "Baseline world radiance injected before traced lighting accumulates.", + "radiance.settings.option.direct_light.desc": "Multiplier for direct sunlight and moonlight contribution.", + "radiance.settings.option.indirect_light.desc": "Multiplier for indirect bounced lighting.", + "radiance.settings.option.planet_radius.desc": "Planet radius used by the atmosphere model.", + "radiance.settings.option.atmosphere_top_radius.desc": "Top atmosphere radius for sky scattering.", + "radiance.settings.option.rayleigh_scale.desc": "Rayleigh falloff height controlling broad sky scattering.", + "radiance.settings.option.mie_scale.desc": "Mie falloff height controlling haze and forward scattering.", + "radiance.settings.option.mie_anisotropy.desc": "Forward-scattering bias for haze and thick atmosphere.", + "radiance.settings.option.rayleigh_scattering.desc": "Per-channel Rayleigh scattering coefficients.", + "radiance.settings.option.mie_scattering.desc": "Per-channel Mie scattering coefficients.", + "radiance.settings.option.minimum_view_cosine.desc": "Clamp the horizon angle used by the atmosphere and cloud underside lighting.", + "radiance.settings.option.sun_radiance.desc": "Sunlight radiance color and strength.", + "radiance.settings.option.moon_radiance.desc": "Moonlight radiance color and strength.", + "radiance.settings.option.cloud_mode.desc": "Use the vanilla cloud renderer mode inside the Radiance settings center.", + "radiance.settings.option.cloud_volume_mode.desc": "Choose between native clouds and two volumetric lighting models with different cost/quality trade-offs.", + "radiance.settings.option.cloud_horizon.desc": "Reuse the horizon-view clamp that most strongly affects cloud undersides.", + "radiance.settings.option.water_surface_mode.desc": "Switch between a flatter native-like water response and brighter sparkling highlights.", + "radiance.settings.option.water_reflection.desc": "Choose which materials participate in reflection rays useful for water/glass scenes.", + "radiance.settings.option.transparent_split.desc": "Control how transparent hits are separated for water and glass.", + "radiance.settings.option.auto_exposure.desc": "Enable adaptive exposure from the tone-mapping module.", + "radiance.settings.option.exposure_metering.desc": "Choose how auto exposure samples the scene.", + "radiance.settings.option.manual_exposure.desc": "Manual exposure value used when auto exposure is off.", + "radiance.settings.option.exposure_bias.desc": "Compensate exposure without changing the base metering mode.", + "radiance.settings.option.exposure_up_speed.desc": "How quickly the eye adapts to bright scenes.", + "radiance.settings.option.exposure_down_speed.desc": "How quickly the eye adapts to dark scenes.", + "radiance.settings.option.min_exposure.desc": "Lower clamp for automatic exposure.", + "radiance.settings.option.max_exposure.desc": "Upper clamp for automatic exposure.", + "radiance.settings.option.center_metering.desc": "Percent of the frame used by center-weighted metering.", + "radiance.settings.option.tonemap_method.desc": "Select the tonemapper used after HDR lighting resolves.", + "radiance.settings.option.saturation.desc": "Adjust output saturation after tone mapping.", + "radiance.settings.option.white_point.desc": "Set the output white point reference.", + "radiance.settings.option.clamp_output.desc": "Clamp the post-tonemap output to the active display range.", + "radiance.settings.option.star_count.desc": "Number of procedural stars generated by post render.", + "radiance.settings.option.star_min_size.desc": "Minimum star sprite size.", + "radiance.settings.option.star_max_size.desc": "Maximum star sprite size.", + "radiance.settings.option.star_radius.desc": "Radius used for the procedural star dome.", + "radiance.materials.auto_pbr": "Auto PBR generation", + "radiance.materials.default_metal": "Default metal preset", + "radiance.materials.generate_normals": "Generate fallback normals", + "radiance.materials.default_emission": "Default emissive strength", + "radiance.materials.default_flame": "Default flame colorant", + "radiance.settings.option.auto_pbr.desc": "Generate missing LabPBR helper textures from the current material rules.", + "radiance.settings.option.default_metal.desc": "Fallback metal preset used by the material helper when no block-specific rule exists.", + "radiance.settings.option.generate_normals.desc": "Generate simple height-derived normals when no normal map is supplied.", + "radiance.settings.option.default_emission.desc": "Default emissive strength for helper-generated materials and light hints.", + "radiance.settings.option.default_flame.desc": "Default spectral tint used by flame-like helper presets.", + "radiance.settings.summary.pipeline_mode": "Pipeline mode", + "radiance.settings.summary.active_preset": "Active preset", + "radiance.settings.summary.recent_hint": "Tip: use search or the Recent Tweaks page to jump back to the last settings you changed.", + "radiance.settings.toolkit.reloaded": "Radiance toolkit files reloaded.", + "radiance.settings.hdr.saved": "HDR PNG saved", + "radiance.settings.dlss.title": "DLSS Runtime Guide", + "radiance.settings.dlss.intro": "This page explains whether DLSS and frame generation are currently usable on this runtime.", + "radiance.settings.dlss.status.runtime_dir": "Runtime directory", + "radiance.settings.dlss.status.module": "DLSS module available", + "radiance.settings.dlss.status.dlss_dll": "nvngx_dlss.dll present", + "radiance.settings.dlss.status.framegen_dll": "nvngx_dlssg.dll present", + "radiance.settings.dlss.status.framegen_available": "Frame generation available", + "radiance.settings.dlss.status.reflex": "Reflex supported", + "radiance.settings.dlss.tip.1": "If the DLSS DLL is missing, bundle or copy the runtime into the Radiance folder before enabling DLSS features.", + "radiance.settings.dlss.tip.2": "If frame generation is unavailable, check GPU support, driver support, and whether the required runtime DLL is present.", + "radiance.settings.dlss.tip.3": "You can still use the preset/pipeline system without DLSS; Radiance will keep a non-DLSS preset path alive.", + "radiance.settings.numeric_input.invalid": "Please enter a valid numeric value.", + "radiance.materials.metal.none": "None", + "radiance.materials.metal.iron": "Iron", + "radiance.materials.metal.gold": "Gold", + "radiance.materials.metal.aluminium": "Aluminium", + "radiance.materials.metal.chrome": "Chrome", + "radiance.materials.metal.copper": "Copper", + "radiance.materials.metal.lead": "Lead", + "radiance.materials.metal.platinum": "Platinum", + "radiance.materials.metal.silver": "Silver", + "radiance.materials.flame.amber": "Amber", + "radiance.materials.flame.soul_blue": "Soul Blue", + "radiance.materials.flame.sunset": "Sunset", + "radiance.materials.flame.arcane": "Arcane", + "radiance.materials.flame.white_hot": "White Hot" } diff --git a/src/main/resources/assets/radiance/lang/zh_cn.json b/src/main/resources/assets/radiance/lang/zh_cn.json index 82bd425..c2400ba 100644 --- a/src/main/resources/assets/radiance/lang/zh_cn.json +++ b/src/main/resources/assets/radiance/lang/zh_cn.json @@ -15,6 +15,13 @@ "options.video.dlss_mode.quality": "质量模式", "options.video.dlss_mode.dlaa": "DLAA模式", "options.video.dlss_mode": "DLSS模式", + "options.video.quality_level": "画质等级", + "options.video.quality_level.fluent": "流畅", + "options.video.quality_level.performance": "性能", + "options.video.quality_level.balanced": "平衡", + "options.video.quality_level.quality": "高", + "options.video.quality_level.ultra": "超高", + "options.video.quality_level.extreme": "极端", "options.video.upscaler_type": "超分", "options.video.upscaler_type.native": "原生", "options.video.upscaler_type.fsr3": "FSR3", @@ -28,6 +35,8 @@ "options.video.denoiser_mode.svgf": "SVGF", "options.video.denoiser_mode.nrd": "NRD", "options.video.denoiser_mode.temporal": "时间累积", + "options.video.hdr_output": "HDR输出", + "options.video.dlss_frame_generation": "DLSS 插帧(NVIDIA/NGX,实验)", "options.video.ray_bounces": "光线反射次数", "options.video.chunk_building_batch_size": "单次提交构建的区块数", "options.video.chunk_building_total_batches": "总构建中的提交数", @@ -44,6 +53,65 @@ "render_pipeline.module.dlss.attribute.mode.quality": "质量模式", "render_pipeline.module.dlss.attribute.mode.dlaa": "DLAA", "render_pipeline.module.ray_tracing.name": "光线追踪", + "render_pipeline.module.ray_tracing.attribute.world_representation_mode": "世界表示方式", + "render_pipeline.module.ray_tracing.attribute.world_representation_mode.triangle_blas": "三角形 BLAS", + "render_pipeline.module.ray_tracing.attribute.world_representation_mode.chunk_aabb": "Chunk AABB", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode": "Chunk 内遍历方式", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.triangle_hit": "三角形命中", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.voxel_dda": "体素 DDA", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.brick": "Brick 遍历", + "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.macrocell": "宏块遍历", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout": "Chunk 数据布局", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.triangle_geometry": "三角形几何", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_bitmask": "占用位掩码", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_palette": "占用位掩码 + 材质调色板", + "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_palette_face_mask": "占用位掩码 + 调色板 + 面掩码", + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size": "Chunk 宏块尺寸", + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.disabled": "关闭", + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.size_4": "4³", + "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.size_8": "8³", + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode": "地形网格生成", + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.legacy_quads": "传统 Quads", + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.greedy_meshing": "Greedy Meshing", + "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge": "共面合并", + "render_pipeline.module.ray_tracing.attribute.greedy_merge_max_span": "Greedy 合并最大跨度", + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode": "BLAS 纳入策略", + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.all_geometry": "全部几何", + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_and_shadow": "仅不透明 + 投影几何", + "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_only": "仅不透明", + "render_pipeline.module.ray_tracing.attribute.glass_path_mode": "玻璃路径", + "render_pipeline.module.ray_tracing.attribute.foliage_path_mode": "植物路径", + "render_pipeline.module.ray_tracing.attribute.decoration_path_mode": "装饰物路径", + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas": "走 BLAS", + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.special_path": "走特殊路径", + "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.exclude": "排除", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode": "远景几何策略", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.exact_chunks": "全程精确 Chunk", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.simplified_shell": "简化壳层", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.clipmap": "Clipmap", + "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.shell_and_clipmap": "壳层 + Clipmap", + "render_pipeline.module.ray_tracing.attribute.far_field_start_distance_chunks": "远景起始距离(Chunk)", + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode": "远景材质简化", + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.full_pbr": "完整 PBR", + "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.flat_surface": "平面材质", + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode": "反射光线材质分级", + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.all_materials": "全部材质", + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.reflective_only": "仅反射材质", + "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.water_glass_metal": "仅水 / 玻璃 / 金属", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode": "漫反射 GI 模式", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.full_ray_tracing": "完整光追", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.probes": "Probe", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.radiance_cache": "Radiance Cache", + "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.low_cost_hybrid": "低成本混合", + "render_pipeline.module.ray_tracing.attribute.separate_entity_terrain_accel_structures": "实体 / 地形分离加速结构", + "render_pipeline.module.ray_tracing.attribute.terrain_update_interval_frames": "地形更新间隔(帧)", + "render_pipeline.module.ray_tracing.attribute.entity_update_interval_frames": "实体更新间隔(帧)", + "render_pipeline.module.ray_tracing.attribute.block_entity_update_interval_frames": "方块实体更新间隔(帧)", + "render_pipeline.module.ray_tracing.attribute.particle_update_interval_frames": "粒子更新间隔(帧)", + "render_pipeline.module.ray_tracing.attribute.particle_crit_glow": "暴击粒子发光", + "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow": "死亡烟雾发光", + "render_pipeline.module.ray_tracing.attribute.particle_crit_glow_strength": "暴击粒子发光强度", + "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow_strength": "死亡烟雾发光强度", "render_pipeline.module.ray_tracing.attribute.num_ray_bounces": "光线反射次数", "render_pipeline.module.ray_tracing.attribute.use_jitter": "使用抖动", "render_pipeline.module.ray_tracing.attribute.pbr_sampling_mode": "PBR采样模式", @@ -52,6 +120,13 @@ "render_pipeline.module.ray_tracing.attribute.transparent_split_mode": "透明分裂模式", "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.deterministic": "固定分裂", "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.stochastic": "随机散布", + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode": "云层体积模式", + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.native": "原生", + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.efficient_volume": "高效体积云", + "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.realistic_volume": "拟真体积云", + "render_pipeline.module.ray_tracing.attribute.water_surface_mode": "水面风格", + "render_pipeline.module.ray_tracing.attribute.water_surface_mode.native_like": "类原生", + "render_pipeline.module.ray_tracing.attribute.water_surface_mode.sparkling": "波光粼粼", "render_pipeline.module.ray_tracing.attribute.direct_light_strength": "直接光强度", "render_pipeline.module.ray_tracing.attribute.indirect_light_strength": "间接光强度", "render_pipeline.module.ray_tracing.attribute.basic_radiance": "基础辐亮度", @@ -166,5 +241,125 @@ "render_pipeline.mode.pipeline": "管线", "render_pipeline.mode.preset": "预设", "render_pipeline.true": "是", - "render_pipeline.false": "否" + "render_pipeline.false": "否", + "radiance.settings.title": "Radiance 设置中心", + "radiance.settings.search": "搜索设置项", + "radiance.settings.options_warning": "options.properties 中存在格式错误的值,已按默认值处理;如有需要请重新生成配置。", + "radiance.settings.action.run": "打开", + "radiance.settings.page.home": "首页", + "radiance.settings.page.pipeline": "管线", + "radiance.settings.page.dlss": "DLSS 与 HDR", + "radiance.settings.page.environment": "环境", + "radiance.settings.page.sky": "天空", + "radiance.settings.page.sun": "太阳", + "radiance.settings.page.moon": "月亮", + "radiance.settings.page.clouds": "云层", + "radiance.settings.page.water": "水体", + "radiance.settings.page.exposure": "曝光", + "radiance.settings.page.post": "后处理", + "radiance.settings.page.materials": "材质", + "radiance.settings.page.emissive": "发光", + "radiance.settings.page.lights": "光源", + "radiance.settings.page.recent": "最近调整", + "radiance.settings.action.pipeline_editor": "打开管线编辑器", + "radiance.settings.action.pipeline_editor.desc": "跳转到节点式编辑器,继续处理 preset / pipeline 双模式。", + "radiance.settings.action.dlss_help": "打开 DLSS 运行时说明", + "radiance.settings.action.dlss_help.desc": "查看运行时文件、模块可用性以及帧生成状态。", + "radiance.settings.action.dlss_runtime.desc": "检查当前环境下 DLSS 或帧生成不可用的原因。", + "radiance.settings.action.export_hdr": "导出 HDR PNG", + "radiance.settings.action.export_hdr.desc": "从 HDR 世界输出写出 16 位 PNG,并附带 Radiance 缩放信息。", + "radiance.settings.action.material_toolkit": "重载材质工具链配置", + "radiance.settings.action.material_toolkit.desc": "手动修改 materials.yaml / lights.yaml 后重新载入。", + "radiance.settings.action.material_toolkit_emissive.desc": "重新载入发光默认值和方块覆盖项。", + "radiance.settings.action.light_registry": "重载光源注册表", + "radiance.settings.action.light_registry.desc": "重新载入自定义光源定义与辅助默认项。", + "radiance.settings.option.quality_level.desc": "在统一设置中心内切换 Radiance 质量预设。", + "radiance.settings.option.dlss_mode.desc": "为预设驱动的管线保存首选 DLSS 重建模式。", + "radiance.settings.option.frame_generation.desc": "在运行时与 GPU 支持时启用 DLSS 帧生成。", + "radiance.settings.option.output_scale_2x.desc": "为展示与后处理保留更高的内部细节。", + "radiance.settings.option.simplified_indirect.desc": "用较轻的路径换取部分间接光质量。", + "radiance.settings.option.reflex.desc": "在运行时支持时启用 Reflex 低延迟模式。", + "radiance.settings.option.reflex_boost.desc": "允许 Reflex Boost 进一步偏向低延迟。", + "radiance.settings.option.vrr.desc": "为支持的显示器暴露可变刷新率提示。", + "radiance.settings.option.ray_bounces.desc": "控制反射与 GI 的最大递归光线深度。", + "radiance.settings.option.chunk_batch_size.desc": "每批地形网格构建工作量。", + "radiance.settings.option.chunk_total_batches.desc": "运行时可调度的地形网格总批次数。", + "radiance.settings.option.hdr_output.desc": "在显示链路允许时请求 HDR 交换链输出。", + "radiance.settings.option.basic_radiance.desc": "在追踪光照累积前注入的基础世界亮度。", + "radiance.settings.option.direct_light.desc": "直射太阳/月光贡献倍率。", + "radiance.settings.option.indirect_light.desc": "间接反弹光贡献倍率。", + "radiance.settings.option.planet_radius.desc": "大气模型使用的行星半径。", + "radiance.settings.option.atmosphere_top_radius.desc": "天空散射的大气顶层半径。", + "radiance.settings.option.rayleigh_scale.desc": "控制宽范围天空散射的 Rayleigh 高度衰减。", + "radiance.settings.option.mie_scale.desc": "控制雾气与前向散射的 Mie 高度衰减。", + "radiance.settings.option.mie_anisotropy.desc": "控制雾气前向散射偏向。", + "radiance.settings.option.rayleigh_scattering.desc": "Rayleigh 散射的 RGB 系数。", + "radiance.settings.option.mie_scattering.desc": "Mie 散射的 RGB 系数。", + "radiance.settings.option.minimum_view_cosine.desc": "限制地平线角度,影响大气与云底部照明。", + "radiance.settings.option.sun_radiance.desc": "太阳光亮度颜色与强度。", + "radiance.settings.option.moon_radiance.desc": "月光亮度颜色与强度。", + "radiance.settings.option.cloud_mode.desc": "在 Radiance 设置中心内直接控制原版云渲染模式。", + "radiance.settings.option.cloud_volume_mode.desc": "在原生云、高效体积云与拟真体积云之间切换,按性能与观感取舍。", + "radiance.settings.option.cloud_horizon.desc": "复用对云底部影响最明显的地平线视角限制。", + "radiance.settings.option.water_surface_mode.desc": "在更接近原版的平稳水面和更强调高光闪烁的波光水面之间切换。", + "radiance.settings.option.water_reflection.desc": "选择哪些材质参与对水面/玻璃更关键的反射光线。", + "radiance.settings.option.transparent_split.desc": "控制水体与玻璃等透明命中的拆分方式。", + "radiance.settings.option.auto_exposure.desc": "启用 tone mapping 模块的自适应曝光。", + "radiance.settings.option.exposure_metering.desc": "选择自动曝光如何采样场景。", + "radiance.settings.option.manual_exposure.desc": "关闭自动曝光时使用的手动曝光值。", + "radiance.settings.option.exposure_bias.desc": "在不改变测光模式的前提下补偿曝光。", + "radiance.settings.option.exposure_up_speed.desc": "眼睛适应亮场景的速度。", + "radiance.settings.option.exposure_down_speed.desc": "眼睛适应暗场景的速度。", + "radiance.settings.option.min_exposure.desc": "自动曝光下限。", + "radiance.settings.option.max_exposure.desc": "自动曝光上限。", + "radiance.settings.option.center_metering.desc": "中心加权测光使用的画面百分比。", + "radiance.settings.option.tonemap_method.desc": "选择 HDR 光照解析后的色调映射器。", + "radiance.settings.option.saturation.desc": "在色调映射后调整输出饱和度。", + "radiance.settings.option.white_point.desc": "设置输出白点参考值。", + "radiance.settings.option.clamp_output.desc": "将后处理输出钳制到当前显示范围。", + "radiance.settings.option.star_count.desc": "后处理模块生成的程序化星星数量。", + "radiance.settings.option.star_min_size.desc": "星点最小尺寸。", + "radiance.settings.option.star_max_size.desc": "星点最大尺寸。", + "radiance.settings.option.star_radius.desc": "程序化星空穹顶半径。", + "radiance.materials.auto_pbr": "自动生成 PBR 辅助贴图", + "radiance.materials.default_metal": "默认金属预设", + "radiance.materials.generate_normals": "生成兜底法线图", + "radiance.materials.default_emission": "默认发光强度", + "radiance.materials.default_flame": "默认火焰色调", + "radiance.settings.option.auto_pbr.desc": "根据当前材质规则为缺失的 LabPBR 辅助贴图生成兜底结果。", + "radiance.settings.option.default_metal.desc": "没有方块专属规则时使用的默认金属预设。", + "radiance.settings.option.generate_normals.desc": "没有法线图时根据高度变化生成简易法线。", + "radiance.settings.option.default_emission.desc": "用于辅助生成材质和光照提示的默认发光强度。", + "radiance.settings.option.default_flame.desc": "火焰类辅助预设使用的默认光谱色调。", + "radiance.settings.summary.pipeline_mode": "当前管线模式", + "radiance.settings.summary.active_preset": "当前预设", + "radiance.settings.summary.recent_hint": "提示:可以用搜索或“最近调整”快速回到刚改过的设置。", + "radiance.settings.toolkit.reloaded": "Radiance 工具链配置已重新载入。", + "radiance.settings.hdr.saved": "HDR PNG 已保存", + "radiance.settings.dlss.title": "DLSS 运行时说明", + "radiance.settings.dlss.intro": "这里会说明当前运行时下 DLSS 与帧生成是否可用。", + "radiance.settings.dlss.status.runtime_dir": "运行时目录", + "radiance.settings.dlss.status.module": "DLSS 模块可用", + "radiance.settings.dlss.status.dlss_dll": "已存在 nvngx_dlss.dll", + "radiance.settings.dlss.status.framegen_dll": "已存在 nvngx_dlssg.dll", + "radiance.settings.dlss.status.framegen_available": "帧生成可用", + "radiance.settings.dlss.status.reflex": "Reflex 支持", + "radiance.settings.dlss.tip.1": "如果缺少 DLSS DLL,请先把运行时文件放到 Radiance 目录中,再启用相关功能。", + "radiance.settings.dlss.tip.2": "如果帧生成不可用,请检查 GPU 支持、驱动支持以及 DLL 是否完整。", + "radiance.settings.dlss.tip.3": "即使没有 DLSS,Radiance 也会保留非 DLSS 的 preset / pipeline 主线。", + "radiance.settings.numeric_input.invalid": "请输入有效的数值。", + "radiance.materials.metal.none": "无", + "radiance.materials.metal.iron": "铁", + "radiance.materials.metal.gold": "金", + "radiance.materials.metal.aluminium": "铝", + "radiance.materials.metal.chrome": "铬", + "radiance.materials.metal.copper": "铜", + "radiance.materials.metal.lead": "铅", + "radiance.materials.metal.platinum": "铂", + "radiance.materials.metal.silver": "银", + "radiance.materials.flame.amber": "琥珀", + "radiance.materials.flame.soul_blue": "灵魂蓝", + "radiance.materials.flame.sunset": "晚霞", + "radiance.materials.flame.arcane": "奥术紫", + "radiance.materials.flame.white_hot": "白热" } diff --git a/src/main/resources/core.dll b/src/main/resources/core.dll new file mode 100644 index 0000000..c867e32 Binary files /dev/null and b/src/main/resources/core.dll differ diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 7f84e28..7a1ce50 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -6,7 +6,8 @@ "description": "A Minecraft mod that uses modern Vulkan C++ as backend renderer.", "authors": [ "LJIONG", - "Interstellarss" + "Interstellarss", + "Felix3322" ], "contact": { "homepage": "https://www.minecraft-radiance.com/", diff --git a/src/main/resources/modules/fsr_upscaler.yaml b/src/main/resources/modules/fsr_upscaler.yaml index 4970e89..c97e1be 100644 --- a/src/main/resources/modules/fsr_upscaler.yaml +++ b/src/main/resources/modules/fsr_upscaler.yaml @@ -19,7 +19,10 @@ attributeConfigs: value: "render_pipeline.true" - name: "render_pipeline.module.fsr_upscaler.attribute.quality_mode" type: "enum:render_pipeline.module.fsr_upscaler.attribute.quality_mode.native-render_pipeline.module.fsr_upscaler.attribute.quality_mode.quality-render_pipeline.module.fsr_upscaler.attribute.quality_mode.balanced-render_pipeline.module.fsr_upscaler.attribute.quality_mode.performance-render_pipeline.module.fsr_upscaler.attribute.quality_mode.ultra" - value: "render_pipeline.module.fsr_upscaler.attribute.quality_mode.quality" + value: "render_pipeline.module.fsr_upscaler.attribute.quality_mode.balanced" - name: "render_pipeline.module.fsr_upscaler.attribute.sharpness" type: "float_range:0.0-1.0" value: "0.7" + - name: "render_pipeline.module.fsr_upscaler.attribute.pre_exposure" + type: "float_range:0.0-16.0" + value: "1.0" diff --git a/src/main/resources/modules/post_render.yaml b/src/main/resources/modules/post_render.yaml index ab0f28c..6df5028 100644 --- a/src/main/resources/modules/post_render.yaml +++ b/src/main/resources/modules/post_render.yaml @@ -1,12 +1,12 @@ name: "render_pipeline.module.post_render.name" inputImageConfigs: - name: "ldr_input" - format: "R8G8B8A8_UNORM" + format: "R16G16B16A16_SFLOAT" - name: "first_hit_depth" format: "R16_SFLOAT" outputImageConfigs: - name: "post_rendered" - format: "R8G8B8A8_UNORM" + format: "R16G16B16A16_SFLOAT" attributeConfigs: - name: "render_pipeline.module.post_render.attribute.star_count" type: "int_range:1-20000" diff --git a/src/main/resources/modules/ray_tracing.yaml b/src/main/resources/modules/ray_tracing.yaml index 39d17fc..5baa14f 100644 --- a/src/main/resources/modules/ray_tracing.yaml +++ b/src/main/resources/modules/ray_tracing.yaml @@ -35,15 +35,87 @@ attributeConfigs: - name: "render_pipeline.module.ray_tracing.attribute.shader_pack_path" type: "string" value: "" + - name: "render_pipeline.module.ray_tracing.attribute.world_representation_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.world_representation_mode.triangle_blas-render_pipeline.module.ray_tracing.attribute.world_representation_mode.chunk_aabb" + value: "render_pipeline.module.ray_tracing.attribute.world_representation_mode.triangle_blas" + - name: "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.triangle_hit-render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.voxel_dda-render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.brick-render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.macrocell" + value: "render_pipeline.module.ray_tracing.attribute.chunk_traversal_mode.triangle_hit" + - name: "render_pipeline.module.ray_tracing.attribute.chunk_data_layout" + type: "enum:render_pipeline.module.ray_tracing.attribute.chunk_data_layout.triangle_geometry-render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_bitmask-render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_palette-render_pipeline.module.ray_tracing.attribute.chunk_data_layout.occupancy_palette_face_mask" + value: "render_pipeline.module.ray_tracing.attribute.chunk_data_layout.triangle_geometry" + - name: "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size" + type: "enum:render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.disabled-render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.size_4-render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.size_8" + value: "render_pipeline.module.ray_tracing.attribute.chunk_macrocell_size.disabled" + - name: "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.legacy_quads-render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.greedy_meshing-render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.coplanar_merge" + value: "render_pipeline.module.ray_tracing.attribute.terrain_meshing_mode.legacy_quads" + - name: "render_pipeline.module.ray_tracing.attribute.greedy_merge_max_span" + type: "int_range:1-64" + value: "16" + - name: "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.all_geometry-render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_and_shadow-render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_only" + value: "render_pipeline.module.ray_tracing.attribute.blas_inclusion_mode.opaque_and_shadow" + - name: "render_pipeline.module.ray_tracing.attribute.glass_path_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas-render_pipeline.module.ray_tracing.attribute.geometry_path_mode.special_path-render_pipeline.module.ray_tracing.attribute.geometry_path_mode.exclude" + value: "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas" + - name: "render_pipeline.module.ray_tracing.attribute.foliage_path_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas-render_pipeline.module.ray_tracing.attribute.geometry_path_mode.special_path-render_pipeline.module.ray_tracing.attribute.geometry_path_mode.exclude" + value: "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas" + - name: "render_pipeline.module.ray_tracing.attribute.decoration_path_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas-render_pipeline.module.ray_tracing.attribute.geometry_path_mode.special_path-render_pipeline.module.ray_tracing.attribute.geometry_path_mode.exclude" + value: "render_pipeline.module.ray_tracing.attribute.geometry_path_mode.blas" + - name: "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.exact_chunks-render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.simplified_shell-render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.clipmap-render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.shell_and_clipmap" + value: "render_pipeline.module.ray_tracing.attribute.far_field_geometry_mode.simplified_shell" + - name: "render_pipeline.module.ray_tracing.attribute.far_field_start_distance_chunks" + type: "int_range:2-256" + value: "28" + - name: "render_pipeline.module.ray_tracing.attribute.far_field_material_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.far_field_material_mode.full_pbr-render_pipeline.module.ray_tracing.attribute.far_field_material_mode.flat_surface" + value: "render_pipeline.module.ray_tracing.attribute.far_field_material_mode.flat_surface" + - name: "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.all_materials-render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.reflective_only-render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.water_glass_metal" + value: "render_pipeline.module.ray_tracing.attribute.reflection_ray_material_mode.water_glass_metal" + - name: "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.full_ray_tracing-render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.probes-render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.radiance_cache-render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.low_cost_hybrid" + value: "render_pipeline.module.ray_tracing.attribute.diffuse_gi_mode.low_cost_hybrid" + - name: "render_pipeline.module.ray_tracing.attribute.separate_entity_terrain_accel_structures" + type: "bool" + value: "render_pipeline.true" + - name: "render_pipeline.module.ray_tracing.attribute.terrain_update_interval_frames" + type: "int_range:1-64" + value: "6" + - name: "render_pipeline.module.ray_tracing.attribute.entity_update_interval_frames" + type: "int_range:1-64" + value: "1" + - name: "render_pipeline.module.ray_tracing.attribute.block_entity_update_interval_frames" + type: "int_range:1-64" + value: "1" + - name: "render_pipeline.module.ray_tracing.attribute.particle_update_interval_frames" + type: "int_range:1-64" + value: "1" + - name: "render_pipeline.module.ray_tracing.attribute.particle_crit_glow" + type: "bool" + value: "render_pipeline.true" + - name: "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow" + type: "bool" + value: "render_pipeline.true" + - name: "render_pipeline.module.ray_tracing.attribute.particle_crit_glow_strength" + type: "float_range:0.0-4.0" + value: "0.55" + - name: "render_pipeline.module.ray_tracing.attribute.particle_death_smoke_glow_strength" + type: "float_range:0.0-4.0" + value: "0.32" - name: "render_pipeline.module.ray_tracing.attribute.num_ray_bounces" type: "int_range:1-128" - value: "4" + value: "2" - name: "render_pipeline.module.ray_tracing.attribute.use_jitter" type: "bool" value: "render_pipeline.true" - name: "render_pipeline.module.ray_tracing.attribute.pbr_sampling_mode" type: "enum:render_pipeline.module.ray_tracing.attribute.pbr_sampling.nearest-render_pipeline.module.ray_tracing.attribute.pbr_sampling.bilinear" - value: "render_pipeline.module.ray_tracing.attribute.pbr_sampling.nearest" + value: "render_pipeline.module.ray_tracing.attribute.pbr_sampling.bilinear" - name: "render_pipeline.module.ray_tracing.attribute.transparent_split_mode" type: "enum:render_pipeline.module.ray_tracing.attribute.transparent_split_mode.deterministic-render_pipeline.module.ray_tracing.attribute.transparent_split_mode.stochastic" value: "render_pipeline.module.ray_tracing.attribute.transparent_split_mode.deterministic" @@ -80,6 +152,12 @@ attributeConfigs: - name: "render_pipeline.module.ray_tracing.attribute.minimum_view_cosine" type: "float_range:-1.0-1.0" value: "0.02" + - name: "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.native-render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.efficient_volume-render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.realistic_volume" + value: "render_pipeline.module.ray_tracing.attribute.cloud_volume_mode.efficient_volume" + - name: "render_pipeline.module.ray_tracing.attribute.water_surface_mode" + type: "enum:render_pipeline.module.ray_tracing.attribute.water_surface_mode.native_like-render_pipeline.module.ray_tracing.attribute.water_surface_mode.sparkling" + value: "render_pipeline.module.ray_tracing.attribute.water_surface_mode.sparkling" - name: "render_pipeline.module.ray_tracing.attribute.sun_radiance" type: "vec3" value: "8.0,8.0,8.0" diff --git a/src/main/resources/modules/tone_mapping.yaml b/src/main/resources/modules/tone_mapping.yaml index b547aa2..68534e0 100644 --- a/src/main/resources/modules/tone_mapping.yaml +++ b/src/main/resources/modules/tone_mapping.yaml @@ -4,7 +4,7 @@ inputImageConfigs: format: "R16G16B16A16_SFLOAT" outputImageConfigs: - name: "mapped_output" - format: "R8G8B8A8_UNORM" + format: "R16G16B16A16_SFLOAT" attributeConfigs: - name: "render_pipeline.module.tone_mapping.attribute.method" type: "enum:render_pipeline.module.tone_mapping.attribute.method.pbr_neutral-render_pipeline.module.tone_mapping.attribute.method.reinhard-render_pipeline.module.tone_mapping.attribute.method.reinhard_white_point-render_pipeline.module.tone_mapping.attribute.method.aces-render_pipeline.module.tone_mapping.attribute.method.aces_white_point-render_pipeline.module.tone_mapping.attribute.method.uncharted2" diff --git a/src/main/resources/modules/xess_sr.yaml b/src/main/resources/modules/xess_sr.yaml index a2454f4..462121a 100644 --- a/src/main/resources/modules/xess_sr.yaml +++ b/src/main/resources/modules/xess_sr.yaml @@ -19,7 +19,7 @@ attributeConfigs: value: "render_pipeline.true" - name: "render_pipeline.module.xess_sr.attribute.quality_mode" type: "enum:render_pipeline.module.xess_sr.attribute.quality_mode.native-render_pipeline.module.xess_sr.attribute.quality_mode.ultra_quality_plus-render_pipeline.module.xess_sr.attribute.quality_mode.ultra_quality-render_pipeline.module.xess_sr.attribute.quality_mode.quality-render_pipeline.module.xess_sr.attribute.quality_mode.balanced-render_pipeline.module.xess_sr.attribute.quality_mode.performance-render_pipeline.module.xess_sr.attribute.quality_mode.ultra_performance" - value: "render_pipeline.module.xess_sr.attribute.quality_mode.quality" + value: "render_pipeline.module.xess_sr.attribute.quality_mode.balanced" - name: "render_pipeline.module.xess_sr.attribute.pre_exposure" type: "float_range:0.0-16.0" value: "1.0" diff --git a/src/main/resources/nvngx_dlss.dll b/src/main/resources/nvngx_dlss.dll new file mode 100644 index 0000000..6004172 Binary files /dev/null and b/src/main/resources/nvngx_dlss.dll differ diff --git a/src/main/resources/nvngx_dlssd.dll b/src/main/resources/nvngx_dlssd.dll new file mode 100644 index 0000000..e81d7ff Binary files /dev/null and b/src/main/resources/nvngx_dlssd.dll differ diff --git a/src/main/resources/nvngx_dlssg.dll b/src/main/resources/nvngx_dlssg.dll new file mode 100644 index 0000000..9a13fcf Binary files /dev/null and b/src/main/resources/nvngx_dlssg.dll differ diff --git a/src/main/resources/radiance.mixins.json b/src/main/resources/radiance.mixins.json index 2df7bd7..fc76295 100644 --- a/src/main/resources/radiance.mixins.json +++ b/src/main/resources/radiance.mixins.json @@ -39,6 +39,7 @@ "vulkan_render_integration.ChunkBuilderMixins", "vulkan_render_integration.ClientChunkManagerMixins", "vulkan_render_integration.CloudRendererMixins", + "vulkan_render_integration.CrashReportMixins", "vulkan_render_integration.DirectoryAtlasSourceMixins", "vulkan_render_integration.DrawContextMixins", "vulkan_render_integration.EntityRenderDispatcherMixins", @@ -50,6 +51,7 @@ "vulkan_render_integration.ItemRendererMixins", "vulkan_render_integration.LightmapTextureManagerMixins", "vulkan_render_integration.LightningEntityRendererMixins", + "vulkan_render_integration.MainMixins", "vulkan_render_integration.MinecraftClientMixins", "vulkan_render_integration.MipmapHelperMixins", "vulkan_render_integration.NativeImageMixins", diff --git a/src/main/resources/shaders/world/ray_tracing/internal.zip b/src/main/resources/shaders/world/ray_tracing/internal.zip new file mode 100644 index 0000000..5bf5635 Binary files /dev/null and b/src/main/resources/shaders/world/ray_tracing/internal.zip differ diff --git a/src/main/resources/sl.common.dll b/src/main/resources/sl.common.dll new file mode 100644 index 0000000..f19d55f Binary files /dev/null and b/src/main/resources/sl.common.dll differ diff --git a/src/main/resources/sl.interposer.dll b/src/main/resources/sl.interposer.dll new file mode 100644 index 0000000..db80769 Binary files /dev/null and b/src/main/resources/sl.interposer.dll differ diff --git a/src/main/resources/sl.pcl.dll b/src/main/resources/sl.pcl.dll new file mode 100644 index 0000000..433160a Binary files /dev/null and b/src/main/resources/sl.pcl.dll differ diff --git a/src/main/resources/sl.reflex.dll b/src/main/resources/sl.reflex.dll new file mode 100644 index 0000000..94caa81 Binary files /dev/null and b/src/main/resources/sl.reflex.dll differ