Skip to content

Developers

Alan D. Tse edited this page May 11, 2026 · 49 revisions

Community Shaders Base Package

This is the base package that loads all the custom shaders.

DLL

This is the main dll in the repo that enables the replacement of vanilla shaders with new shaders.

Can be found primarily in https://github.com/doodlum/skyrim-community-shaders/tree/main/src

Shaders

The default shaders will be found in https://github.com/doodlum/skyrim-community-shaders/tree/main/package/Shaders.

These default shaders should be overwritten by feature specific shaders.

Features

These are custom features that are loaded after the default CS install.

DLL

To add a new feature, the DLL needs to be modified. See prior examples of adding new features for the full list. https://github.com/doodlum/skyrim-community-shaders/pull/83

The main features should live here: https://github.com/doodlum/skyrim-community-shaders/tree/dev/src/Features

Virtual functions

Required functions:

  • GetName
    • Used in ImGui feature list and json entry per feature
  • GetShortName
    • Used for ini loading (No spaces usually)
  • SetupResources
    • Called once in startup
  • Reset
    • Called once per frame
  • DrawSettings
    • Used for rendering imgui
  • Draw
    • Normal rendering code here
  • Save
    • Serialize settings to json
  • RestoreDefaultSettings
    • Reset feature settings to default value

Functions that should be defined

  • Load
    • Deserialize settings from json, also need to call Feature::Load after loading

Functions that can be overridden

These are already default defined in Feature.h but can be overriden if you need to do something extra like defined below

  • GetShaderDefineName
    • used as an additional macro added when compiling shaders specified by HasShaderDefine, if the feature is loaded
  • HasShaderDefine
    • if a shader type returns true, then GetShaderDefineName macro is added to its compilation, if the feature is loaded
  • DrawDeferred
    • This one is not called yet, that is in the subsurface-scattering branch
  • DataLoaded
    • Called by SKSE kDataLoaded event
  • PostPostLoad
    • Called by SKSE kPostPostLoad event
  • ClearShaderCache
    • Called by imgui clear shader cache button
  • GetFeatureModLink
    • Specify link to feature, which will be presented to the user when feature is unloaded: example
  • DrawFailLoadMessage
    • Returns true/false depending on whether the stock failed to load message should be printed: example
  • DrawUnloadedUI
    • Called when feature is unloaded to add any content to the panel (you probably want to return false for DrawFailLoadMessage if using this): example

Shaders

Shaders are stored in https://github.com/doodlum/skyrim-community-shaders/tree/dev/features

Register Usage

Buffers (updated by ci)

Debugging

To debug CS you will need to be able to debug both the cpp dll and the hlsl shader files.

Remove DRM

  1. Save a copy of the original exe so you can replace it.
  2. Use Steamless to strip the SteamDRM from the Skyrim.exe. This is required for a debugger to attach.

Make sure to check Keep Bind Section in steamless, game will not boot without it.

Disable ASLR

To ensure addresses don't move, disable ASLR. This can be done with CFF Explorer. Optional Header -> DLL Characteristics -> DLL can move. Disable this.

image

Attach

Renderdoc

Renderdoc can be used to debug shaders. You can also try alandtse's fork. See extra features.

  1. Disable incompatible features:
    1. Skyrim Upscaler
    2. reshade
  2. Optional Enable Global Hooking. Make sure to Enable Global Hook which will grey out all settings. This also will prevent closing renderdoc until you disable the setting.
  3. Set up Launch Application so it will find the skse.exe (or Skyrim.exe if global hooking) when it launches.

image

  1. Launch Skyrim. You will know RenderDoc has connected because of the message in the top left of Skyrim Capturing D3D11. If it doesn't show, try toggling the global hook and launching again.

image

  1. Enable Developer Mode. Developer Mode can be enabled by setting the Advanced Settings -> Log Level to TRACE or DEBUG.

image

  1. Enable Advanced -> Extended Frame Annotations to help populate info in the rendering process.

image

  1. Clear Shader Cache and Disk Cache. This is necessary to save debug information in the shaders to access named buffers/hlsl in renderdoc.

  2. In game, press F12 to capture the scene.

  3. In Renderdoc, File -> Attach to Running Instance. Select Skyrim and Connect to App.

image

  1. Once attached, a new tab will appear. Double click any captures to load.

image

  1. You can verify you are seeing debug information by opening up the Pipeline tab and checking the Vertex or Pixel shaders. The resources should be named. In this case, we're getting water.hlsl data.

