From 4b2fe93080e6bd5edcb667571d9f07566c244cf5 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Wed, 16 Sep 2026 07:43:05 +0300 Subject: [PATCH] feat(agc): add standalone shader test for libSceAgcDriver --- README.md | 2 + core/libs/prx/libSceAgcDriver/CMakeLists.txt | 58 ++++++++++ .../libs/prx/libSceAgcDriver/tests/Visual.cpp | 106 ++++++++++++++++++ .../tests/shaders/Triangle.frag | 6 + .../tests/shaders/Triangle.vert | 9 ++ 5 files changed, 181 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/Visual.cpp create mode 100644 core/libs/prx/libSceAgcDriver/tests/shaders/Triangle.frag create mode 100644 core/libs/prx/libSceAgcDriver/tests/shaders/Triangle.vert diff --git a/README.md b/README.md index 7ad8b57dd..6254d7510 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Execution reaches `_start`, stack unwinding and exception handling tables are bu Audio output and video output initialization pass. Reaches the `main` function with render loop (deep call chain, ~64KB stack frame function) - init and shader loading from disk fully complete. +A standalone [SPIR-V triangle test](core/libs/prx/libSceAgcDriver/tests/Visual.cpp) passes GPU readback checks and presents a frame through the [libSceAgcDriver](core/libs/prx/libSceAgcDriver). The first application draw passes driver state validation and reaches shader recompilation. Application shader execution remains unverified without [recompiler](core/shader/recompiler/Recompiler.cpp). + Now: `ShaderRecompiler::Recompile: InstructionTranslator::Translate not implementedgi`. ## Disclaimer diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 3ec74d28b..9493d3f33 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -67,3 +67,61 @@ target_include_directories(agc_driver_graphics_tests PRIVATE ${LIBS_INCLUDE_DIR} target_link_libraries(agc_driver_graphics_tests PRIVATE libc) set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_graphics_tests) + +option(AGC_BUILD_VISUAL_TEST "Enable the standalone AGC SPIR-V visual test" OFF) +if(AGC_BUILD_VISUAL_TEST) + function(add_agc_test_glslang) + set(BUILD_SHARED_LIBS OFF) + set(BUILD_EXTERNAL OFF) + set(GLSLANG_TESTS OFF) + set(GLSLANG_ENABLE_INSTALL OFF) + set(ENABLE_GLSLANG_BINARIES ON) + set(ENABLE_SPIRV ON) + set(ENABLE_SPVREMAPPER OFF) + set(ENABLE_OPT OFF) + add_subdirectory("${CMAKE_SOURCE_DIR}/3rdparty/glslang" "${CMAKE_BINARY_DIR}/3rdparty/glslang" EXCLUDE_FROM_ALL) + endfunction() + add_agc_test_glslang() + set(visualShaders) + foreach(stage IN ITEMS vert frag) + set(shaderOutput "${CMAKE_CURRENT_BINARY_DIR}/Triangle.${stage}.spv") + add_custom_command(OUTPUT "${shaderOutput}" + COMMAND "$" -V --target-env vulkan1.1 -o "${shaderOutput}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/Triangle.${stage}" + DEPENDS glslang-standalone "${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/Triangle.${stage}" + VERBATIM) + list(APPEND visualShaders "${shaderOutput}") + endforeach() + add_executable(agc_driver_visual_test EXCLUDE_FROM_ALL + tests/Visual.cpp + Execution/src/VulkanDevice.cpp + Execution/src/GuestMemory.cpp + Graphics/src/Draw.cpp + Graphics/src/Pipeline.cpp + Graphics/src/Resources.cpp + Graphics/src/ShaderResources.cpp + Graphics/src/ShaderValidation.cpp + ${visualShaders}) + target_include_directories(agc_driver_visual_test PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include ${CMAKE_SOURCE_DIR}/3rdparty/SPIRV-Headers/include) + target_link_libraries(agc_driver_visual_test PRIVATE SDL2-static libc) + set_target_properties(agc_driver_visual_test PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(agc_driver_visual_test) + if(WIN32) + set(visualRuntimeFiles "$") + if(MINGW) + get_filename_component(visualCompilerDirectory "${CMAKE_CXX_COMPILER}" DIRECTORY) + foreach(runtimeName IN ITEMS libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll) + set(runtimePath "${visualCompilerDirectory}/${runtimeName}") + if(NOT EXISTS "${runtimePath}") + message(FATAL_ERROR "Cannot locate MinGW runtime: ${runtimeName}") + endif() + list(APPEND visualRuntimeFiles "${runtimePath}") + endforeach() + endif() + add_custom_target(agc_driver_visual_runtime + COMMAND "${CMAKE_COMMAND}" -E copy_if_different ${visualRuntimeFiles} "$" + DEPENDS libc + COMMAND_EXPAND_LISTS + VERBATIM) + add_dependencies(agc_driver_visual_test agc_driver_visual_runtime) + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/Visual.cpp b/core/libs/prx/libSceAgcDriver/tests/Visual.cpp new file mode 100644 index 000000000..3fcbf43cd --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/Visual.cpp @@ -0,0 +1,106 @@ +#define SDL_MAIN_HANDLED +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +constexpr std::uint32_t Width = 640; +constexpr std::uint32_t Height = 480; +alignas(256) std::array Pixels{}; + +ShaderRecompiler::RecompileResult LoadShader(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + Require(file.is_open(), "cannot open test SPIR-V: " + path.string()); + const auto size = file.tellg(); + Require(size >= 20 && size <= 1024 * 1024 && size % 4 == 0, "invalid test SPIR-V size"); + ShaderRecompiler::RecompileResult result; + result.spirv.resize(static_cast(size) / 4); + file.seekg(0); + Require(static_cast(file.read(reinterpret_cast(result.spirv.data()), size)), "cannot read test SPIR-V"); + return result; +} + +void Run(SDL_Window* window, const std::filesystem::path& directory, bool verifyOnly) { + unsigned count = 0; + Require(SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr) == SDL_TRUE, SDL_GetError()); + std::vector extensions(count); + Require(SDL_Vulkan_GetInstanceExtensions(window, &count, extensions.data()) == SDL_TRUE, SDL_GetError()); + const AgcDriver::PresentationWindow presentation{window, extensions, [](void* context, VkInstance instance) { + VkSurfaceKHR surface = VK_NULL_HANDLE; + Require(SDL_Vulkan_CreateSurface(static_cast(context), instance, &surface) == SDL_TRUE, SDL_GetError()); + return surface; + }, Width, Height}; + AgcDriver::VulkanDevice device(&presentation); + auto vertex = LoadShader(directory / "Triangle.vert.spv"); + auto fragment = LoadShader(directory / "Triangle.frag.spv"); + const std::array shaders{{ + {ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, + {ShaderRecompiler::ShaderStage::Fragment, &fragment, AgcDriver::Graphics::StagePushConstantBytes} + }}; + for (std::size_t i = 0; i < Pixels.size(); i += 4) { + Pixels[i] = std::byte{16}; + Pixels[i + 1] = std::byte{24}; + Pixels[i + 2] = std::byte{40}; + Pixels[i + 3] = std::byte{255}; + } + AgcDriver::Graphics::State state{}; + state.stages.path = AgcDriver::Graphics::ShaderPath::Vertex; + state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size()}; + state.hasColorTarget = true; + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.scissor = {{0, 0}, {Width, Height}}; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + const std::array indices{0, 1, 2}; + const AgcDriver::Pm4::IndexedDraw draw{reinterpret_cast(indices.data()), 3, 2, 1, 0}; + device.DrawIndexed(state, draw, shaders); + const auto center = (Height / 2 * Width + Width / 2) * 4; + Require(std::to_integer(Pixels[center]) > 30 && std::to_integer(Pixels[center + 1]) > 30 && std::to_integer(Pixels[center + 2]) > 30, "GPU readback: triangle center was not rendered"); + Require(Pixels[0] == std::byte{16} && Pixels[1] == std::byte{24} && Pixels[2] == std::byte{40}, "GPU readback: background changed"); + device.WaitPresented(device.PresentPixels(Width, Height, Pixels)); + std::cout << "SPIR-V triangle rendered, GPU readback verified, frame presented. Close the window or press Escape.\n" << std::flush; + if (!verifyOnly) { + bool running = true; + while (running) { + SDL_Event event{}; + if (SDL_WaitEventTimeout(&event, 100)) { + if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) running = false; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_EXPOSED) device.WaitPresented(device.PresentPixels(Width, Height, Pixels)); + } + } + } + device.WaitIdle(); +} + +} + +int main(int argc, char** argv) { + try { + Require(argc == 1 || (argc == 2 && std::string_view(argv[1]) == "--verify"), "usage: agc_driver_visual_test [--verify]"); + SDL_SetMainReady(); + Require(SDL_Init(SDL_INIT_VIDEO) == 0, SDL_GetError()); + { + const auto window = std::unique_ptr(SDL_CreateWindow("AnyPS5 AGC - SPIR-V triangle test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_WINDOW_VULKAN | SDL_WINDOW_SHOWN), SDL_DestroyWindow); + Require(window != nullptr, SDL_GetError()); + const auto base = std::unique_ptr(SDL_GetBasePath(), SDL_free); + Require(base != nullptr, SDL_GetError()); + Run(window.get(), std::filesystem::path(base.get()), argc == 2); + } + SDL_Quit(); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + SDL_Quit(); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/shaders/Triangle.frag b/core/libs/prx/libSceAgcDriver/tests/shaders/Triangle.frag new file mode 100644 index 000000000..930658e51 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/shaders/Triangle.frag @@ -0,0 +1,6 @@ +#version 450 +layout(location = 0) in vec3 color; +layout(location = 0) out vec4 outputColor; +void main() { + outputColor = vec4(color, 1.0); +} diff --git a/core/libs/prx/libSceAgcDriver/tests/shaders/Triangle.vert b/core/libs/prx/libSceAgcDriver/tests/shaders/Triangle.vert new file mode 100644 index 000000000..d3b4891f4 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/shaders/Triangle.vert @@ -0,0 +1,9 @@ +#version 450 +out gl_PerVertex { vec4 gl_Position; }; +layout(location = 0) out vec3 color; +void main() { + vec2 positions[3] = vec2[](vec2(-0.8, -0.7), vec2(0.8, -0.7), vec2(0.0, 0.8)); + vec3 colors[3] = vec3[](vec3(1.0, 0.15, 0.1), vec3(0.1, 1.0, 0.2), vec3(0.15, 0.3, 1.0)); + gl_Position = vec4(positions[gl_VertexIndex], 0.5, 1.0); + color = colors[gl_VertexIndex]; +}