Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
882e497
feat(mcp): gated console output capture
alandtse May 30, 2026
b572219
feat(devbench): client-side bridge scaffold (inert until enabled)
alandtse May 31, 2026
1057866
build(devbench): consume devbench-api vcpkg port; enable the bridge
alandtse May 31, 2026
1774720
fix(devbench): make feature toggle reversible
alandtse May 31, 2026
dc9d4a1
fix(devbench): address review — arg parse errors, null-task, port docs
alandtse May 31, 2026
83080c6
refactor(devbench): scope PR to the bridge; address review
alandtse May 31, 2026
cd4a910
build(devbench): gate the bridge behind a CMake option (default ON)
alandtse May 31, 2026
3f49354
fix(build): leave RemoteControl.cpp untouched by this PR
alandtse May 31, 2026
e2137a3
build(devbench): surface DEVBENCH_BRIDGE in the user preset template
alandtse May 31, 2026
d7aa0a9
feat(devbench): emit openshaders.feature.changed; gate dep via vcpkg …
alandtse May 31, 2026
cb921c3
docs(devbench): document the feature-toggle threading contract
alandtse May 31, 2026
482a95b
feat(devbench)!: migrate CS off its embedded MCP server to the bridge
alandtse May 31, 2026
59b96d5
feat(devbench): migration code — bridge tools, gutted server, status …
alandtse May 31, 2026
345d41f
Merge origin/dev into feat/devbench-bridge
alandtse May 31, 2026
31c6aa2
Merge origin/dev into feat/devbench-bridge
alandtse May 31, 2026
65735e4
feat(devbench): add openshaders.shadercache (clear/deleteDisk); revie…
alandtse May 31, 2026
79df600
docs: describe present bridge/panel role, not removed server (review)
alandtse May 31, 2026
95b9216
build(devbench-api): bump port to devbench c75e7d2
alandtse May 31, 2026
dfaf39e
fix(devbench): thread-safety + correctness from PR review
alandtse May 31, 2026
1740a03
feat(abtest): manual variant switching for path-aligned A/B
alandtse May 31, 2026
f949ffb
fix(abtest): reset manualMode on Disable; guard reset task Save
alandtse May 31, 2026
101a05f
feat(abtest): expose aggregated A/B timing via openshaders.abtest res…
alandtse May 31, 2026
12e5a9c
fix(devbench): marshal feature list/get reads; drop status from abtes…
alandtse May 31, 2026
68e6502
feat(abtest): auto-enable overlay profiling in manual mode
alandtse May 31, 2026
63f8ae7
fix(devbench): marshal remaining loaded reads; A/B manual polish; rev…
alandtse May 31, 2026
b333e62
refactor(devbench): drop A/B/benchmark from the bridge PR
alandtse May 31, 2026
aa50d80
docs(remotecontrol): fix feature summary tool list (drop abtest)
alandtse May 31, 2026
67bdf70
refactor(remotecontrol): own devbench install at DataLoaded
alandtse May 31, 2026
6d236c4
refactor(remotecontrol): theme colors, QPC, drop brand from panel
alandtse May 31, 2026
0360fc4
refactor(remotecontrol): drop redundant overrides + external refs
alandtse May 31, 2026
4da6fc8
refactor: move DevBenchBridge out of /src
alandtse Jun 1, 2026
283bbed
fix(devbench): validate set/reset + suppress post-timeout side effects
alandtse Jun 1, 2026
1bc015b
docs(devbench): clarify shadercache clear vs deleteDisk
alandtse Jun 1, 2026
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
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ option(
"Include features whose .ini has autoupload != true in the AIO. Off by default — the AIO ships only release-ready features. Turn on for local dev builds that want every feature regardless of release status."
OFF
)
option(
DEVBENCH_BRIDGE
"Register Open Shaders tools into the devbench test bench (requires the devbench-api port; runtime no-op when no devbench host is present)"
ON
)
message("\tAuto plugin deployment: ${AUTO_PLUGIN_DEPLOYMENT}")
message("\tZip to dist: ${ZIP_TO_DIST}")
message("\tAIO Zip to dist: ${AIO_ZIP_TO_DIST}")
Expand Down Expand Up @@ -97,6 +102,9 @@ find_package(efsw CONFIG REQUIRED)
find_path(EXPRTK_INCLUDE_DIRS "exprtk.hpp" REQUIRED)
find_package(Tracy CONFIG REQUIRED)
find_package(directx-headers CONFIG REQUIRED)
if(DEVBENCH_BRIDGE)
find_package(devbench-api CONFIG REQUIRED)
endif()
add_subdirectory(${CMAKE_SOURCE_DIR}/cmake/Streamline)