image

Agentic Debugging of Shaders with Renderdoc MCP

With Claude Code or other agentic AI, you can connect RenderDoc as a MCP server to help debug hlsl shaders.

  1. Download agentic-renderdoc. This is alandtse's fork, the upstream may be better. Follow it's installation steps.
  2. Setup Claude Code with the CS repo.
  3. Confirm you can see the MCP server:
claude mcp list
Checking MCP server health...
renderdoc: agentic-renderdoc  - ✓ Connected
  1. Capture the scene in renderdoc with the version of CS that is causing the issue.
  2. Make sure Claud knows about and can access the MCP. Just ask it to use the renderdoc MCP.
  3. Describe the problem to Claude to a reasonable degree to not waste tokens. Identify:
    1. What event ID (EID)? Find exactly which shader is drawing the bad item. E.g., scroll through the timeline to what is drawing it.
    2. What shader? Pixel/Compute etc. Check the Pipeline State. If you can identify the specific CS hlsl file, it'll be even more efficient.
    3. Identify the coordinates and the texture. E.g., (Rt0 kMain at 754, 587). In the Texture viewer, you can right click to save the information in the bottom of the window.

Debugging Individual Shaders in Game

It is possible to block individual shaders in game to find a faulty shader.

  1. Find the faulty mesh in game. Confirm the mesh is caused by CS by toggling CS using the Toggle Effects Key (default Numpad *). The faulty mesh should disappear. If it doesn't, it's a Vanilla bug. This is an example with CS disabled:

image

CS Enabled showing faulty mesh (note texture is missing in red circle):

image

  1. Enable Developer Mode. Developer Mode can be enabled by setting the Advanced Settings -> Log Level to TRACE or DEBUG.

image

  1. Hit the PageUp or PageDown to cycle all active shaders until the faulty mesh disappears. This should match the vanilla shader.

image

  1. [Optional] Stop blocking shaders by clicking on the Advanced Settings -> Stop Blocking Shaders button. This should also flush the log and also provide a noticeable log entry to identify the last shader blocked. This button only appears when shaders are being blocked.

image

  1. Review the CommunityShaders.log file. The blocked shader should be the last entry before Stopped blocking shaders (from step 4). In the example below, the ID/descriptor is 12000004 and it is a Lighting/Vertex shader, with the compile options of WETNESS_EFFECTS LIGHT_LIMIT_FIX COMPLEX_PARALLAX_MATERIALS DYNAMIC_CUBEMAPS LODLANDNOISE LODLANDSCAPE MODELSPACENORMALS SHADOWSPLITCOUNT=3. With this information, we can look at lighting.hlsl to figure out what hlsl is active.
[2023-11-20 19:09:59.451] [debug] [33112] [ShaderCache.cpp:1557] Blocking shader (6/93) Lighting:Vertex:WETNESS_EFFECTS LIGHT_LIMIT_FIX COMPLEX_PARALLAX_MATERIALS DYNAMIC_CUBEMAPS LODLANDNOISE LODLANDSCAPE MODELSPACENORMALS SHADOWSPLITCOUNT=3 
[2023-11-20 19:09:59.451] [debug] [33112] [ShaderCache.cpp:1209] Skipping blocked shader 12000004:Lighting:Vertex:WETNESS_EFFECTS LIGHT_LIMIT_FIX COMPLEX_PARALLAX_MATERIALS DYNAMIC_CUBEMAPS LODLANDNOISE LODLANDSCAPE MODELSPACENORMALS SHADOWSPLITCOUNT=3  total: 1
[2023-11-20 19:10:10.734] [debug] [33112] [ShaderCache.cpp:1568] Stopped blocking shaders

For comparison, if we block the corresponding Pixel shader, the coloring will be distorted instead which is revealed to be ID/descriptor 12000005 which is the Lighting:Pixel shader.

image

[2023-11-20 23:07:17.546] [debug] [70860] [ShaderCache.cpp:1537] Blocking shader (24/97) Lighting:Pixel:WETNESS_EFFECTS LIGHT_LIMIT_FIX COMPLEX_PARALLAX_MATERIALS DYNAMIC_CUBEMAPS LODLANDNOISE LODLANDSCAPE MODELSPACENORMALS SHADOWSPLITCOUNT=3 VC 
[2023-11-20 23:07:17.564] [debug] [74308] [ShaderCache.cpp:1243] Skipping blocked shader 12000005:Lighting:Pixel:WETNESS_EFFECTS LIGHT_LIMIT_FIX COMPLEX_PARALLAX_MATERIALS DYNAMIC_CUBEMAPS LODLANDNOISE LODLANDSCAPE MODELSPACENORMALS SHADOWSPLITCOUNT=3 VC  total: 1

