feat(agc): add standalone shader test for libSceAgcDriver

This commit is contained in:
boykopovar 2026-09-16 07:43:05 +03:00
parent 9c78d10044
commit 4b2fe93080
5 changed files with 181 additions and 0 deletions

View File

@ -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

View File

@ -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 "$<TARGET_FILE:glslang-standalone>" -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 "$<TARGET_FILE:libc>")
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} "$<TARGET_FILE_DIR:agc_driver_visual_test>"
DEPENDS libc
COMMAND_EXPAND_LISTS
VERBATIM)
add_dependencies(agc_driver_visual_test agc_driver_visual_runtime)
endif()
endif()

View File

@ -0,0 +1,106 @@
#define SDL_MAIN_HANDLED
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include <SDL.h>
#include <SDL_vulkan.h>
#include <array>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <string_view>
namespace {
using AgcDriver::Graphics::Require;
constexpr std::uint32_t Width = 640;
constexpr std::uint32_t Height = 480;
alignas(256) std::array<std::byte, Width * Height * 4> 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<std::size_t>(size) / 4);
file.seekg(0);
Require(static_cast<bool>(file.read(reinterpret_cast<char*>(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<const char*> 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<SDL_Window*>(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<AgcDriver::Graphics::CompiledShader, 2> 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<std::uintptr_t>(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<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
state.scissor = {{0, 0}, {Width, Height}};
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
state.blend.colorWriteMask = 15;
const std::array<std::uint16_t, 3> indices{0, 1, 2};
const AgcDriver::Pm4::IndexedDraw draw{reinterpret_cast<std::uintptr_t>(indices.data()), 3, 2, 1, 0};
device.DrawIndexed(state, draw, shaders);
const auto center = (Height / 2 * Width + Width / 2) * 4;
Require(std::to_integer<unsigned>(Pixels[center]) > 30 && std::to_integer<unsigned>(Pixels[center + 1]) > 30 && std::to_integer<unsigned>(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_Window, decltype(&SDL_DestroyWindow)>(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<char, decltype(&SDL_free)>(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;
}
}

View File

@ -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);
}

View File

@ -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];
}