find_path(DETOURS_INCLUDE_DIRS "detours/detours.h")
Expand Down Expand Up @@ -154,6 +162,13 @@ target_link_libraries(
${DETOURS_LIBRARY}
)

# devbench bridge: opt-out via -DDEVBENCH_BRIDGE=OFF. When off, the port isn't required
# and DevBenchBridge.cpp compiles to an empty Install().
if(DEVBENCH_BRIDGE)
target_link_libraries(${PROJECT_NAME} PRIVATE DevBench::API)
target_compile_definitions(${PROJECT_NAME} PRIVATE DEVBENCH_BRIDGE_ENABLED)
endif()

# Some third-party libs (e.g. FFX backend) are built with /GL.
# In RelWithDebInfo, align linker options to avoid LNK4075/LNK1218 when warnings are treated as errors.
if(MSVC)
Expand Down
3 changes: 2 additions & 1 deletion CMakeUserPresets.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"name": "ALL-WITH-AUTO-DEPLOYMENT",
"binaryDir": "${sourceDir}/build/ALL",
"cacheVariables": {
"AUTO_PLUGIN_DEPLOYMENT": "ON"
"AUTO_PLUGIN_DEPLOYMENT": "ON",
"DEVBENCH_BRIDGE": "ON"
},
"environment": {
"CommunityShadersOutputDir": "F:/MySkyrimModpack/mods/CommunityShaders;F:/SteamLibrary/steamapps/common/SkyrimVR/Data;F:/SteamLibrary/steamapps/common/Skyrim Special Edition/Data"
Expand Down
43 changes: 43 additions & 0 deletions cmake/ports/devbench-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# devbench-api vcpkg port

Vendor the devbench cross-plugin API (`DevBenchAPI.h` + `.cpp`) into your SKSE plugin
via vcpkg — **do not copy the files into your tree** (they drift). Mirrors the
SkyrimVRESL port.

## Consume (once devbench is published)

`vcpkg.json`:

```json
{ "dependencies": ["devbench-api"] }
```

`CMakeLists.txt`:

```cmake
find_package(devbench-api CONFIG REQUIRED)
target_link_libraries(YourPlugin PRIVATE DevBench::API)
```

Then, after SKSE sends `kPostLoad`:

```cpp
#include <DevBenchAPI.h>
if (auto* dvb = DevBenchAPI::GetDevBenchInterface001()) {
dvb->RegisterTool("yourmod.dothing", R"({"description":"...","inputSchema":{...}})",
&YourHandler, yourCtx);
}
```

Linking `DevBench::API` puts `DevBenchAPI.h` on the include path and compiles
`DevBenchAPI.cpp` (the messaging-handshake helper) into your plugin. The API glue is
**MIT** (`DevBenchAPI.LICENSE.txt`); the devbench plugin itself is GPL-3.0.

## Pinning / bumping

`portfile.cmake` is pinned to a concrete devbench commit via `vcpkg_from_github`
(`REF` + `SHA512`) — no placeholders to fill in; the overlay works as-is. To pull a
newer API revision, update `REF` to the new commit and replace `SHA512` with the value
vcpkg reports on the first (failed) build, or precompute it with
`vcpkg hash <downloaded-tarball>`. The API header is ABI-versioned, so a bump is only
needed to adopt a new interface revision.
13 changes: 13 additions & 0 deletions cmake/ports/devbench-api/devbench-api-config.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
get_filename_component(_DEVBENCH_API_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)

# DevBench::API — header-only-ish target. The consumer gets the include dir and
# DevBenchAPI.cpp compiled into it (the consumer-side helper that fetches the
# interface via SKSE messaging). Link this, then call
# DevBenchAPI::GetDevBenchInterface001() after kPostLoad.
if(NOT TARGET DevBench::API)
add_library(DevBench::API INTERFACE IMPORTED)
set_target_properties(DevBench::API PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_DEVBENCH_API_DIR}/../../include"
INTERFACE_SOURCES "${_DEVBENCH_API_DIR}/src/DevBenchAPI.cpp"
)
endif()
29 changes: 29 additions & 0 deletions cmake/ports/devbench-api/portfile.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# devbench-api — header-only cross-plugin API for SKSE consumers.
# Mirrors the SkyrimVRESL port: installs the MIT API header + its companion .cpp
Comment thread
alandtse marked this conversation as resolved.
Outdated
# (compiled into the consumer via the config's INTERFACE_SOURCES).
#
# Pinned to a published commit. devbench-api isn't in the official vcpkg registry, so
# consumers add this directory to VCPKG_OVERLAY_PORTS (see README). To ship a newer API
# revision, bump REF to the new commit and SHA512 to its archive hash.

vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO alandtse/devbench
REF 2f0dac74f409f895b8b74e5440be8371e33fe0b0
SHA512 0bc46d6a09cf0b216373b1ec09d8da8b338b58b90618f3b1b8cea07616875a7fc85c6e777166a0998213f0e73e7ea099e20142591fe3de77f36113241985ae3e
HEAD_REF main
)

# MIT API glue → header to include/, source to share/ (referenced by the config target).
file(INSTALL "${SOURCE_PATH}/include/DevBenchAPI.h"
DESTINATION "${CURRENT_PACKAGES_DIR}/include")
file(INSTALL "${SOURCE_PATH}/include/DevBenchAPI.cpp"
DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}/src")

# CMake package config — defines DevBench::API.
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/devbench-api-config.cmake"
DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")

# The API glue is MIT (not the GPL-3.0 plugin) — ship that as the port copyright.
file(INSTALL "${SOURCE_PATH}/include/DevBenchAPI.LICENSE.txt"
DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)
7 changes: 7 additions & 0 deletions cmake/ports/devbench-api/vcpkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "devbench-api",
"version": "1.0.0",
"description": "devbench cross-plugin API header for SKSE plugin developers. Register MCP/REST tools and emit events into the devbench host. MIT-licensed glue; the devbench plugin itself is GPL-3.0.",
"homepage": "https://github.com/alandtse/devbench",
"license": "MIT"
}
119 changes: 119 additions & 0 deletions src/DevBenchBridge.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "DevBenchBridge.h"

// Registers our tools into the devbench test bench over its C-ABI. Gated by
// DEVBENCH_BRIDGE_ENABLED (set by CMake when the devbench-api port is available);
// otherwise this file compiles to an empty Install(). Inert at runtime when no
// devbench plugin is present (GetDevBenchInterface001() returns null).

#ifdef DEVBENCH_BRIDGE_ENABLED

# include "Feature.h"

# include <DevBenchAPI.h>
# include <nlohmann/json.hpp>

# include <stdexcept>

namespace
{
// Build the `feature` tool result. May throw (json type errors etc.); the C-ABI
// handler below contains every exception so none crosses the DLL boundary.
nlohmann::json BuildFeatureResult(const nlohmann::json& a_args)
{
using json = nlohmann::json;
const std::string action = a_args.value("action", std::string("list"));

if (action == "list") {
json out = json::array();
for (auto* f : Feature::GetFeatureList()) {
out.push_back(json{
{ "name", f->GetName() },
{ "shortName", f->GetShortName() },
{ "loaded", f->loaded },
{ "isCore", f->IsCore() },
{ "supportsVR", f->SupportsVR() },
});
}
return out;
}
if (action == "toggle") {
const std::string shortName = a_args.value("shortName", std::string{});
// Match over the full feature list (NOT Feature::FindFeatureByShortName,
// which only matches *loaded* features — that makes toggle one-way: you could
// disable a feature but never re-enable it). Mirrors the 'list' branch.
Feature* target = nullptr;
if (!shortName.empty()) {
for (auto* f : Feature::GetFeatureList()) {
if (f->GetShortName() == shortName) {
target = f;
break;
}
}
}
if (!target)
return json{ { "error", "unknown or missing shortName" }, { "shortName", shortName } };
const bool desired = a_args.value("enabled", !target->loaded);
Comment thread
alandtse marked this conversation as resolved.
Outdated
auto* task = SKSE::GetTaskInterface();
if (!task)
return json{ { "error", "SKSE task interface unavailable" }, { "shortName", shortName } };
task->AddTask([target, desired]() { target->loaded = desired; });
return json{ { "queued", true }, { "shortName", shortName }, { "requested", desired } };
}
return json{ { "error", "unknown action (list|toggle)" }, { "action", action } };
}

// CS `feature` tool, re-exposed through devbench. Captureless C function (the C-ABI
// forbids std::function across the DLL boundary). Runs on devbench's listener thread;
// mutations marshal to the main thread via SKSE's TaskInterface. Every exception is
// contained here and a_write is always called once — nothing escapes across the C ABI.
void FeatureToolHandler(void*, const char* a_argsJson, void* a_sink, DevBenchAPI::WriteFn a_write)
{
using json = nlohmann::json;
json out;
try {
json args = json::object();
if (a_argsJson && *a_argsJson)
args = json::parse(a_argsJson); // throws on malformed input
if (!args.is_object())
throw std::runtime_error("arguments must be a JSON object");
out = BuildFeatureResult(args);
} catch (const std::exception& e) {
out = json{ { "error", "invalid request" }, { "detail", e.what() } };
} catch (...) {
out = json{ { "error", "unknown handler error" } };
}
const std::string dumped = out.dump();
a_write(a_sink, dumped.c_str());
}
}