Hotreload to quickly test builds

Please note the hotreload works for changes to hlsl/hlsli files. Changes to cpp code require a restart of the game.

With manual clearing of shader cache

  1. Make sure 'Enable Async' is on
  2. Disable 'Enable Disk Cache' (Probably obsolete, but does not hurt)
  3. Change hlsl/hlsli
  4. Click 'Clear Shader Cache'
  5. The game should recompile shaders now

With automatic clearing of shader cache

  1. Make sure 'Enable Async' is on
  2. Disable 'Enable Disk Cache' (Probably obsolete, but does not hurt)
  3. Enable File Watcher, this will add a watcher for hlsl files in the root folder and automatically clear shader cache for changed files
  4. Change hlsl file in root
  5. The game should recompile shaders now

Resources

Release Workflow

Pipeline Overview

Stage How triggered Notes
1. Semantic version Manual — Release: Semantic Version Bumps INIs, tags, creates draft release
2. Build artifacts Automatic — fires on the tag push from step 1 Requires RELEASE_PAT secret to be set
3. Publish release Manual — edit and publish draft on GitHub Review notes, confirm artifacts attached
4. Nexus dry run Automatic on publish — Release: Nexus Upload Trigger Always runs as dry_run: true; verifies versions, does not upload
5. Nexus upload Manual — Nexus: Upload Release with dry_run: false Run after reviewing dry-run output

Branch model

Branch Role Releases it produces
main Stable release channel vX.Y.Z
dev Integration / RC vX.Y.Z-rc.N prereleases
hotfix/X.Y.x Maintenance for older release lines vX.Y.Z on the X.Y channel

semantic-release infers behavior from the branch the workflow is dispatched on. There is no release_type input. hotfix/X.Y.x is only valid once main has shipped a release on a newer minor/major — to patch the current line, use the "Patch current line" flow below, not the hotfix workflow.

main and dev stay aligned by fast-forwards only — no rebases of shared branches, no back-merges, no force pushes. The Release: Semantic Version workflow takes care of both FF directions at promotion time.


Standard Release Process

When to use

Use the standard process for the next minor or major — new features that have accumulated on dev and you're ready to promote to stable.

Step-by-step

1. Merge all work to dev via normal PRs

All commits that should ship must be on dev. Semantic-release reads conventional commit messages (feat:, fix:, chore:, etc.) to determine the version bump:

  • feat: → minor bump (e.g. 1.5.x1.6.0)
  • fix: → patch bump (e.g. 1.5.01.5.1)
  • feat!: or BREAKING CHANGE: → major bump

chore:, docs:, style:, etc. produce no version bump on their own.

2. Cut a final RC for confirmation (recommended)

Go to Actions → Release: Semantic Version → Run workflow, branch dev, leave ff_target empty. This produces vX.Y.Z-rc.N and confirms the version stable will produce. Install and verify.

3. Promote dev → main

