Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/java/rs117/hd/HdPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
import rs117.hd.renderer.zone.SceneManager;
import rs117.hd.renderer.zone.ZoneRenderer;
import rs117.hd.scene.AreaManager;
import rs117.hd.scene.CustomSkyboxManager;
import rs117.hd.scene.EnvironmentManager;
import rs117.hd.scene.FishingSpotReplacer;
import rs117.hd.scene.GamevalManager;
Expand Down Expand Up @@ -217,6 +218,7 @@ public class HdPlugin extends Plugin {
// Manually instantiate singletons and lazily inject them to avoid circular dependencies
private static final List<Class<?>> LAZY_SINGLETONS = List.of(
AreaManager.class,
CustomSkyboxManager.class,
EnvironmentManager.class,
GamevalManager.class,
GroundMaterialManager.class,
Expand Down Expand Up @@ -263,6 +265,9 @@ public class HdPlugin extends Plugin {
@Inject
private EnvironmentManager environmentManager;

@Inject
private CustomSkyboxManager customSkyboxManager;

@Inject
private TextureManager textureManager;

Expand Down Expand Up @@ -712,6 +717,7 @@ protected void startUp() {
modelOverrideManager.startUp();
lightManager.startUp();
environmentManager.startUp();
customSkyboxManager.startUp();
fishingSpotReplacer.startUp();
gammaCalibrationOverlay.initialize();
npcDisplacementCache.initialize();
Expand Down Expand Up @@ -781,6 +787,7 @@ protected void shutDown() {
modelOverrideManager.shutDown();
lightManager.shutDown();
environmentManager.shutDown();
customSkyboxManager.shutDown();
fishingSpotReplacer.shutDown();
areaManager.shutDown();
gamevalManager.shutDown();
Expand Down
49 changes: 46 additions & 3 deletions src/main/java/rs117/hd/HdPluginConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import rs117.hd.config.TextureResolution;
import rs117.hd.config.UIScalingMode;
import rs117.hd.config.VanillaShadowMode;
import rs117.hd.config.SkyboxTheme;

import static rs117.hd.HdPlugin.MAX_DISTANCE;
import static rs117.hd.HdPlugin.MAX_FOG_DEPTH;
Expand Down Expand Up @@ -797,12 +798,54 @@ default boolean characterDisplacement() {
default boolean pohThemeEnvironments() { return true; }


/*====== Skybox settings ======*/

@ConfigSection(
name = "Skybox",
description = "Settings for high-resolution 3D panoramic skybox rendering templates.",
position = 4,
closedByDefault = true
)
String skyboxSettings = "skyboxSettings";

String KEY_SELECTED_SKYBOX_THEME = "selectedSkyboxTheme";
@ConfigItem(
keyName = KEY_SELECTED_SKYBOX_THEME,
name = "Skybox Style",
description = "Select the panoramic 3D skybox style.<br>Select 'None' to use default 117 skybox.",
position = 0,
section = skyboxSettings
)
default SkyboxTheme selectedSkyboxTheme()
{
return SkyboxTheme.NONE;
}
@ConfigItem(keyName = KEY_SELECTED_SKYBOX_THEME, hidden = true, name = "", description = "")
void selectedSkyboxTheme(SkyboxTheme theme);

String KEY_CUSTOM_SKYBOX_NAME = "customSkyboxName";
@ConfigItem(
keyName = KEY_CUSTOM_SKYBOX_NAME,
name = "Custom Skybox Name",
description = "When 'Skybox Style' is set to Custom, the skybox to use.<br>" +
"Drop an image into .runelite/117hd/custom-skyboxes/ and enter its filename here<br>" +
"(with or without its file extension), or add a manifest.json there for<br>" +
"cubemaps/multiple skies.<br>" +
"In-game commands: ::117hd skybox list, ::117hd skybox cycle, ::117hd skybox open.",
position = 1,
section = skyboxSettings
)
default String customSkyboxName() { return ""; }
@ConfigItem(keyName = KEY_CUSTOM_SKYBOX_NAME, hidden = true, name = "", description = "")
void customSkyboxName(String name);


/*====== Miscellaneous settings ======*/

@ConfigSection(
name = "Miscellaneous",
description = "Miscellaneous settings",
position = 4,
position = 5,
closedByDefault = true
)
String miscellaneousSettings = "miscellaneousSettings";
Expand Down Expand Up @@ -956,7 +999,7 @@ default boolean windowsHdrCorrection() {
@ConfigSection(
name = "Legacy",
description = "Legacy options. If you dislike a change, you might find an option to change it back here.",
position = 5,
position = 6,
closedByDefault = true
)
String legacySettings = "legacySettings";
Expand Down Expand Up @@ -1109,7 +1152,7 @@ default boolean legacyTzHaarReskin() {
@ConfigSection(
name = "Experimental",
description = "Experimental features - if you're experiencing issues you should consider disabling these.",
position = 6,
position = 7,
closedByDefault = true
)
String experimentalSettings = "experimentalSettings";
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/rs117/hd/config/SkyboxTheme.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package rs117.hd.config;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum SkyboxTheme {
NONE("None"),
CUSTOM("Custom");

private final String name;

@Override
public String toString() {
return name;
}
}
45 changes: 45 additions & 0 deletions src/main/java/rs117/hd/opengl/shader/SkyboxShaderProgram.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package rs117.hd.opengl.shader;

import java.io.IOException;

import static org.lwjgl.opengl.GL33C.*;

public abstract class SkyboxShaderProgram extends ShaderProgram {
protected final UniformTexture uniSkyboxTexture = addUniformTexture("skyboxTexture");
protected final UniformTexture uniSkyboxCubemap = addUniformTexture("skyboxCubemap");

protected boolean isCubemap;

SkyboxShaderProgram() {
super(t -> t
.add(GL_VERTEX_SHADER, "skybox_vert.glsl")
.add(GL_FRAGMENT_SHADER, "skybox_frag.glsl"));
}

@Override
public void compile(ShaderIncludes includes) throws ShaderException, IOException {
super.compile(includes.copy().define("IS_CUBEMAP", isCubemap));
}

public void setSkyboxTexture(int textureUnit) {
uniSkyboxTexture.set(textureUnit);
}

public void setSkyboxCubemap(int textureUnit) {
uniSkyboxCubemap.set(textureUnit);
}

public static class Equirect extends SkyboxShaderProgram {
public Equirect() {
isCubemap = false;
uniSkyboxCubemap.ignoreMissing = true;
}
}

public static class Cubemap extends SkyboxShaderProgram {
public Cubemap() {
isCubemap = true;
uniSkyboxTexture.ignoreMissing = true;
}
}
}
121 changes: 120 additions & 1 deletion src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,23 @@
import rs117.hd.config.ColorFilter;
import rs117.hd.config.DynamicLights;
import rs117.hd.config.ShadowMode;
import rs117.hd.config.SkyboxTheme;
import rs117.hd.opengl.shader.SceneShaderProgram;
import rs117.hd.opengl.shader.ShaderException;
import rs117.hd.opengl.shader.ShaderIncludes;
import rs117.hd.opengl.shader.ShadowShaderProgram;
import rs117.hd.opengl.shader.SkyboxShaderProgram;
import rs117.hd.opengl.uniforms.UBOLights;
import rs117.hd.opengl.uniforms.UBOWorldViews;
import rs117.hd.overlays.FrameTimer;
import rs117.hd.overlays.Timer;
import rs117.hd.renderer.Renderer;
import rs117.hd.scene.CustomSkyboxManager;
import rs117.hd.scene.EnvironmentManager;
import rs117.hd.scene.LightManager;
import rs117.hd.scene.ProceduralGenerator;
import rs117.hd.scene.SceneContext;
import rs117.hd.scene.TextureManager;
import rs117.hd.scene.lights.Light;
import rs117.hd.utils.Camera;
import rs117.hd.utils.ColorUtils;
Expand Down Expand Up @@ -91,6 +95,24 @@ public class ZoneRenderer implements Renderer {

private static int TEXTURE_UNIT_COUNT = HdPlugin.TEXTURE_UNIT_COUNT;
public static final int TEXTURE_UNIT_TEXTURED_FACES = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++;
public static final int TEXTURE_UNIT_SKYBOX = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++;
public static final int TEXTURE_UNIT_SKYBOX_CUBEMAP = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++;
private int customSkyboxTextureId;
private int customSkyboxCubemapTextureId;
private boolean loadedSkyboxIsCubemap;
private String loadedCustomSkyboxName;

@Inject
private CustomSkyboxManager customSkyboxManager;

@Inject
private TextureManager textureManager;

@Inject
private SkyboxShaderProgram.Equirect skyboxEquirectProgram;

@Inject
private SkyboxShaderProgram.Cubemap skyboxCubemapProgram;

private static int UNIFORM_BLOCK_COUNT = HdPlugin.UNIFORM_BLOCK_COUNT;
public static final int UNIFORM_BLOCK_WORLD_VIEWS = UNIFORM_BLOCK_COUNT++;
Expand Down Expand Up @@ -233,13 +255,18 @@ public void initializeShaders(ShaderIncludes includes) throws ShaderException, I
sceneProgram.compile(includes);
fastShadowProgram.compile(includes);
detailedShadowProgram.compile(includes);
skyboxEquirectProgram.compile(includes);
skyboxCubemapProgram.compile(includes);
// The actual skybox texture is loaded lazily in scenePass() once the configured theme is known
}

@Override
public void destroyShaders() {
sceneProgram.destroy();
fastShadowProgram.destroy();
detailedShadowProgram.destroy();
skyboxEquirectProgram.destroy();
skyboxCubemapProgram.destroy();
}

private void initializeBuffers() {
Expand Down Expand Up @@ -774,6 +801,62 @@ private void scenePass() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
frameTimer.end(Timer.CLEAR_SCENE);

// --- DYNAMIC SKYBOX THEME CONFIGURATION TRACKER ---
SkyboxTheme currentTheme = config.selectedSkyboxTheme();
boolean hasActiveSkyboxTheme = currentTheme == SkyboxTheme.CUSTOM;
String currentCustomSkyboxName = config.customSkyboxName();

if (hasActiveSkyboxTheme && environmentManager.isOverworld()) {
// INTERCEPT: Check if the user changed the custom skybox name mid-game
if (!currentCustomSkyboxName.equals(loadedCustomSkyboxName)) {
// Free the old texture handles from GPU memory if they exist
if (customSkyboxTextureId != 0) {
glDeleteTextures(customSkyboxTextureId);
customSkyboxTextureId = 0;
}
if (customSkyboxCubemapTextureId != 0) {
glDeleteTextures(customSkyboxCubemapTextureId);
customSkyboxCubemapTextureId = 0;
}
loadedSkyboxIsCubemap = false;

loadCustomSkybox(currentCustomSkyboxName);
loadedCustomSkyboxName = currentCustomSkyboxName;
}

// Only run drawing commands if the selected asset loaded successfully
if (customSkyboxTextureId != 0 || customSkyboxCubemapTextureId != 0) {
glDisable(GL_DEPTH_TEST);
glDepthMask(false);

// isCubemap is a compile-time #define, so equirect and cubemap skyboxes use
// separately-compiled shader variants rather than a per-pixel runtime branch.
// The camera ray itself is reconstructed in skybox_frag.glsl from the global
// UBO's viewMatrix/projectionMatrix, so no per-frame camera uniforms are needed.
if (loadedSkyboxIsCubemap) {
skyboxCubemapProgram.use();
glActiveTexture(TEXTURE_UNIT_SKYBOX_CUBEMAP);
glBindTexture(GL_TEXTURE_CUBE_MAP, customSkyboxCubemapTextureId);
skyboxCubemapProgram.setSkyboxCubemap(TEXTURE_UNIT_SKYBOX_CUBEMAP);
} else {
skyboxEquirectProgram.use();
glActiveTexture(TEXTURE_UNIT_SKYBOX);
glBindTexture(GL_TEXTURE_2D, customSkyboxTextureId);
skyboxEquirectProgram.setSkyboxTexture(TEXTURE_UNIT_SKYBOX);
}

glDrawArrays(GL_TRIANGLES, 0, 3);

glEnable(GL_DEPTH_TEST);
glDepthMask(true);
sceneProgram.use();
} else {
sceneProgram.use();
}
} else {
sceneProgram.use();
}

frameTimer.begin(Timer.RENDER_SCENE);

renderState.enable.set(GL_BLEND);
Expand Down Expand Up @@ -1218,4 +1301,40 @@ public void swapScene(Scene scene) {
plugin.stopPlugin();
}
}
}

/**
* Loads the custom skybox entry with the given name from {@link CustomSkyboxManager},
* dispatching to the equirectangular or cubemap upload path depending on the entry's declared type.
* Populates {@link #customSkyboxTextureId}, {@link #customSkyboxCubemapTextureId} and
* {@link #loadedSkyboxIsCubemap}. Leaves both texture ids at 0 if the entry can't be found or loaded.
*/
private void loadCustomSkybox(String name) {
var entry = customSkyboxManager.getEntry(name);
if (entry == null) {
if (!name.isEmpty())
log.warn("Custom skybox not found in manifest: {}", name);
return;
}

try {
switch (entry.type) {
case "cubemap":
customSkyboxCubemapTextureId = textureManager.createCubemapTexture(customSkyboxManager.loadCubemapFaceImages(entry));
loadedSkyboxIsCubemap = true;
break;
case "cubemap_cross":
customSkyboxCubemapTextureId = textureManager.createCubemapTexture(customSkyboxManager.loadCubemapCrossImage(entry));
loadedSkyboxIsCubemap = true;
break;
case "equirect":
customSkyboxTextureId = textureManager.createTexture2D(customSkyboxManager.loadEquirectImage(entry));
break;
default:
log.warn("Unknown custom skybox type '{}' for entry '{}'", entry.type, name);
}
} catch (Exception ex) {
log.warn("Failed to load custom skybox '{}'", name, ex);
}
}

}
8 changes: 8 additions & 0 deletions src/main/java/rs117/hd/scene/CustomSkyboxEntry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package rs117.hd.scene;

public class CustomSkyboxEntry {
public String name;
public String type; // "equirect" | "cubemap" | "cubemap_cross"
public String file; // equirect / cubemap_cross
public String[] faces; // cubemap, length 6: +X,-X,+Y,-Y,+Z,-Z
}
Loading