namespace DevBenchBridge
{
void Install()
{
auto* dvb = DevBenchAPI::GetDevBenchInterface001();
if (!dvb) {
logger::info("DevBenchBridge: devbench not present; CS tools not registered");
return;
}
logger::info("DevBenchBridge: devbench build {} present — registering CS tools", dvb->GetBuildNumber());

// Namespaced tool name — devbench's registry is shared across plugins, so a bare
// "feature" could collide with devbench's own or another mod's tool.
static constexpr const char* featureDesc =
R"({"description":"List or toggle Open Shaders features.","inputSchema":{"type":"object","properties":{"action":{"type":"string","enum":["list","toggle"]},"shortName":{"type":"string"},"enabled":{"type":"boolean"}}}})";
dvb->RegisterTool("openshaders.feature", featureDesc, &FeatureToolHandler, nullptr);

// Further CS tools (feature set/reset, shader-cache, capture, abtest) and
// shader-recompile events register here via the same dvb->RegisterTool / EmitEvent.
}
}

#else

namespace DevBenchBridge
{
void Install() {} // inert until built with DEVBENCH_BRIDGE_ENABLED
}

#endif
16 changes: 16 additions & 0 deletions src/DevBenchBridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

// Client-side bridge to the devbench host (https://github.com/alandtse/devbench).
// Registers Open Shaders' tools into devbench over its cross-plugin C-ABI so they are
// drivable from the shared bench (MCP + REST).
//
// The implementation compiles only with -DDEVBENCH_BRIDGE_ENABLED (set by CMake when the
// `devbench-api` port is available); otherwise this file compiles to an empty Install().
// When built in, Install() is still a runtime no-op if no devbench host is present — so
// it is always safe to call.
namespace DevBenchBridge
{
// Fetch the devbench interface (after kPostLoad) and register our tools. No-op if
// devbench is not present or the bridge was built disabled. Safe to call always.
void Install();
}
5 changes: 5 additions & 0 deletions src/XSEPlugin.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "Deferred.h"
#include "DevBenchBridge.h"
#include "Features/Upscaling.h"
#include "FrameAnnotations.h"
#include "Globals.h"
Expand Down Expand Up @@ -114,6 +115,10 @@ void MessageHandler(SKSE::MessagingInterface::Message* message)
EngineFix::InstallOnDataLoadedFixes();
FrameAnnotations::OnDataLoaded();

// Register our tools into the devbench host if one is present (runtime
// no-op otherwise). devbench has provided its interface by kPostLoad.
DevBenchBridge::Install();
Comment thread
alandtse marked this conversation as resolved.
Outdated
Comment thread
alandtse marked this conversation as resolved.
Outdated

auto shaderCache = globals::shaderCache;
shaderCache->menuLoaded = true;

Expand Down
1 change: 1 addition & 0 deletions vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": [
"bshoshany-thread-pool",
"clib-util",
"devbench-api",
Comment thread
alandtse marked this conversation as resolved.
Outdated
"cppwinrt",
"directx-headers",
"directxtex",
Expand Down
Loading