M2(Vulkan): Vulkan mock-game backend + volk/Vulkan-Headers submodules
Add render_vk.cpp (selectable as `vk`): brings up a real Vulkan instance/device/swap chain via volk (which dlopens vulkan-1.dll -- the loader-bypass case the capture hook must handle) and clears the swap-chain image to the frame-counter colour each frame with vkCmdClearColorImage (no pipeline, no shaders, no SPIR-V) and presents. The whole image encodes the frame number, so it animates and stale frames are detectable. Adds the official Khronos Vulkan-Headers + zeux/volk submodules and a coop_require_submodule() CMake helper that fails with a clear "git submodule update --init --recursive" message rather than auto-cloning. volk is pinned to the project's dynamic CRT (no LNK4098). mock_game_test gets a vk liveness check (its present pointer is cached at init, so late injection can't hook it -- the capture path needs the early-load path, to come). 16/16 ctest, skips cleanly without a Vulkan driver. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -4,3 +4,9 @@
|
|||||||
[submodule "third_party/safetyhook"]
|
[submodule "third_party/safetyhook"]
|
||||||
path = third_party/safetyhook
|
path = third_party/safetyhook
|
||||||
url = https://github.com/cursey/safetyhook.git
|
url = https://github.com/cursey/safetyhook.git
|
||||||
|
[submodule "third_party/Vulkan-Headers"]
|
||||||
|
path = third_party/Vulkan-Headers
|
||||||
|
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||||
|
[submodule "third_party/volk"]
|
||||||
|
path = third_party/volk
|
||||||
|
url = https://github.com/zeux/volk.git
|
||||||
|
|||||||
@@ -35,6 +35,19 @@ function(coop_output_subdir subdir)
|
|||||||
endforeach()
|
endforeach()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
# Fail with a clear message if a git submodule wasn't checked out, instead of a confusing
|
||||||
|
# "file not found" deep in a build. `sentinel` is a path (relative to the source root) that
|
||||||
|
# only exists when the submodule is populated. We deliberately do NOT auto-clone -- explicit
|
||||||
|
# is better than a surprise network fetch during configure.
|
||||||
|
# Usage: coop_require_submodule(<name> <sentinel-path>).
|
||||||
|
function(coop_require_submodule name sentinel)
|
||||||
|
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/${sentinel}")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Submodule '${name}' is not checked out (missing ${sentinel}).\n"
|
||||||
|
"Run: git submodule update --init --recursive")
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
add_compile_options(/W4 /permissive- /Zc:__cplusplus /utf-8 /MP)
|
add_compile_options(/W4 /permissive- /Zc:__cplusplus /utf-8 /MP)
|
||||||
add_compile_definitions(UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX)
|
add_compile_definitions(UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX)
|
||||||
|
|||||||
@@ -300,6 +300,37 @@ void test_video_capture(const char* backend, ID3D11Device* device)
|
|||||||
game.kill();
|
game.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Liveness smoke check for a backend whose capture can't be exercised by late injection
|
||||||
|
// (Vulkan caches its present pointer at init): launch it for a couple of seconds and assert it
|
||||||
|
// comes up and exits cleanly. Exit code 2 = backend unavailable on this machine (e.g. no Vulkan
|
||||||
|
// driver) -> skip without failing.
|
||||||
|
void test_liveness(const char* backend)
|
||||||
|
{
|
||||||
|
std::printf("== liveness: %s ==\n", backend);
|
||||||
|
std::wstring args;
|
||||||
|
for (const char* p = backend; *p != '\0'; ++p)
|
||||||
|
{
|
||||||
|
args.push_back(static_cast<wchar_t>(*p));
|
||||||
|
}
|
||||||
|
args += L" 2"; // run 2s then exit on its own
|
||||||
|
MockGame game = MockGame::launch(args);
|
||||||
|
if (!game.ok)
|
||||||
|
{
|
||||||
|
check(false, "launch coop_mock_game (liveness)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WaitForSingleObject(game.pi.hProcess, 6000); // let the 2s run finish
|
||||||
|
if (!game.alive() && game.exit_code() == 2)
|
||||||
|
{
|
||||||
|
std::printf(" backend '%s' unavailable on this machine -- skipping\n", backend);
|
||||||
|
game.kill();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(!game.alive(), "backend ran and exited within the time limit (no hang)");
|
||||||
|
check(game.exit_code() == 0, "backend came up and exited cleanly");
|
||||||
|
game.kill();
|
||||||
|
}
|
||||||
|
|
||||||
// Launch the mock game rendering audio at `rate`/`channels`/`bits`/`fmt`, inject the audio
|
// Launch the mock game rendering audio at `rate`/`channels`/`bits`/`fmt`, inject the audio
|
||||||
// hook (late, so it's the guessed path), and verify the hook MEASURES the right sample rate
|
// hook (late, so it's the guessed path), and verify the hook MEASURES the right sample rate
|
||||||
// for this variant and captures non-silent audio. (Channels/bit-depth aren't recoverable for
|
// for this variant and captures non-silent audio. (Channels/bit-depth aren't recoverable for
|
||||||
@@ -522,6 +553,10 @@ int main()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vulkan: its present pointer is cached at init, so late injection can't hook it -- a
|
||||||
|
// liveness check (the mock comes up + presents + exits) is the automated coverage here; the
|
||||||
|
// capture path is exercised via the early-load path documented in the README.
|
||||||
|
test_liveness("vk");
|
||||||
test_video_capture("gl", device);
|
test_video_capture("gl", device);
|
||||||
test_video_capture("dx9ex", device);
|
test_video_capture("dx9ex", device);
|
||||||
test_video_capture("dx9", device);
|
test_video_capture("dx9", device);
|
||||||
|
|||||||
23
third_party/CMakeLists.txt
vendored
23
third_party/CMakeLists.txt
vendored
@@ -18,3 +18,26 @@ if(MSVC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(imgui PUBLIC d3d11 dxgi dwmapi)
|
target_link_libraries(imgui PUBLIC d3d11 dxgi dwmapi)
|
||||||
|
|
||||||
|
# --- Vulkan: official Khronos headers + volk meta-loader (submodules) -------
|
||||||
|
# volk dynamically loads vulkan-1.dll at runtime (no link-time loader lib, no SDK install),
|
||||||
|
# so the Vulkan mock backend + capture hook need only these two submodules.
|
||||||
|
coop_require_submodule("Vulkan-Headers" "third_party/Vulkan-Headers/include/vulkan/vulkan.h")
|
||||||
|
coop_require_submodule("volk" "third_party/volk/volk.h")
|
||||||
|
|
||||||
|
add_library(vulkan_headers INTERFACE)
|
||||||
|
target_include_directories(vulkan_headers INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include)
|
||||||
|
|
||||||
|
add_library(volk STATIC volk/volk.c)
|
||||||
|
target_include_directories(volk PUBLIC volk)
|
||||||
|
target_link_libraries(volk PUBLIC vulkan_headers)
|
||||||
|
# VK_NO_PROTOTYPES: volk provides the entry points itself (no link to a loader lib).
|
||||||
|
target_compile_definitions(volk PUBLIC VK_NO_PROTOTYPES)
|
||||||
|
if(WIN32)
|
||||||
|
target_compile_definitions(volk PUBLIC VK_USE_PLATFORM_WIN32_KHR)
|
||||||
|
endif()
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(volk PRIVATE /W0) # third-party source
|
||||||
|
# Match the rest of the project's CRT (dynamic) so it doesn't drag in LIBCMT (LNK4098).
|
||||||
|
set_target_properties(volk PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||||
|
endif()
|
||||||
|
|||||||
1
third_party/Vulkan-Headers
vendored
Submodule
1
third_party/Vulkan-Headers
vendored
Submodule
Submodule third_party/Vulkan-Headers added at 01393c3df0
1
third_party/volk
vendored
Submodule
1
third_party/volk
vendored
Submodule
Submodule third_party/volk added at 477a354c50
@@ -7,12 +7,13 @@ add_executable(coop_mock_game
|
|||||||
render_dx12.cpp
|
render_dx12.cpp
|
||||||
render_dx10.cpp
|
render_dx10.cpp
|
||||||
render_dx09.cpp
|
render_dx09.cpp
|
||||||
render_gl.cpp)
|
render_gl.cpp
|
||||||
|
render_vk.cpp)
|
||||||
|
|
||||||
# Reuses the shared ToneSource (also used by coop_tone + the audio hook self-test).
|
# Reuses the shared ToneSource (also used by coop_tone + the audio hook self-test).
|
||||||
target_include_directories(coop_mock_game PRIVATE ${CMAKE_SOURCE_DIR}/tools/audio_tone)
|
target_include_directories(coop_mock_game PRIVATE ${CMAKE_SOURCE_DIR}/tools/audio_tone)
|
||||||
|
|
||||||
target_link_libraries(coop_mock_game PRIVATE d3d11 d3d12 d3d10 d3d9 dxgi ole32 opengl32 gdi32)
|
target_link_libraries(coop_mock_game PRIVATE d3d11 d3d12 d3d10 d3d9 dxgi ole32 opengl32 gdi32 volk)
|
||||||
set_target_properties(coop_mock_game PROPERTIES OUTPUT_NAME "coop_mock_game")
|
set_target_properties(coop_mock_game PROPERTIES OUTPUT_NAME "coop_mock_game")
|
||||||
|
|
||||||
# Test fixture -> stage next to the tests (alongside coop_tone), not in the deployable root.
|
# Test fixture -> stage next to the tests (alongside coop_tone), not in the deployable root.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// CoopMockGame -- a tiny test "game" used to exercise the capture + audio + hook paths.
|
// CoopMockGame -- a tiny test "game" used to exercise the capture + audio + hook paths.
|
||||||
//
|
//
|
||||||
// coop_mock_game.exe [dx9|dx9ex|dx10|dx11|dx12|gl] [seconds] [rate] [channels] [bits] [pcm|float]
|
// coop_mock_game.exe [dx9|dx9ex|dx10|dx11|dx12|gl|vk] [seconds] [rate] [channels] [bits] [pcm|float]
|
||||||
//
|
//
|
||||||
// It opens a normal visible window and renders an animated, frame-numbered pattern (see
|
// It opens a normal visible window and renders an animated, frame-numbered pattern (see
|
||||||
// render_backend.hpp): a moving bar + per-frame background colour make motion obvious, and
|
// render_backend.hpp): a moving bar + per-frame background colour make motion obvious, and
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ std::unique_ptr<RenderBackend> RenderBackend::create(const std::string& name)
|
|||||||
{
|
{
|
||||||
return create_gl_backend();
|
return create_gl_backend();
|
||||||
}
|
}
|
||||||
|
if (name == "vk" || name == "vulkan")
|
||||||
|
{
|
||||||
|
return create_vk_backend();
|
||||||
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,5 +63,6 @@ std::unique_ptr<RenderBackend> create_dx12_backend();
|
|||||||
std::unique_ptr<RenderBackend> create_dx10_backend();
|
std::unique_ptr<RenderBackend> create_dx10_backend();
|
||||||
std::unique_ptr<RenderBackend> create_dx9_backend(bool ex); // ex: D3D9Ex vs plain D3D9
|
std::unique_ptr<RenderBackend> create_dx9_backend(bool ex); // ex: D3D9Ex vs plain D3D9
|
||||||
std::unique_ptr<RenderBackend> create_gl_backend();
|
std::unique_ptr<RenderBackend> create_gl_backend();
|
||||||
|
std::unique_ptr<RenderBackend> create_vk_backend();
|
||||||
|
|
||||||
} // namespace coop::mock
|
} // namespace coop::mock
|
||||||
|
|||||||
318
tools/mock_game/render_vk.cpp
Normal file
318
tools/mock_game/render_vk.cpp
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
// Vulkan backend for the mock game. Brings up a real Vulkan instance/device/swap chain via
|
||||||
|
// volk (which dlopens vulkan-1.dll -- the loader-bypass case the capture hook must handle) and
|
||||||
|
// each frame clears the swap-chain image to the frame-counter colour with vkCmdClearColorImage
|
||||||
|
// (no pipeline, no shaders, no SPIR-V) and presents. The whole image encodes the frame number,
|
||||||
|
// so it animates and a dropped/stale frame is detectable by the capture test. Clear-only keeps
|
||||||
|
// this to one image-clear per frame; richer per-rect drawing would need a render pass.
|
||||||
|
#include "render_backend.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <volk.h>
|
||||||
|
|
||||||
|
namespace coop::mock
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class VkBackend : public RenderBackend
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool init(HWND hwnd, std::uint32_t width, std::uint32_t height) override
|
||||||
|
{
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
if (volkInitialize() != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
return false; // no Vulkan loader on this machine
|
||||||
|
}
|
||||||
|
|
||||||
|
VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
|
||||||
|
app.pApplicationName = "coop_mock_game";
|
||||||
|
app.apiVersion = VK_API_VERSION_1_1;
|
||||||
|
const char* inst_ext[] = {VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME};
|
||||||
|
VkInstanceCreateInfo ici{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||||
|
ici.pApplicationInfo = &app;
|
||||||
|
ici.enabledExtensionCount = 2;
|
||||||
|
ici.ppEnabledExtensionNames = inst_ext;
|
||||||
|
if (vkCreateInstance(&ici, nullptr, &instance_) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
volkLoadInstance(instance_);
|
||||||
|
|
||||||
|
VkWin32SurfaceCreateInfoKHR sci{VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR};
|
||||||
|
sci.hinstance = GetModuleHandleW(nullptr);
|
||||||
|
sci.hwnd = hwnd;
|
||||||
|
if (vkCreateWin32SurfaceKHR(instance_, &sci, nullptr, &surface_) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pick_device() || !create_device() || !create_swapchain() || !create_commands())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_and_present(std::uint32_t frame) override
|
||||||
|
{
|
||||||
|
if (device_ == VK_NULL_HANDLE || swapchain_ == VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vkWaitForFences(device_, 1, &in_flight_, VK_TRUE, UINT64_MAX);
|
||||||
|
|
||||||
|
std::uint32_t idx = 0;
|
||||||
|
VkResult acq = vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX, acquire_sem_, VK_NULL_HANDLE, &idx);
|
||||||
|
if (acq == VK_ERROR_OUT_OF_DATE_KHR || acq == VK_SUBOPTIMAL_KHR)
|
||||||
|
{
|
||||||
|
return; // skip this frame (the mock window isn't resized in practice)
|
||||||
|
}
|
||||||
|
if (acq != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vkResetFences(device_, 1, &in_flight_);
|
||||||
|
|
||||||
|
VkCommandBuffer cb = cmd_;
|
||||||
|
vkResetCommandBuffer(cb, 0);
|
||||||
|
VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||||||
|
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||||
|
vkBeginCommandBuffer(cb, &bi);
|
||||||
|
|
||||||
|
barrier(cb, images_[idx], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0,
|
||||||
|
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||||
|
|
||||||
|
std::uint8_t r = 0, g = 0, b = 0;
|
||||||
|
frame_to_rgb(frame, r, g, b); // whole image encodes the frame -> animates + decodable
|
||||||
|
VkClearColorValue cc{};
|
||||||
|
cc.float32[0] = r / 255.0f;
|
||||||
|
cc.float32[1] = g / 255.0f;
|
||||||
|
cc.float32[2] = b / 255.0f;
|
||||||
|
cc.float32[3] = 1.0f;
|
||||||
|
VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||||
|
vkCmdClearColorImage(cb, images_[idx], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &cc, 1, &range);
|
||||||
|
|
||||||
|
barrier(cb, images_[idx], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||||
|
VK_ACCESS_TRANSFER_WRITE_BIT, 0, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
|
||||||
|
vkEndCommandBuffer(cb);
|
||||||
|
|
||||||
|
VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||||
|
VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||||
|
si.waitSemaphoreCount = 1;
|
||||||
|
si.pWaitSemaphores = &acquire_sem_;
|
||||||
|
si.pWaitDstStageMask = &wait_stage;
|
||||||
|
si.commandBufferCount = 1;
|
||||||
|
si.pCommandBuffers = &cb;
|
||||||
|
si.signalSemaphoreCount = 1;
|
||||||
|
si.pSignalSemaphores = &submit_sem_;
|
||||||
|
vkQueueSubmit(queue_, 1, &si, in_flight_);
|
||||||
|
|
||||||
|
VkPresentInfoKHR pi{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
||||||
|
pi.waitSemaphoreCount = 1;
|
||||||
|
pi.pWaitSemaphores = &submit_sem_;
|
||||||
|
pi.swapchainCount = 1;
|
||||||
|
pi.pSwapchains = &swapchain_;
|
||||||
|
pi.pImageIndices = &idx;
|
||||||
|
vkQueuePresentKHR(queue_, &pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char* name() const override
|
||||||
|
{
|
||||||
|
return "vk";
|
||||||
|
}
|
||||||
|
|
||||||
|
~VkBackend() override
|
||||||
|
{
|
||||||
|
if (device_ != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
vkDeviceWaitIdle(device_);
|
||||||
|
if (in_flight_ != VK_NULL_HANDLE)
|
||||||
|
vkDestroyFence(device_, in_flight_, nullptr);
|
||||||
|
if (acquire_sem_ != VK_NULL_HANDLE)
|
||||||
|
vkDestroySemaphore(device_, acquire_sem_, nullptr);
|
||||||
|
if (submit_sem_ != VK_NULL_HANDLE)
|
||||||
|
vkDestroySemaphore(device_, submit_sem_, nullptr);
|
||||||
|
if (pool_ != VK_NULL_HANDLE)
|
||||||
|
vkDestroyCommandPool(device_, pool_, nullptr);
|
||||||
|
if (swapchain_ != VK_NULL_HANDLE)
|
||||||
|
vkDestroySwapchainKHR(device_, swapchain_, nullptr);
|
||||||
|
vkDestroyDevice(device_, nullptr);
|
||||||
|
}
|
||||||
|
if (surface_ != VK_NULL_HANDLE)
|
||||||
|
vkDestroySurfaceKHR(instance_, surface_, nullptr);
|
||||||
|
if (instance_ != VK_NULL_HANDLE)
|
||||||
|
vkDestroyInstance(instance_, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool pick_device()
|
||||||
|
{
|
||||||
|
std::uint32_t n = 0;
|
||||||
|
vkEnumeratePhysicalDevices(instance_, &n, nullptr);
|
||||||
|
std::vector<VkPhysicalDevice> devs(n);
|
||||||
|
vkEnumeratePhysicalDevices(instance_, &n, devs.data());
|
||||||
|
for (VkPhysicalDevice pd : devs)
|
||||||
|
{
|
||||||
|
std::uint32_t qn = 0;
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(pd, &qn, nullptr);
|
||||||
|
std::vector<VkQueueFamilyProperties> qf(qn);
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(pd, &qn, qf.data());
|
||||||
|
for (std::uint32_t i = 0; i < qn; ++i)
|
||||||
|
{
|
||||||
|
VkBool32 present = VK_FALSE;
|
||||||
|
vkGetPhysicalDeviceSurfaceSupportKHR(pd, i, surface_, &present);
|
||||||
|
if ((qf[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && present)
|
||||||
|
{
|
||||||
|
phys_ = pd;
|
||||||
|
qfam_ = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool create_device()
|
||||||
|
{
|
||||||
|
float prio = 1.0f;
|
||||||
|
VkDeviceQueueCreateInfo qci{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
|
||||||
|
qci.queueFamilyIndex = qfam_;
|
||||||
|
qci.queueCount = 1;
|
||||||
|
qci.pQueuePriorities = &prio;
|
||||||
|
const char* dev_ext[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
||||||
|
VkDeviceCreateInfo dci{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
||||||
|
dci.queueCreateInfoCount = 1;
|
||||||
|
dci.pQueueCreateInfos = &qci;
|
||||||
|
dci.enabledExtensionCount = 1;
|
||||||
|
dci.ppEnabledExtensionNames = dev_ext;
|
||||||
|
if (vkCreateDevice(phys_, &dci, nullptr, &device_) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
volkLoadDevice(device_);
|
||||||
|
vkGetDeviceQueue(device_, qfam_, 0, &queue_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool create_swapchain()
|
||||||
|
{
|
||||||
|
VkSurfaceCapabilitiesKHR caps{};
|
||||||
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(phys_, surface_, &caps);
|
||||||
|
|
||||||
|
std::uint32_t fn = 0;
|
||||||
|
vkGetPhysicalDeviceSurfaceFormatsKHR(phys_, surface_, &fn, nullptr);
|
||||||
|
std::vector<VkSurfaceFormatKHR> formats(fn);
|
||||||
|
vkGetPhysicalDeviceSurfaceFormatsKHR(phys_, surface_, &fn, formats.data());
|
||||||
|
VkSurfaceFormatKHR chosen = formats.empty() ? VkSurfaceFormatKHR{VK_FORMAT_B8G8R8A8_UNORM,
|
||||||
|
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}
|
||||||
|
: formats[0];
|
||||||
|
for (const VkSurfaceFormatKHR& f : formats)
|
||||||
|
{
|
||||||
|
if (f.format == VK_FORMAT_B8G8R8A8_UNORM || f.format == VK_FORMAT_R8G8B8A8_UNORM)
|
||||||
|
{
|
||||||
|
chosen = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format_ = chosen.format;
|
||||||
|
|
||||||
|
std::uint32_t want = caps.minImageCount + 1;
|
||||||
|
if (caps.maxImageCount > 0 && want > caps.maxImageCount)
|
||||||
|
{
|
||||||
|
want = caps.maxImageCount;
|
||||||
|
}
|
||||||
|
VkSwapchainCreateInfoKHR sc{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
|
||||||
|
sc.surface = surface_;
|
||||||
|
sc.minImageCount = want;
|
||||||
|
sc.imageFormat = chosen.format;
|
||||||
|
sc.imageColorSpace = chosen.colorSpace;
|
||||||
|
sc.imageExtent = caps.currentExtent.width != 0xFFFFFFFFu
|
||||||
|
? caps.currentExtent
|
||||||
|
: VkExtent2D{width_, height_};
|
||||||
|
sc.imageArrayLayers = 1;
|
||||||
|
// TRANSFER_DST so we can clear it; TRANSFER_SRC so the capture hook can copy it out.
|
||||||
|
sc.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||||
|
sc.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
|
sc.preTransform = caps.currentTransform;
|
||||||
|
sc.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||||
|
sc.presentMode = VK_PRESENT_MODE_FIFO_KHR; // vsync, universally supported
|
||||||
|
sc.clipped = VK_TRUE;
|
||||||
|
if (vkCreateSwapchainKHR(device_, &sc, nullptr, &swapchain_) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::uint32_t in = 0;
|
||||||
|
vkGetSwapchainImagesKHR(device_, swapchain_, &in, nullptr);
|
||||||
|
images_.resize(in);
|
||||||
|
vkGetSwapchainImagesKHR(device_, swapchain_, &in, images_.data());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool create_commands()
|
||||||
|
{
|
||||||
|
VkCommandPoolCreateInfo pci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||||||
|
pci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||||
|
pci.queueFamilyIndex = qfam_;
|
||||||
|
if (vkCreateCommandPool(device_, &pci, nullptr, &pool_) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VkCommandBufferAllocateInfo ai{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
||||||
|
ai.commandPool = pool_;
|
||||||
|
ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
|
ai.commandBufferCount = 1;
|
||||||
|
if (vkAllocateCommandBuffers(device_, &ai, &cmd_) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VkSemaphoreCreateInfo si{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||||
|
VkFenceCreateInfo fi{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||||
|
fi.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||||
|
return vkCreateSemaphore(device_, &si, nullptr, &acquire_sem_) == VK_SUCCESS &&
|
||||||
|
vkCreateSemaphore(device_, &si, nullptr, &submit_sem_) == VK_SUCCESS &&
|
||||||
|
vkCreateFence(device_, &fi, nullptr, &in_flight_) == VK_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void barrier(VkCommandBuffer cb, VkImage img, VkImageLayout from, VkImageLayout to,
|
||||||
|
VkAccessFlags src_access, VkAccessFlags dst_access, VkPipelineStageFlags src_stage,
|
||||||
|
VkPipelineStageFlags dst_stage)
|
||||||
|
{
|
||||||
|
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||||
|
b.srcAccessMask = src_access;
|
||||||
|
b.dstAccessMask = dst_access;
|
||||||
|
b.oldLayout = from;
|
||||||
|
b.newLayout = to;
|
||||||
|
b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
b.image = img;
|
||||||
|
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||||
|
vkCmdPipelineBarrier(cb, src_stage, dst_stage, 0, 0, nullptr, 0, nullptr, 1, &b);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t width_ = 0;
|
||||||
|
std::uint32_t height_ = 0;
|
||||||
|
VkInstance instance_ = VK_NULL_HANDLE;
|
||||||
|
VkSurfaceKHR surface_ = VK_NULL_HANDLE;
|
||||||
|
VkPhysicalDevice phys_ = VK_NULL_HANDLE;
|
||||||
|
std::uint32_t qfam_ = 0;
|
||||||
|
VkDevice device_ = VK_NULL_HANDLE;
|
||||||
|
VkQueue queue_ = VK_NULL_HANDLE;
|
||||||
|
VkSwapchainKHR swapchain_ = VK_NULL_HANDLE;
|
||||||
|
VkFormat format_ = VK_FORMAT_UNDEFINED;
|
||||||
|
std::vector<VkImage> images_;
|
||||||
|
VkCommandPool pool_ = VK_NULL_HANDLE;
|
||||||
|
VkCommandBuffer cmd_ = VK_NULL_HANDLE;
|
||||||
|
VkSemaphore acquire_sem_ = VK_NULL_HANDLE;
|
||||||
|
VkSemaphore submit_sem_ = VK_NULL_HANDLE;
|
||||||
|
VkFence in_flight_ = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<RenderBackend> create_vk_backend()
|
||||||
|
{
|
||||||
|
return std::make_unique<VkBackend>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace coop::mock
|
||||||
Reference in New Issue
Block a user