Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/tested-configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ otherwise.
| Date | Build | OS / desktop | GPU / driver | CPU / RAM | Settings | Where | Result |
|---|---|---|---|---|---|---|---|
| 2026-11-10 | v2.0.14 (source 3a9998c3), Hookshot Builder; later v2.0.14 + the scale-image SAMPLED fix (as in #1940) | Windows 11 Pro 26200, fullscreen | RTX 5090, 621.14 | Ryzen 7 5800X3D, 64 GB | 3840x2160 output, DLSS Quality (67%), Steam Controller (2026) | Castle Town market, Kokiri Forest | Plays well. On the release: HDR Expanded a black picture, Native flat (#1613); with the fix Expanded is right, frame generation 3x too. One crash in `nvoglv64.dll` (0xc0000005) after about 17,000 frames with frame generation 5x. Steam Controller started from Steam: no gyro until Steam's `SDL_GAMECONTROLLER_IGNORE_DEVICES` and `SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD` were cleared (#1897) |
| 2026-11-10 | `cc80fde` on `9447e1d`, Vulkan API 14 | Windows 11 Pro 26300, windowed | RTX 5070 Ti, 621.01 | Ryzen 7 9800X3D, 32 GB | 3440x1440 game output, fixed FG 2x, parallel replay (2 workers, batch 1), pacing off | Link's house in Kokiri, copied checkpoint | 6,688 presents logged. Swapchain 1444x720 → 1440x829 → 1444x720, then 1444x720 after minimize/restore. The scene returned and the window closed normally. Surface loss and deferred admission were exercised with injected results in the source fixture. |
| 2026-11-10 | v2.0.11-449-g1987d8c (main caaee0b + the VRAM compaction branch, unmerged then) | Windows 11, no window (unattended, scripted input) | RTX 4070 SUPER 12 GB, 617.42 | Ryzen 9 9950X3D (SMT off), 32 GB | 2560x1440, DLAA, `RX_VK_COMPACT=1`; other programs open (10.2-10.5 GB of VRAM allowed) | Hyrule Field, a scripted five-minute walk (not standing still) | Two runs: 58.2, 58.6 fps mean; VRAM 8.3, 7.9 GB; GPU time over 12.5 ms a frame in 6 of 122 and 10 of 123 120-present windows; 88, 62 frames over 33 ms; 1% low 21.1, 26.5 fps. No device lost |
| 2026-11-10 | as above | as above | as above | as above | as above, without the switch | as above | Two runs: 53.2, 54.4 fps mean; VRAM 9.5, 9.6 GB; the write-back shadows given back in 73 of 111 and 80 of 114 windows; GPU time over 12.5 ms in 69 of 111 and 55 of 114; 107, 112 frames over 33 ms; 1% low 19.4, 20.6 fps. No device lost |
| 2026-11-10 | host `v2.0.11-438-gcd544a7` (source `8bdd7a54`); renderer main `8bac8aa` + #1502 + the host-clock capture change | Windows 11 Home N, 26300 | RX 9070 XT 16 GB, 32.0.32015.12012 | Ryzen 7 5700X3D, 64 GB | 2560x720 window, 32:9 camera, serial, FG off, dense capture, core validation; fresh prepared shader bank | Boot, title, save load; gameplay not reached | 96 s: 1,476 consecutive original capture records; all 1,461 records covered by the flushed native trace align one-to-one with native returns. Movement window rejected: no filled gameplay interval. No new validation IDs; existing `02251`, and two `08737` errors during full preparation. No gameplay speed or performance claim |
Expand Down
2 changes: 1 addition & 1 deletion gpu/vk/vk_framegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ void display_main() {
// 5080, 60 fps), and a VRR display's refresh changed every frame (flicker on OLED). Now a set's copies go to the GPU
// together, the first one's end is waited for, and only the presents keep their times on the line.
static const bool ahead_env = !getenv("RX_VK_PRESENT_AHEAD") || atoi(getenv("RX_VK_PRESENT_AHEAD")) != 0;
const bool ahead = present_ahead_wanted(ahead_env, pace, shown, dev.swap_spare);
const bool ahead = present_ahead_wanted(ahead_env, pace, shown, dev.swap_spare.load(std::memory_order_relaxed));
PendingPresent pending[kMaxMult];
if (ahead) {
static bool said;
Expand Down
1 change: 1 addition & 0 deletions gpu/vk/vk_framegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct PendingPresent {
uint32_t index = 0;
VkFence done = VK_NULL_HANDLE; // (the copy's fence)
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
uint64_t swapchain_generation = 0;
};
extern thread_local PendingPresent* present_defer_into;
bool present_commit(const PendingPresent& p); // (false: not shown, nothing deferred or a swapchain made again since)
Expand Down
6 changes: 5 additions & 1 deletion gpu/vk/vk_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ struct Device {
// the window
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
uint64_t swapchain_generation = 0; // (under the presentation lock)
VkFormat swap_format = VK_FORMAT_UNDEFINED;
VkExtent2D swap_extent{};
std::vector<VkImage> swap_images;
// (hookshot) how many of its images may be held acquired at once with no acquire timeout: the count less the surface's
// minImageCount (the Vulkan spec's limit). vk_framegen.cpp's present ahead holds two
uint32_t swap_spare = 0;
std::atomic<uint32_t> swap_spare{0};
std::vector<VkImageView> swap_views; // (hookshot) a view of each, for Dear ImGui's overlays (VK_NULL_HANDLE: none)
VkFormat swap_view_format = VK_FORMAT_UNDEFINED; // (their format: the UNORM one of an sRGB swapchain when it can be)
bool swap_mutable = false; // (VK_KHR_swapchain_mutable_format: an sRGB swapchain's images viewed as UNORM)
Expand All @@ -90,6 +91,7 @@ struct Device {
// (hookshot) a queue of a compute family for vkQueuePresentKHR alone (vk_main.cpp create_device; RX_VK_PRESENT_QUEUE=0 / 1).
// VK_NULL_HANDLE: presents go on `queue` under present_mu. Its own lock: taken after present_mu, never before it
VkQueue present_queue = VK_NULL_HANDLE;
bool present_queue_supported = false;
std::mutex present_queue_mu;
uint32_t present_family = 0; // (present_queue's family, the swapchain's images shared with it)
};
Expand Down Expand Up @@ -293,6 +295,8 @@ void vma_log_image_blocks(); // (the image pools' blocks: what they hold, and
PFN_vkGetInstanceProcAddr instance_proc(); // vk_main.cpp

bool create_swapchain(uint32_t width, uint32_t height);
void retire_swapchain(); // (dev.present_mu held)
bool invalidate_lost_surface(VkResult result); // (dev.present_mu held)
bool replay_start(); // vk_replay.cpp
bool precompile_wanted(); // vk_draw.cpp
void precompile_run(void (*progress)(size_t done, size_t total));
Expand Down
113 changes: 80 additions & 33 deletions gpu/vk/vk_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,50 @@ void swap_views_make(VkImageUsageFlags usage, VkFormat format) {
}
}

void retire_swapchain() {
if (!dev.swapchain) return;
device_wait_idle_locked();
reflex_swapchain_destroyed();
swap_views_drop();
for (auto semaphore : dev.swap_rendered) vkDestroySemaphore(dev.device, semaphore, nullptr);
vkDestroySwapchainKHR(dev.device, dev.swapchain, nullptr);
dev.swapchain = VK_NULL_HANDLE;
++dev.swapchain_generation;
dev.swap_rendered.clear();
dev.swap_images.clear();
dev.swap_spare = 0;
}

bool invalidate_lost_surface(VkResult result) {
if (result != VK_ERROR_SURFACE_LOST_KHR) return false;
retire_swapchain();
if (dev.surface) vkDestroySurfaceKHR(dev.instance, dev.surface, nullptr);
dev.surface = VK_NULL_HANDLE;
dev.present_queue_supported = false;
static unsigned logged = 0;
if (logged++ < 8) logf("vk: presentation surface lost; will retry with a replacement");
return true;
}

namespace {
bool open_window_surface(bool log_failure);
void window_size(uint32_t* width, uint32_t* height);
}

bool create_swapchain(uint32_t width, uint32_t height) {
if (!dev.surface) {
window_size(&width, &height);
if (!width || !height) return false;
using Clock = std::chrono::steady_clock;
static Clock::time_point next_attempt;
const auto now = Clock::now();
if (now < next_attempt) return false;
next_attempt = now + std::chrono::milliseconds(250);
if (!open_window_surface(false)) return false;
}
VkSurfaceCapabilitiesKHR caps;
VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.physical, dev.surface, &caps);
if (invalidate_lost_surface(result)) return false;
if (result != VK_SUCCESS) {
logf("vk: querying surface capabilities failed (VkResult %d)", (int)result);
return false;
Expand All @@ -555,12 +596,15 @@ bool create_swapchain(uint32_t width, uint32_t height) {
if (!width || !height) return false;
uint32_t n = 0;
result = vkGetPhysicalDeviceSurfaceFormatsKHR(dev.physical, dev.surface, &n, nullptr);
if (invalidate_lost_surface(result)) return false;
if (result != VK_SUCCESS || !n) {
logf("vk: querying surface formats failed (VkResult %d, count %u)", (int)result, n);
return false;
}
std::vector<VkSurfaceFormatKHR> formats(n);
vkGetPhysicalDeviceSurfaceFormatsKHR(dev.physical, dev.surface, &n, formats.data());
result = vkGetPhysicalDeviceSurfaceFormatsKHR(dev.physical, dev.surface, &n, formats.data());
if (invalidate_lost_surface(result) || result != VK_SUCCESS || !n) return false;
formats.resize(n);
VkSurfaceFormatKHR fmt = formats[0];
for (const auto& f : formats) // (an UNORM target: the game's window textures are presented as they are)
// (in sRGB: RADV on Wayland lists every format again with PASS_THROUGH_EXT and the compositor's colour spaces,
Expand Down Expand Up @@ -596,6 +640,7 @@ bool create_swapchain(uint32_t width, uint32_t height) {
modes.resize(n);
result = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.physical, dev.surface, &n, modes.data());
}
if (invalidate_lost_surface(result)) return false;
if (result != VK_SUCCESS || modes.empty())
logf("vk: querying present modes failed (VkResult %d, count %u); trying mandatory FIFO mode", (int)result, n);
// (hookshot) the Insert menu's V-Sync (menu_vsync: RX_VK_FIFO=1 On, RX_VK_IMMEDIATE=1 Off for a run): On is FIFO, as on a
Expand Down Expand Up @@ -642,7 +687,7 @@ bool create_swapchain(uint32_t width, uint32_t height) {
}
ci.presentMode = mode;
const uint32_t sharing[2] = {dev.queue_family, dev.present_family};
if (dev.present_queue && dev.present_family != dev.queue_family) { // (presented from another family: no ownership moves)
if (dev.present_queue_supported && dev.present_family != dev.queue_family) { // (presented from another family: no ownership moves)
ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
ci.queueFamilyIndexCount = 2;
ci.pQueueFamilyIndices = sharing;
Expand All @@ -665,17 +710,8 @@ bool create_swapchain(uint32_t width, uint32_t height) {
VkSwapchainKHR sc;
result = vkCreateSwapchainKHR(dev.device, &ci, nullptr, &sc);
// Passing oldSwapchain retires it even when creation fails. Nothing may acquire from it again.
if (dev.swapchain) {
device_wait_idle_locked(); // (the present queue's presents of the old swapchain done too)
reflex_swapchain_destroyed(); // (no latency sleep or markers may use the destroyed chain)
swap_views_drop(); // (hookshot: the overlays' views of its images, before it)
for (auto semaphore : dev.swap_rendered) vkDestroySemaphore(dev.device, semaphore, nullptr);
vkDestroySwapchainKHR(dev.device, dev.swapchain, nullptr);
dev.swapchain = VK_NULL_HANDLE;
dev.swap_rendered.clear();
dev.swap_images.clear();
dev.swap_spare = 0;
}
retire_swapchain();
if (invalidate_lost_surface(result)) return false;
if (result != VK_SUCCESS) {
logf("vk: vkCreateSwapchainKHR failed (VkResult %d, extent %ux%u, format %d, usage %#x, mode %d, images %u)",
(int)result, width, height, (int)fmt.format, ci.imageUsage, (int)mode, ci.minImageCount);
Expand Down Expand Up @@ -983,16 +1019,44 @@ bool started_up() {
}
// (the window's surface made: the presents' own queue kept where its family can present to it, create_device)
static void choose_present_queue() {
dev.present_queue_supported = false;
if (!dev.present_queue) return;
VkBool32 ok = VK_FALSE;
if (vkGetPhysicalDeviceSurfaceSupportKHR(dev.physical, dev.present_family, dev.surface, &ok) != VK_SUCCESS || !ok) {
logf("vk: queue family %u cannot present to the window: presents on the replay's queue", dev.present_family);
dev.present_queue = VK_NULL_HANDLE, dev.present_family = dev.queue_family;
return;
}
dev.present_queue_supported = true;
logf("vk: presents on a queue of their own, of compute family %u (RX_VK_PRESENT_QUEUE=0: off)", dev.present_family);
}

bool open_window_surface(bool log_failure) {
VkSurfaceKHR surface = VK_NULL_HANDLE;
#ifdef _WIN32
VkWin32SurfaceCreateInfoKHR si{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR};
si.hinstance = GetModuleHandleA(nullptr);
si.hwnd = main_window;
if (!IsWindow(main_window) || vkCreateWin32SurfaceKHR(dev.instance, &si, nullptr, &surface) != VK_SUCCESS) return false;
#else
const MkwVkWindow* w = prep_window;
if (!w || !w->create_surface(w->window, (void*)dev.instance, (void*)&surface)) {
if (log_failure)
logf("vk: SDL_Vulkan_CreateSurface failed; check SDL_VIDEODRIVER and the Vulkan window-system extensions");
return false;
}
#endif
VkBool32 ok = VK_FALSE;
const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(dev.physical, dev.queue_family, surface, &ok);
if (result != VK_SUCCESS || !ok) {
if (log_failure) logf("vk: queue-family present support failed (VkResult %d, supported %u)", (int)result, ok);
vkDestroySurfaceKHR(dev.instance, surface, nullptr);
return false;
}
dev.surface = surface;
choose_present_queue();
return true;
}

int start(void* hwnd) {
main_window = (HWND)hwnd;
if (!hwnd) { // (HOS_HEADLESS: no window; the frames are rendered all the same, RX_VK_SHOTS writes them out)
Expand All @@ -1005,14 +1069,7 @@ int start(void* hwnd) {
return 1;
}
#ifdef _WIN32
VkWin32SurfaceCreateInfoKHR si{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR};
si.hinstance = GetModuleHandleA(nullptr);
si.hwnd = (HWND)hwnd;
if (vkCreateWin32SurfaceKHR(dev.instance, &si, nullptr, &dev.surface) != VK_SUCCESS) return 0;
VkBool32 ok = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(dev.physical, dev.queue_family, dev.surface, &ok);
if (!ok) return 0;
choose_present_queue();
if (!open_window_surface(true)) return 0;
RECT r{};
GetClientRect((HWND)hwnd, &r);
if (!create_swapchain((uint32_t)(r.right - r.left), (uint32_t)(r.bottom - r.top))) return 0;
Expand All @@ -1023,17 +1080,7 @@ int start(void* hwnd) {
logf("vk: unsupported SDL window bridge version %u (expected %u)", w->version, MKWVK_WINDOW_VERSION);
return 0;
}
if (!w->create_surface(w->window, (void*)dev.instance, (void*)&dev.surface)) {
logf("vk: SDL_Vulkan_CreateSurface failed; check SDL_VIDEODRIVER and the Vulkan window-system extensions");
return 0;
}
VkBool32 ok = VK_FALSE;
VkResult present = vkGetPhysicalDeviceSurfaceSupportKHR(dev.physical, dev.queue_family, dev.surface, &ok);
if (present != VK_SUCCESS || !ok) {
logf("vk: queue-family present support failed (VkResult %d, supported %u)", (int)present, ok);
return 0;
}
choose_present_queue();
if (!open_window_surface(true)) return 0;
int width = 0, height = 0;
w->drawable_size(w->window, &width, &height);
if (width <= 0 || height <= 0) {
Expand Down
Loading
Loading