Copy the SHA of the verified RC (or dev's current HEAD). Go to Actions → Release: Semantic Version → Run workflow, branch main, paste the SHA into the ff_target input.

The workflow will:

  • Fast-forward main to that SHA (no PR, no merge commit)
  • Auto-bump any feature .ini versions whose shaders or code changed since the last stable tag
  • Run semantic-release, which determines the next stable version (e.g. v1.6.0)
  • Update CMakeLists.txt and commit chore(release): back to main
  • Push the new tag (v1.6.0) and create a draft GitHub release with auto-generated notes
  • Fast-forward dev to absorb the release commit so the two branches reconverge

If dev was pushed to during the promotion, the dev FF is skipped with a warning and a one-line manual-reconcile snippet appears in the run summary.

4. Wait for the build to complete

The tag push automatically triggers Release: Build Artifacts. Watch it under Actions — it will compile the DLL, validate shaders, and upload packaged .7z artifacts to the draft release.

If the build does not appear within a minute of the tag being created, see Build artifacts are missing.

5. Review and publish the draft release

Open the draft release on GitHub. Review the auto-generated release notes, edit as needed, then publish.

6. Nexus dry run fires automatically

Publishing triggers Release: Nexus Upload Trigger, which always runs as a dry run. It queries Nexus for each mod and prints a version table in the Actions summary — no files are uploaded. Review it to confirm the planned versions look correct.

7. Run the real Nexus upload

Go to Actions → Nexus: Upload Release → Run workflow. Enter the tag, set dry_run: false, and run.

The workflow uploads core and any features with a bumped .ini version. Already-present versions are skipped automatically, so re-running is always safe.


Patch Current Line

Use this when a fix is needed on the current stable line and you do not want to drag dev's unreleased features into the release.

1. Land the fix on dev first (normal PR flow)

This keeps dev current and gives semantic-release a fix: commit to use later when it sees dev again.

2. Cherry-pick to main

On the merged dev PR's page, scroll to the merge box and click "Cherry-pick changes". Pick main as the target. GitHub opens a new PR against main with the fix applied. Review and merge it.

(If the cherry-pick conflicts, GitHub creates the branch with conflict markers — resolve them in the web editor or locally and push.)

3. Dispatch the release

Go to Actions → Release: Semantic Version → Run workflow, branch main, leave ff_target empty. Semantic-release cuts vX.Y.(Z+1) from main.

4–7. Build, publish, Nexus — same as steps 4–7 of the standard process above.

The release commit on main and the next promotion's tail-FF reconcile cleanly; semantic-release's patch-id-dedup collapses the duplicate fix commit when dev next merges to main.


RC Releases

RC releases let you ship a pre-release for testing before committing to a stable version.

  • Go to Actions → Release: Semantic Version → Run workflow, branch dev, leave ff_target empty.
  • Semantic-release creates a pre-release tag like v1.6.0-rc.1, then v1.6.0-rc.2 on the next run, etc.
  • RC tags are pre-releases — Release: Nexus Upload Trigger skips them (only stable tags, those without -, are uploaded).
  • Release: Build Artifacts fires automatically on the RC tag push — same as stable releases.

Version escalation between RC and stable

Semantic-release computes the stable version by analyzing all commits since the last stable tag on main, not since the last RC tag. The RC tags only exist on the rc channel; when running stable on main, semantic-release operates on the default channel and treats the last stable tag as the baseline.

This means a post-RC commit can escalate the version only if its bump level exceeds what the RC series was already targeting:

RC was targeting Post-RC commit Stable result Escalation?
v1.6.0 (minor) feat: v1.6.0 No — already minor
v1.6.0 (minor) fix: v1.6.0 No
v1.5.1 (patch) feat: v1.6.0 Yes — escalates to minor
v1.6.0 (minor) feat!: v2.0.0 Yes — escalates to major
v1.5.1 (patch) fix: v1.5.1 No

Cutting another RC before going stable is the safe approach when in doubt. If a new commit could escalate the version, run RC again first — the new RC tag will reflect the escalated version, making the final stable version explicit and testable before you commit to it.

To promote an RC to stable, follow steps 2–7 of the Standard Release Process — dispatch on main with the verified RC's SHA in ff_target.


Troubleshooting

Semantic-release reports "no release needed"

Semantic-release found no feat: or fix: commits since the last tag. Either:

  • The new commits are all chore: / docs: / style: — these intentionally produce no release.
  • You are running on the wrong branch or against the wrong base tag.

If you need to force a patch release anyway, add an empty fix commit:

git commit --allow-empty -m "fix: trigger release"
git push origin dev

Then re-run the workflow.

Build artifacts are missing from the draft release

The build auto-triggers from the tag push via RELEASE_PAT. If it is missing, check:

  1. RELEASE_PAT is expired or missing — the most common cause. Regenerate the token, update the repo secret, and manually trigger Actions → Release: Build Artifacts on the tag to recover this release.
  2. The build ran but failed — check the Actions log. Common causes are shader validation errors or a flaky submodule checkout. Fix and re-run.
  3. Branch protection bypass not set — if the PAT owner is not in the bypass list, the chore(release): push fails and no tag is created, so no build triggers. Check the Release: Semantic Version run log.

Nexus upload fails with permission error

unex reports "You don't have permission to upload". The UNEX_NEXUSMODS_SESSION_COOKIE secret belongs to an account that does not own those mod pages. The session cookie must come from the Nexus account that owns each mod. Update the secret and re-run.

Nexus upload skips a mod with "version already exists"

Expected behaviour — check_existing: true prevents double-uploads. If you need to replace the file, do it manually through the Nexus web UI.

Semantic-release fails mid-run (dirty working tree or push rejected)

The feature version auto-bump step may have produced a conflict, or the bot account lacks push access to the branch being released. Check the Apply feature version bumps step log. If the bump commit was partially applied, reset the branch to the last good commit and re-run.

"The release X.Y.Z on branch hotfix/X.Y.x cannot be published as it is out of range"

Semantic-release's maintenance-branch contract: hotfix/X.Y.x is only valid once main has shipped a release on a newer minor or major. If main is still on the same line as the hotfix branch, the maintenance range is empty and semantic-release refuses to publish. Use Patch Current Line instead — cherry-pick the fix to main and release from main.

ff_target rejected with "not an ancestor"

The promotion mode validates that main is an ancestor of ff_target and ff_target is an ancestor of dev. If either check fails, main and dev have diverged — usually because something landed directly on main (a current-line patch) that hasn't been reconciled back to dev. Reconcile first: git fetch && git checkout dev && git merge --ff-only origin/main && git push. If dev no longer FF's main, you'll need to manually merge or resolve.

Draft release has wrong or missing release notes

Edit the draft release body directly on GitHub before publishing. The notes are generated from conventional commits; you can add context, screenshots, or upgrade instructions by hand.

RC artifacts are stale when promoting to stable

The stable build runs from the stable tag, not from an RC tag, so it always produces a fresh build. RC artifacts in GitHub releases are separate and are not reused.


Hotfix Release Process

Hotfixes are for older release lines — patching v1.5.x after main has already shipped v1.6.0 or beyond.

Precondition: main must have shipped a release on a newer minor or major than the hotfix line. Semantic-release's maintenance-branch contract requires the hotfix line to be strictly older than the project's stable branch; if main is still on the same line, semantic-release fails with The release X.Y.Z on branch hotfix/X.Y.x cannot be published as it is out of range.

To patch the current stable line, do not use the hotfix workflow. Use Patch Current Line instead — cherry-pick the fix from dev to main via GitHub's "Cherry-pick changes" button, merge, dispatch Release: Semantic Version on main.

Branch naming

Hotfix branches represent a maintenance line, not a single patch:

hotfix/1.5.x   ← correct (the 1.5 line)
hotfix/1.5.1   ← wrong (semantic-release won't match this)

Step-by-step

1. Land the fix on dev first (normal PR flow)

If the fix is also relevant to current dev/main, it lands there too via normal flow. Skip this step if the fix only applies to the older line.

2. Dispatch the hotfix candidate workflow

Go to Actions → Release: Hotfix Candidate → Run workflow. Inputs:

  • release_line: e.g. 1.5. Leave empty to auto-detect the latest stable tag.
  • scope: which commit types to cherry-pick from dev (fix-only is the default and safest).
  • commits: optional explicit list of SHAs to cherry-pick instead of the scope filter.
  • dry_run: true to plan only.

The workflow creates hotfix/X.Y.x from the latest stable tag on that line if it doesn't already exist, cherry-picks eligible commits, and opens a PR against hotfix/X.Y.x. Breaking changes are always skipped.

3. Install the prerelease and verify

PR checks build the candidate and publish a vX.Y.Z-prNNNN prerelease. Install it, verify the fix works, and check for regressions. If the candidate is bad, close the PR and re-dispatch (auto-supersedes prior candidates on the same line).

4. Merge the candidate PR

Standard PR merge against hotfix/X.Y.x.

5. Trigger the release

Go to Actions → Release: Semantic Version → Run workflow, select branch hotfix/X.Y.x, leave ff_target empty.

The workflow will:

  • Auto-bump any feature INI versions that changed on this branch
  • Run semantic-release, which sees the latest stable on the line as the baseline and produces the next patch (e.g. v1.5.1v1.5.2)
  • Update CMakeLists.txt version and commit it
  • Create a draft GitHub release on the X.Y channel
  • Trigger the build and Nexus upload pipeline automatically

6. Publish the release

Review the draft release on GitHub, then publish it. The Nexus upload will trigger automatically on publish.

7. Clean up

The hotfix/X.Y.x branch can be left in place for future patches on the same older line, or deleted if no further patches are expected. All fixes should already be on dev from step 1 (where applicable).

Clone this wiki locally