Merge pull request #24 from SamG-Coder/main
fix: support SDK startup, linear framebuffers and indexed draws Fix SDK startup with an empty allocator replacement table, add linear framebuffer presentation through the current presentation path, and implement explicit indexed draws with packet validation. Retain buffer-address regression coverage for the correction already upstream, and restrict the POSIX exception-runtime test to Unix so Windows test builds succeed. Also fix two issues reproduced after rebasing: render-target format lookup probed unsupported formats by repeatedly throwing exceptions, costing about 78 ms per draw; the lookup now searches supported entries directly. Recorder buffers now use Windows FLS cleanup to avoid MinGW freeing emulated TLS storage before running their destructors. Cross-DLL tests check independent values and destruction on standard and native Windows threads.
This commit is contained in:
commit
d1238299b7
|
|
@ -109,6 +109,34 @@ endforeach()
|
|||
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_library(host_thread_local_test_library SHARED tests/HostThreadLocalLibrary.cpp)
|
||||
target_include_directories(host_thread_local_test_library PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_executable(host_thread_local_tests tests/HostThreadLocal.cpp)
|
||||
target_link_libraries(host_thread_local_tests PRIVATE host_thread_local_test_library)
|
||||
if(MINGW)
|
||||
target_compile_options(host_thread_local_test_library PRIVATE -fasynchronous-unwind-tables)
|
||||
target_compile_options(host_thread_local_tests PRIVATE -fasynchronous-unwind-tables)
|
||||
endif()
|
||||
add_test(NAME host_thread_local COMMAND host_thread_local_tests)
|
||||
set_tests_properties(host_thread_local PROPERTIES TIMEOUT 15)
|
||||
add_executable(texture_format_lookup_tests tests/TextureFormatLookup.cpp)
|
||||
target_include_directories(texture_format_lookup_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
|
||||
target_link_libraries(texture_format_lookup_tests PRIVATE libSceAgcDriver libc)
|
||||
configure_windows_unwind(texture_format_lookup_tests)
|
||||
add_test(NAME texture_format_lookup COMMAND texture_format_lookup_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(texture_format_lookup PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_executable(linear_display_buffer_tests tests/LinearDisplayBuffer.cpp)
|
||||
target_include_directories(linear_display_buffer_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(linear_display_buffer_tests PRIVATE libSceAgcDriver libc)
|
||||
configure_windows_unwind(linear_display_buffer_tests)
|
||||
add_test(NAME linear_display_buffer COMMAND linear_display_buffer_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(linear_display_buffer PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_executable(guest_rendering_wait_tests tests/GuestRenderingWait.cpp)
|
||||
target_include_directories(guest_rendering_wait_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_rendering_wait_tests PRIVATE libSceAgcDriver libc)
|
||||
|
|
@ -127,6 +155,23 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_buffer_reuse PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_executable(guest_indexed_draw_tests tests/GuestIndexedDraw.cpp)
|
||||
add_executable(shader_buffer_address_tests tests/ShaderBufferAddress.cpp)
|
||||
target_link_libraries(shader_buffer_address_tests PRIVATE shader_recompiler_agc_driver libc)
|
||||
configure_windows_unwind(shader_buffer_address_tests)
|
||||
add_test(NAME shader_buffer_address COMMAND shader_buffer_address_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(shader_buffer_address PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
target_include_directories(guest_indexed_draw_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_indexed_draw_tests PRIVATE libSceAgc libSceAgcDriver libc)
|
||||
configure_windows_unwind(guest_indexed_draw_tests)
|
||||
add_test(NAME guest_indexed_draw COMMAND guest_indexed_draw_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_indexed_draw PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_executable(guest_shader_link_tests tests/GuestShaderLink.cpp)
|
||||
target_include_directories(guest_shader_link_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_shader_link_tests PRIVATE libSceAgc libc)
|
||||
|
|
@ -346,22 +391,29 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_strings PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_executable(exception_runtime_tests tests/ExceptionRuntime.cpp)
|
||||
target_include_directories(exception_runtime_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(exception_runtime_tests PRIVATE libc)
|
||||
configure_windows_unwind(exception_runtime_tests)
|
||||
add_test(NAME exception_runtime COMMAND exception_runtime_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(exception_runtime PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
if(UNIX)
|
||||
add_executable(exception_runtime_tests tests/ExceptionRuntime.cpp)
|
||||
target_include_directories(exception_runtime_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(exception_runtime_tests PRIVATE libc)
|
||||
add_test(NAME exception_runtime COMMAND exception_runtime_tests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable(application_heap_tests EXCLUDE_FROM_ALL tests/ApplicationHeap.cpp prx/libc/src/ApplicationHeap.cpp)
|
||||
add_executable(application_heap_tests EXCLUDE_FROM_ALL tests/ApplicationHeap.cpp)
|
||||
target_include_directories(application_heap_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set_target_properties(application_heap_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
target_link_libraries(application_heap_tests PRIVATE libc)
|
||||
configure_windows_unwind(application_heap_tests)
|
||||
set_target_properties(application_heap_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(application_heap_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
|
||||
add_test(NAME application_heap_replacement COMMAND application_heap_tests)
|
||||
add_test(NAME application_heap_invalid_metadata COMMAND application_heap_tests invalid)
|
||||
add_test(NAME application_heap_default COMMAND application_heap_tests default)
|
||||
if(WIN32)
|
||||
set_tests_properties(application_heap_replacement application_heap_invalid_metadata application_heap_default
|
||||
PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_executable(guest_locale_tests EXCLUDE_FROM_ALL tests/GuestLocale.cpp)
|
||||
target_include_directories(guest_locale_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
|
|
|||
|
|
@ -9,13 +9,10 @@
|
|||
|
||||
extern "C" {
|
||||
|
||||
uint32_t* APS5_VABI sceAgcDcbDrawIndex(CommandBuffer* buf, uint32_t index_count, const volatile void* index_addr, uint64_t modifier) {
|
||||
(void)buf;
|
||||
(void)index_count;
|
||||
(void)index_addr;
|
||||
(void)modifier;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return nullptr;
|
||||
std::uint32_t* APS5_VABI sceAgcDcbDrawIndex(CommandBuffer* buf, std::uint32_t indexCount, const volatile void* indexAddress, std::uint64_t modifier) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(indexAddress);
|
||||
Agc::Command::CheckAddress(address, 1, __func__);
|
||||
return Agc::Command::Emit(buf, 0x27u, {indexCount, static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u), indexCount, Agc::Command::DrawInitiator(modifier, true, __func__)}, __func__);
|
||||
}
|
||||
|
||||
std::uint32_t APS5_VABI sceAgcDcbDrawIndexGetSize() {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ std::uint32_t APS5_VABI sceAgcDcbSetIndexBufferGetSize() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
uint32_t* APS5_VABI sceAgcDcbSetIndexCount(CommandBuffer* buf, uint32_t index_count) {
|
||||
return Agc::Command::Emit(buf, 0x13u, {index_count}, __func__);
|
||||
std::uint32_t* APS5_VABI sceAgcDcbSetIndexCount(CommandBuffer* buf, std::uint32_t indexCount) {
|
||||
return Agc::Command::Emit(buf, 0x13u, {indexCount}, __func__);
|
||||
}
|
||||
|
||||
std::uint32_t APS5_VABI sceAgcDcbSetIndexCountGetSize() {
|
||||
return 2;
|
||||
return 8;
|
||||
}
|
||||
|
||||
std::uint32_t* APS5_VABI sceAgcDcbSetIndexSize(CommandBuffer* buf, std::uint8_t indexSize, std::uint8_t cachePolicy) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ struct DisplayBuffer {
|
|||
std::uint64_t pixelFormat;
|
||||
std::uint32_t width;
|
||||
std::uint32_t height;
|
||||
std::uint32_t tilingMode = 0;
|
||||
std::uint32_t pitchInPixel = 0;
|
||||
};
|
||||
|
||||
std::size_t DisplayBufferSize(const DisplayBuffer& buffer);
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ struct DrawArguments {
|
|||
DrawArguments ReadDrawArguments(const DrawParameters::IndirectDraw& indirect, std::uint32_t record);
|
||||
std::uint32_t ReadDrawCount(const DrawParameters::IndirectDraw& indirect);
|
||||
inline bool IndirectDrawOpcode(std::uint32_t opcode) { return opcode == 0x24 || opcode == 0x25 || opcode == 0x2c || opcode == 0x38; }
|
||||
inline bool DrawOpcode(std::uint32_t opcode) { return opcode == 0x2d || opcode == 0x35 || IndirectDrawOpcode(opcode); }
|
||||
inline bool DrawOpcode(std::uint32_t opcode) { return opcode == 0x27 || opcode == 0x2d || opcode == 0x35 || IndirectDrawOpcode(opcode); }
|
||||
|
||||
std::string Name(std::uint32_t header);
|
||||
// A PM4 type-2 packet is a one-dword filler (command-buffer padding); type 3 and type 0 carry a
|
||||
|
|
|
|||
|
|
@ -38,14 +38,19 @@ std::size_t DisplayBufferSize(const DisplayBuffer& buffer) {
|
|||
throw std::runtime_error(message);
|
||||
}
|
||||
require(buffer.address != 0 && (buffer.address & 65535u) == 0, "VideoOut: display buffer requires 64 KiB alignment");
|
||||
const auto size = static_cast<std::uint64_t>((buffer.width + 127u) / 128u) * ((buffer.height + 127u) / 128u) * 65536u;
|
||||
require(buffer.tilingMode <= 1, "VideoOut: unsupported display tiling mode");
|
||||
require(buffer.tilingMode != 0 || buffer.pitchInPixel == 0, "VideoOut: tiled display pitch is unsupported");
|
||||
const auto pitch = buffer.pitchInPixel == 0 ? buffer.width : buffer.pitchInPixel;
|
||||
require(pitch >= buffer.width && pitch <= 16384, "VideoOut: invalid linear display pitch");
|
||||
const auto size = buffer.tilingMode == 1 ? static_cast<std::uint64_t>(pitch) * buffer.height * 4u
|
||||
: static_cast<std::uint64_t>((buffer.width + 127u) / 128u) * ((buffer.height + 127u) / 128u) * 65536u;
|
||||
require(size <= std::numeric_limits<std::size_t>::max() && size <= std::numeric_limits<std::uintptr_t>::max() - buffer.address, "VideoOut: display buffer range overflow");
|
||||
return static_cast<std::size_t>(size);
|
||||
}
|
||||
|
||||
std::vector<std::byte> DecodeDisplayBuffer(const DisplayBuffer& buffer, std::span<const std::byte> source) {
|
||||
PerformanceTimer timing("DisplayBuffer.Decode");
|
||||
require(source.size() == DisplayBufferSize(buffer), "VideoOut: invalid tiled display buffer size");
|
||||
require(source.size() == DisplayBufferSize(buffer), "VideoOut: invalid display buffer size");
|
||||
std::vector<std::byte> pixels(static_cast<std::size_t>(buffer.width) * buffer.height * 4);
|
||||
const auto blocksPerRow = (buffer.width + 127u) / 128u;
|
||||
const bool rgba = baseFormat(buffer.pixelFormat) == PixelFormatR8G8B8A8;
|
||||
|
|
@ -55,7 +60,9 @@ std::vector<std::byte> DecodeDisplayBuffer(const DisplayBuffer& buffer, std::spa
|
|||
timing.Mark("validate_allocate");
|
||||
for (std::uint32_t y = 0; y < buffer.height; ++y) {
|
||||
for (std::uint32_t x = 0; x < buffer.width; ++x) {
|
||||
const auto tiled = (static_cast<std::size_t>(y / 128u) * blocksPerRow + x / 128u) * 65536u + tileOffset(x, y);
|
||||
const auto pitch = buffer.pitchInPixel == 0 ? buffer.width : buffer.pitchInPixel;
|
||||
const auto tiled = buffer.tilingMode == 1 ? (static_cast<std::size_t>(y) * pitch + x) * 4u
|
||||
: (static_cast<std::size_t>(y / 128u) * blocksPerRow + x / 128u) * 65536u + tileOffset(x, y);
|
||||
const auto linear = (static_cast<std::size_t>(y) * buffer.width + x) * 4;
|
||||
if (tenBit) {
|
||||
std::uint32_t word = 0;
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ bool NeedsRecordedLabels(std::uint32_t header) {
|
|||
bool PacketLocksItself(std::uint32_t header) {
|
||||
if (header == FlipPacketHeader) return true;
|
||||
switch ((header >> 8u) & 0xffu) {
|
||||
case 0x15: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: return true;
|
||||
case 0x15: case 0x16: case 0x27: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,14 +137,14 @@ std::string_view UnsupportedReason(std::uint32_t header) {
|
|||
}
|
||||
}
|
||||
switch (opcode) {
|
||||
case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26:
|
||||
case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26: case 0x27:
|
||||
case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x46: case 0x50:
|
||||
case 0x58: case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a:
|
||||
case 0x81: case 0x83: case 0x9f: return {};
|
||||
case 0x24: case 0x25: case 0x2c: case 0x38:
|
||||
if (IndirectDrawsDisabled()) return "graphics draw, shader stages and guest render-target materialization are not implemented";
|
||||
return {};
|
||||
case 0x27: case 0x3a: case 0x8d:
|
||||
case 0x3a: case 0x8d:
|
||||
return "graphics draw, shader stages and guest render-target materialization are not implemented";
|
||||
case 0x20: return "GPU query predication is not implemented";
|
||||
case 0x22: return "conditional command execution and conditional flip reservation are not implemented";
|
||||
|
|
@ -219,6 +219,13 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
|
|||
case 0x12: graphics(); size(2); require((packet[1] & ~0xfu) == 0, "unsupported CLEAR_STATE payload bits"); break;
|
||||
case 0x13: case 0x2f: graphics(); size(2); break;
|
||||
case 0x26: graphics(); size(3); break;
|
||||
case 0x27:
|
||||
graphics();
|
||||
size(6);
|
||||
require(packet[3] <= 0xffffu, "unsupported index address bits");
|
||||
require(packet[4] <= packet[1], "index count exceeds maximum index size");
|
||||
require((packet[5] & ~0x20u) == 0, "unsupported indexed draw flags");
|
||||
break;
|
||||
case 0x2a: graphics(); size(2); require(packet[1] <= 3, "unsupported index-type modifiers"); break;
|
||||
case 0x2d:
|
||||
graphics();
|
||||
|
|
@ -470,7 +477,7 @@ bool UsesGpuCacheBarrier(std::span<const std::uint32_t> packet) {
|
|||
|
||||
bool AccessesMemory(std::uint32_t header) {
|
||||
switch ((header >> 8u) & 0xffu) {
|
||||
case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true;
|
||||
case 0x27: case 0x3c: case 0x93: case 0x49: case 0x16: case 0x2d: case 0x35: case 0x24: case 0x25: case 0x2c: case 0x38: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -578,16 +585,21 @@ DrawParameters ResolveDraw(std::span<const std::uint32_t> packet, const QueueSta
|
|||
require(packet[1] == 0 || firstVertex <= std::numeric_limits<std::uint32_t>::max() - (packet[1] - 1u), "auto draw vertex range overflow");
|
||||
return {0, packet[1], 0, queue.instanceCount, packet[2] & 0x20u, false, firstVertex, 0};
|
||||
}
|
||||
require(((packet[0] >> 8u) & 0xffu) == 0x35, "expected DRAW_INDEX_OFFSET_2 packet");
|
||||
const auto indexSize = indexSizeOf(queue);
|
||||
const auto offset = static_cast<std::uint64_t>(packet[2]) * indexSize;
|
||||
require(offset <= std::numeric_limits<std::uint64_t>::max() - queue.indexBase, "index address overflow");
|
||||
const auto address = queue.indexBase + offset;
|
||||
const auto bytes = static_cast<std::uint64_t>(packet[3]) * indexSize;
|
||||
const bool explicitAddress = ((packet[0] >> 8u) & 0xffu) == 0x27;
|
||||
require(explicitAddress || ((packet[0] >> 8u) & 0xffu) == 0x35, "expected indexed draw packet");
|
||||
require(queue.indexType <= 2, "unsupported index type");
|
||||
const std::uint32_t indexSize = queue.indexType == 0 ? 2 : queue.indexType == 1 ? 4 : 1;
|
||||
const auto base = explicitAddress ? address(packet[2], packet[3]) : queue.indexBase;
|
||||
const auto count = explicitAddress ? packet[4] : packet[3];
|
||||
require(base != 0 && base % indexSize == 0, "null or misaligned index base");
|
||||
const auto offset = explicitAddress ? 0 : static_cast<std::uint64_t>(packet[2]) * indexSize;
|
||||
require(offset <= std::numeric_limits<std::uint64_t>::max() - base, "index address overflow");
|
||||
const auto address = base + offset;
|
||||
const auto bytes = static_cast<std::uint64_t>(count) * indexSize;
|
||||
require(bytes <= std::numeric_limits<std::size_t>::max(), "index range size overflow");
|
||||
GuestMemory::CheckRange(reinterpret_cast<const void*>(address), static_cast<std::size_t>(bytes), indexSize);
|
||||
APS5_LOG_OUT_DEBUG("ResolveDraw context targetMask=0x%x shaderMask=0x%x indexCount=%u indexType=%u instances=%u", queue.context.contains(0x8e) ? queue.context.at(0x8e) : 0u, queue.context.contains(0x8f) ? queue.context.at(0x8f) : 0u, packet[3], queue.indexType, queue.instanceCount);
|
||||
return {address, packet[3], indexSize, queue.instanceCount, packet[4]};
|
||||
APS5_LOG_OUT_DEBUG("ResolveDraw context targetMask=0x%x shaderMask=0x%x indexCount=%u indexType=%u instances=%u", queue.context.contains(0x8e) ? queue.context.at(0x8e) : 0u, queue.context.contains(0x8f) ? queue.context.at(0x8f) : 0u, count, queue.indexType, queue.instanceCount);
|
||||
return {address, count, indexSize, queue.instanceCount, packet.back()};
|
||||
}
|
||||
|
||||
void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
|
||||
|
|
|
|||
|
|
@ -1629,6 +1629,10 @@ FrameDumps& Dumps() {
|
|||
}
|
||||
|
||||
bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
|
||||
if (buffer.tilingMode == 1) {
|
||||
const auto pixels = ReadDisplayBuffer(buffer);
|
||||
return present(buffer.width, buffer.height, true, pixels);
|
||||
}
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
// The display buffer is usually a resident render target whose results are still on the GPU: it
|
||||
// is blitted from that image, which skips the write-back retile, the 33 MB read of guest memory,
|
||||
|
|
@ -1934,6 +1938,7 @@ VulkanDevice::PresentStatistics VulkanDevice::PresentCounts() {
|
|||
|
||||
bool VulkanDevice::PresentWaitsForSlots(const DisplayBuffer* buffer) const {
|
||||
if (buffer == nullptr || !state->SharedSlotInFlight()) return false;
|
||||
if (buffer->tilingMode == 1) return true;
|
||||
const auto& dumps = Dumps();
|
||||
if ((dumps.dumped < dumps.limit && !dumps.cpu) || NoResidentPresent()) return true;
|
||||
VkFilter filter = VK_FILTER_LINEAR;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@
|
|||
#endif
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
VkFormat ResolveTextureFormat(std::uint32_t guestFormat);
|
||||
std::optional<std::uint32_t> FindGuestTextureFormat(VkFormat format, std::uint32_t elementBytes);
|
||||
std::uint32_t BytesPerElement(std::uint32_t guestFormat);
|
||||
bool IsBlockCompressed(std::uint32_t guestFormat);
|
||||
std::uint32_t BlockWidth(std::uint32_t guestFormat);
|
||||
|
|
|
|||
|
|
@ -34,19 +34,7 @@ namespace {
|
|||
|
||||
// A guest texture format code with the same Vulkan format and texel size as a color buffer.
|
||||
std::uint32_t GuestFormatFor(VkFormat format, std::uint32_t elementBytes) {
|
||||
static std::mutex mutex;
|
||||
static std::map<std::pair<VkFormat, std::uint32_t>, std::uint32_t> known;
|
||||
std::lock_guard lock(mutex);
|
||||
if (const auto found = known.find({format, elementBytes}); found != known.end()) return found->second;
|
||||
for (std::uint32_t code = 0; code < 256; ++code) {
|
||||
try {
|
||||
if (!IsBlockCompressed(code) && ResolveTextureFormat(code) == format && BytesPerElement(code) == elementBytes) {
|
||||
known.emplace(std::make_pair(format, elementBytes), code);
|
||||
return code;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
}
|
||||
if (const auto guest = FindGuestTextureFormat(format, elementBytes)) return *guest;
|
||||
throw std::runtime_error("AGC graphics: no guest texture format matches the color buffer format " + std::to_string(static_cast<int>(format)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include "prx/libc/include/HostThreadLocal.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
|
|
@ -276,7 +277,8 @@ struct DeferredBatch {
|
|||
std::vector<std::shared_ptr<void>> kept;
|
||||
std::vector<std::function<void()>> completions;
|
||||
};
|
||||
thread_local std::vector<DeferredBatch> deferredBatches;
|
||||
struct DeferredBatchesTag {};
|
||||
auto& DeferredBatches() { return HostThreadLocal<std::vector<DeferredBatch>, DeferredBatchesTag>(); }
|
||||
std::atomic<std::uint64_t> deferredPending{0};
|
||||
// APS5_PROFILE_DRAW, for the [recorder] line: batches and objects released after an unlock and the
|
||||
// time that took, on the release thread and inline (on the unlocking thread: the kill switch, a
|
||||
|
|
@ -392,11 +394,11 @@ void JoinReleaseThread() {
|
|||
// The unlock hook: hands what this thread deferred to the release thread, or destroys it here
|
||||
// (APS5_RELEASE_ON_UNLOCK=1, the queue is full or stopping, or the hand-off cannot be made).
|
||||
void ReleaseDeferredKeeps() {
|
||||
if (deferredBatches.empty()) return;
|
||||
if (DeferredBatches().empty()) return;
|
||||
// Taken off the thread's list first: a destructor that took and released the mutex (none is
|
||||
// known to) would re-enter here and must find nothing.
|
||||
auto releasing = std::move(deferredBatches);
|
||||
deferredBatches.clear();
|
||||
auto releasing = std::move(DeferredBatches());
|
||||
DeferredBatches().clear();
|
||||
if (ReleaseOnUnlock()) {
|
||||
DestroyDeferred(std::move(releasing), false);
|
||||
return;
|
||||
|
|
@ -475,10 +477,12 @@ Recorder* labelTableOwner = nullptr;
|
|||
// The ranges of the calling worker's queued labels (NoteQueuedLabel, cleared by
|
||||
// ForgetQueuedLabels): the flush hook, on the same thread, has them recorded before an access that
|
||||
// overlaps one, through the function the driver installed.
|
||||
thread_local std::vector<std::pair<std::uint64_t, std::uint64_t>> queuedLabelRanges;
|
||||
struct QueuedLabelRangesTag {};
|
||||
auto& QueuedLabelRanges() { return HostThreadLocal<std::vector<std::pair<std::uint64_t, std::uint64_t>>, QueuedLabelRangesTag>(); }
|
||||
std::atomic<void (*)()> queuedLabelRecorder{nullptr};
|
||||
// The dwords (with their stamps) this thread noted since its last CloseLabelGroup (see Recorder::CloseLabelGroup).
|
||||
thread_local std::vector<std::pair<std::uint64_t, std::uint64_t>> labelGroupDwords;
|
||||
struct LabelGroupDwordsTag {};
|
||||
auto& LabelGroupDwords() { return HostThreadLocal<std::vector<std::pair<std::uint64_t, std::uint64_t>>, LabelGroupDwordsTag>(); }
|
||||
// Late-rule counters (Recorder::LateCounts), per thread: lookups run on the waiting worker.
|
||||
thread_local Recorder::LateStatistics lateCounts{};
|
||||
|
||||
|
|
@ -548,7 +552,7 @@ bool SeparateQueuedLabels() {
|
|||
|
||||
bool QueuedLabelOverlaps(std::uint64_t address, std::size_t bytes) {
|
||||
const auto end = address + bytes;
|
||||
for (const auto& [begin, finish] : queuedLabelRanges) {
|
||||
for (const auto& [begin, finish] : QueuedLabelRanges()) {
|
||||
if (address < finish && begin < end) return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -943,7 +947,7 @@ void FlushForAccess(std::uint64_t address, std::size_t bytes) {
|
|||
// would reap nested in the reap in progress, landing later batches' write-backs before the
|
||||
// rest of this one's, and this queue's queued label follows every batch of its own in flight
|
||||
// anyway, so the write-back landing first is the order the hardware gives.
|
||||
if (!queuedLabelRanges.empty() && QueuedLabelOverlaps(address, bytes)) {
|
||||
if (!QueuedLabelRanges().empty() && QueuedLabelOverlaps(address, bytes)) {
|
||||
if (completionDepth != 0 && HookCompletionGuard()) {
|
||||
queuedLabelHookInCompletion.fetch_add(1, std::memory_order_relaxed);
|
||||
} else if (auto* record = queuedLabelRecorder.load(std::memory_order_acquire); record != nullptr) {
|
||||
|
|
@ -1074,9 +1078,9 @@ Recorder::~Recorder() {
|
|||
// other threads (an unlock hook that found the queue stopping or full) are waited for before
|
||||
// the caller destroys the device. The count is global: a newer recorder's batches (device
|
||||
// replacement) are waited for too, which only prolongs the spin.
|
||||
if (!deferredBatches.empty()) {
|
||||
auto own = std::move(deferredBatches);
|
||||
deferredBatches.clear();
|
||||
if (!DeferredBatches().empty()) {
|
||||
auto own = std::move(DeferredBatches());
|
||||
DeferredBatches().clear();
|
||||
DestroyDeferred(std::move(own), false);
|
||||
}
|
||||
JoinReleaseThread();
|
||||
|
|
@ -1160,9 +1164,9 @@ Recorder::LateStatistics Recorder::LateCounts() {
|
|||
}
|
||||
|
||||
void Recorder::CloseLabelGroup(std::uint64_t trackerGeneration) {
|
||||
if (labelGroupDwords.empty()) return;
|
||||
auto group = std::move(labelGroupDwords);
|
||||
labelGroupDwords.clear();
|
||||
if (LabelGroupDwords().empty()) return;
|
||||
auto group = std::move(LabelGroupDwords());
|
||||
LabelGroupDwords().clear();
|
||||
if (trackerGeneration == 0) return;
|
||||
std::lock_guard tableLock(labelTableMutex);
|
||||
if (labelTableOwner == nullptr) return;
|
||||
|
|
@ -1183,7 +1187,7 @@ void Recorder::NoteQueuedLabel(std::uint64_t address, std::span<const std::byte>
|
|||
if (bytes.size() < 4 || address % 4 != 0) return;
|
||||
// The range before the table: the hook covers the group whether or not a table exists to
|
||||
// enter it in (no owner: the callback records it outright, by a CPU store when need be).
|
||||
queuedLabelRanges.emplace_back(address, address + bytes.size());
|
||||
QueuedLabelRanges().emplace_back(address, address + bytes.size());
|
||||
queuedLabelsNoted.fetch_add(1, std::memory_order_relaxed);
|
||||
std::lock_guard tableLock(labelTableMutex);
|
||||
if (labelTableOwner == nullptr) return;
|
||||
|
|
@ -1199,9 +1203,9 @@ void Recorder::NoteQueuedLabel(std::uint64_t address, std::span<const std::byte>
|
|||
}
|
||||
|
||||
void Recorder::ForgetQueuedLabels() {
|
||||
if (queuedLabelRanges.empty()) return;
|
||||
auto ranges = std::move(queuedLabelRanges);
|
||||
queuedLabelRanges.clear();
|
||||
if (QueuedLabelRanges().empty()) return;
|
||||
auto ranges = std::move(QueuedLabelRanges());
|
||||
QueuedLabelRanges().clear();
|
||||
std::lock_guard tableLock(labelTableMutex);
|
||||
if (labelTableOwner == nullptr) return;
|
||||
auto& recorded = labelTableOwner->labels;
|
||||
|
|
@ -2124,7 +2128,7 @@ void Recorder::noteLabelOn(Batch& batch, std::uint64_t address, std::span<const
|
|||
const auto dword = address + offset;
|
||||
labels.insert_or_assign(dword, LabelEntry{value, queue, stamp, &batch, 0, false, behindCompletion});
|
||||
batch.labelDwords.push_back(dword);
|
||||
labelGroupDwords.emplace_back(dword, stamp);
|
||||
LabelGroupDwords().emplace_back(dword, stamp);
|
||||
}
|
||||
recordedLabels.store(labels.size(), std::memory_order_relaxed);
|
||||
}
|
||||
|
|
@ -2855,7 +2859,7 @@ void Recorder::finish(std::unique_ptr<Batch> batch, bool wait, int source) {
|
|||
// destroys the batch's objects here, under the mutex, counts nothing the destructor would
|
||||
// wait for and must not escape (the rest of finish() releases the fence and command buffer).
|
||||
try {
|
||||
deferredBatches.push_back({std::move(batch->kept), std::move(batch->completions)});
|
||||
DeferredBatches().push_back({std::move(batch->kept), std::move(batch->completions)});
|
||||
deferredPending.fetch_add(1, std::memory_order_acq_rel);
|
||||
} catch (...) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,13 @@ VkFormat ResolveTextureFormat(std::uint32_t guestFormat) {
|
|||
return findFormatEntry(guestFormat).vkFormat;
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> FindGuestTextureFormat(VkFormat format, std::uint32_t elementBytes) {
|
||||
for (const auto& entry : kFormatLookup) {
|
||||
if (!entry.blockCompressed && entry.vkFormat == format && entry.bytesPerElement == elementBytes) return entry.guestFormat;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::uint32_t BytesPerElement(std::uint32_t guestFormat) {
|
||||
return findFormatEntry(guestFormat).bytesPerElement;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
AgcDriver::DisplayBuffer DescribeVideoOutBuffer(const VideoOutBuffer& buffer, const BufferAttributeGroup& group) {
|
||||
const auto& attribute = group.attribute;
|
||||
if (attribute.reserved0 != 0 || attribute.pad0 != 0 || attribute.reserved1[0] != 0 || attribute.reserved1[1] != 0 || attribute.reserved1[2] != 0) throw std::runtime_error("VideoOut: reserved buffer attribute bits are set");
|
||||
if (attribute.tiling_mode != 0 || attribute.pitch_in_pixel != 0 || attribute.aspect_ratio != 0 || attribute.option != 0) throw std::runtime_error("VideoOut: unsupported tiling, pitch, aspect ratio or buffer option");
|
||||
if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || attribute.option != 0) throw std::runtime_error("VideoOut: unsupported tiling, pitch, aspect ratio or buffer option");
|
||||
if (group.category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED || buffer.metadataAddress != 0 || attribute.dcc_control != 0 || attribute.dcc_cb_register_clear_color != 0) throw std::runtime_error("VideoOut: DCC presentation is not implemented");
|
||||
const AgcDriver::DisplayBuffer result{buffer.dataAddress, attribute.pixel_format, attribute.width, attribute.height};
|
||||
const AgcDriver::DisplayBuffer result{buffer.dataAddress, attribute.pixel_format, attribute.width, attribute.height, attribute.tiling_mode, attribute.pitch_in_pixel};
|
||||
static_cast<void>(AgcDriverDisplayBufferSize_nid_postfix(result));
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_HOSTTHREADLOCAL_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_HOSTTHREADLOCAL_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
template<typename TValue, typename TTag>
|
||||
TValue& HostThreadLocal() {
|
||||
#ifdef _WIN32
|
||||
static const DWORD slot = FlsAlloc(+[](void* value) { delete static_cast<TValue*>(value); });
|
||||
if (slot == FLS_OUT_OF_INDEXES) throw std::runtime_error("host thread-local slot allocation failed");
|
||||
auto* value = static_cast<TValue*>(FlsGetValue(slot));
|
||||
if (value == nullptr) {
|
||||
auto created = std::make_unique<TValue>();
|
||||
if (!FlsSetValue(slot, created.get())) throw std::runtime_error("host thread-local value registration failed");
|
||||
value = created.release();
|
||||
}
|
||||
return *value;
|
||||
#else
|
||||
static thread_local TValue value;
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libc/include/GuestHeap.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
#include <mutex>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -28,6 +30,28 @@ Initialize heapFinalize = nullptr;
|
|||
bool heapFinalized = false;
|
||||
thread_local bool heapCallbackActive = false;
|
||||
|
||||
void* APS5_VABI defaultAllocate(std::size_t bytes) { return GuestHeap::GuestHeapAllocate_nid_postfix(bytes); }
|
||||
void APS5_VABI defaultFree(void* pointer) { GuestHeap::GuestHeapFree_nid_postfix(pointer); }
|
||||
void* APS5_VABI defaultReallocate(void* pointer, std::size_t bytes) { return GuestHeap::GuestHeapReallocate_nid_postfix(pointer, bytes); }
|
||||
void* APS5_VABI defaultCalloc(std::size_t count, std::size_t bytes) {
|
||||
if (bytes != 0 && count > std::numeric_limits<std::size_t>::max() / bytes) throw std::bad_alloc();
|
||||
auto* pointer = defaultAllocate(count * bytes);
|
||||
std::memset(pointer, 0, count * bytes);
|
||||
return pointer;
|
||||
}
|
||||
void* APS5_VABI defaultAlign(std::size_t alignment, std::size_t bytes) { return GuestHeap::GuestHeapAlign_nid_postfix(alignment, bytes); }
|
||||
int APS5_VABI defaultPosixAlign(void** pointer, std::size_t alignment, std::size_t bytes) {
|
||||
if (pointer == nullptr || alignment < sizeof(void*) || (alignment & (alignment - 1)) != 0) return 22;
|
||||
try { *pointer = defaultAlign(alignment, bytes); return 0; }
|
||||
catch (const std::bad_alloc&) { return 12; }
|
||||
}
|
||||
|
||||
std::array<void*, 10> defaultApi() {
|
||||
return {reinterpret_cast<void*>(defaultAllocate), reinterpret_cast<void*>(defaultFree),
|
||||
reinterpret_cast<void*>(defaultCalloc), reinterpret_cast<void*>(defaultReallocate),
|
||||
reinterpret_cast<void*>(defaultAlign), nullptr, reinterpret_cast<void*>(defaultPosixAlign)};
|
||||
}
|
||||
|
||||
class CallbackScope {
|
||||
public:
|
||||
CallbackScope() {
|
||||
|
|
@ -87,8 +111,12 @@ void ApplicationHeapRegister_nid_no_patch(void* const* api) {
|
|||
if (api == nullptr) throw std::invalid_argument("application heap: null allocator API");
|
||||
std::array<void*, 10> replacement;
|
||||
std::memcpy(replacement.data(), api, sizeof(replacement));
|
||||
for (std::size_t index = 0; index < 7; ++index) {
|
||||
if (replacement[index] == nullptr) throw std::invalid_argument("application heap: incomplete allocator API");
|
||||
if (std::all_of(replacement.begin(), replacement.end(), [](const void* entry) { return entry == nullptr; })) {
|
||||
replacement = defaultApi();
|
||||
} else {
|
||||
for (std::size_t index = 0; index < 7; ++index) {
|
||||
if (replacement[index] == nullptr) throw std::invalid_argument("application heap: incomplete allocator API");
|
||||
}
|
||||
}
|
||||
std::lock_guard lock(heapMutex);
|
||||
if (heapFailure) std::rethrow_exception(heapFailure);
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ void write(std::array<std::byte, TSize>& data, std::size_t offset, TValue value)
|
|||
|
||||
}
|
||||
|
||||
int main(int argc, char**) {
|
||||
int main(int argc, char** argv) {
|
||||
reject([] { ApplicationHeapAllocate_nid_no_patch(64); });
|
||||
reject([] { ApplicationHeapRegister_nid_no_patch(nullptr); });
|
||||
std::array<std::byte, 0x40> process{};
|
||||
|
|
@ -92,6 +92,40 @@ int main(int argc, char**) {
|
|||
write(replacement, 0x40, &align);
|
||||
write(replacement, 0x48, &reallocate);
|
||||
write(replacement, 0x50, &posixAlign);
|
||||
if (argc > 1 && std::strcmp(argv[1], "default") == 0) {
|
||||
std::array<void*, 10> partial{};
|
||||
partial[0] = reinterpret_cast<void*>(&allocate);
|
||||
reject([&] { ApplicationHeapRegister_nid_no_patch(partial.data()); });
|
||||
std::memset(replacement.data() + 0x20, 0, sizeof(partial));
|
||||
ApplicationHeapInitialize_nid_no_patch(process.data());
|
||||
require(initializes == 1);
|
||||
auto* pointer = static_cast<unsigned char*>(ApplicationHeapCalloc_nid_no_patch(7, 9));
|
||||
for (unsigned i = 0; i < 63; ++i) require(pointer[i] == 0);
|
||||
std::memset(pointer, 0x5a, 63);
|
||||
pointer = static_cast<unsigned char*>(ApplicationHeapReallocate_nid_no_patch(pointer, 150));
|
||||
for (unsigned i = 0; i < 63; ++i) require(pointer[i] == 0x5a);
|
||||
pointer = static_cast<unsigned char*>(ApplicationHeapReallocate_nid_no_patch(pointer, 11));
|
||||
for (unsigned i = 0; i < 11; ++i) require(pointer[i] == 0x5a);
|
||||
require(ApplicationHeapReallocate_nid_no_patch(pointer, 0) == nullptr);
|
||||
pointer = static_cast<unsigned char*>(ApplicationHeapReallocate_nid_no_patch(nullptr, 32));
|
||||
require(pointer != nullptr);
|
||||
ApplicationHeapFree_nid_no_patch(pointer);
|
||||
for (std::size_t alignment : {16, 64, 4096}) {
|
||||
auto* aligned = ApplicationHeapAlign_nid_no_patch(alignment, 37);
|
||||
require(reinterpret_cast<std::uintptr_t>(aligned) % alignment == 0);
|
||||
ApplicationHeapFree_nid_no_patch(aligned);
|
||||
}
|
||||
void* aligned = nullptr;
|
||||
require(ApplicationHeapPosixAlign_nid_no_patch(&aligned, 256, 99) == 0);
|
||||
require(reinterpret_cast<std::uintptr_t>(aligned) % 256 == 0);
|
||||
ApplicationHeapFree_nid_no_patch(aligned);
|
||||
ApplicationHeapFree_nid_no_patch(nullptr);
|
||||
reject([] { ApplicationHeapCalloc_nid_no_patch(SIZE_MAX, 2); });
|
||||
reject([] { ApplicationHeapAlign_nid_no_patch(3, 16); });
|
||||
ApplicationHeapInitialize_nid_no_patch(process.data());
|
||||
require(initializes == 1);
|
||||
return 0;
|
||||
}
|
||||
if (argc > 1) {
|
||||
write(replacement, 8, std::uint64_t{99});
|
||||
reject([&] { ApplicationHeapInitialize_nid_no_patch(process.data()); });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
|
||||
extern "C" {
|
||||
std::uint32_t* APS5_VABI sceAgcDcbSetIndexCount(CommandBuffer*, std::uint32_t);
|
||||
std::uint32_t APS5_VABI sceAgcDcbSetIndexCountGetSize();
|
||||
std::uint32_t* APS5_VABI sceAgcDcbDrawIndex(CommandBuffer*, std::uint32_t, const volatile void*, std::uint64_t);
|
||||
std::uint32_t APS5_VABI sceAgcDcbDrawIndexGetSize();
|
||||
}
|
||||
static void Require(bool condition) { if (!condition) std::abort(); }
|
||||
template<typename TAction> static void Reject(TAction action) {
|
||||
bool rejected = false;
|
||||
try { action(); } catch (const std::exception&) { rejected = true; }
|
||||
Require(rejected);
|
||||
}
|
||||
int main() {
|
||||
std::array<std::uint32_t, 16> words{};
|
||||
CommandBuffer buffer{words.data(), words.data()+words.size(), words.data(), words.data()+words.size(), nullptr, nullptr, 0};
|
||||
auto* sizePacket = sceAgcDcbSetIndexCount(&buffer, 3);
|
||||
Require(sizePacket == words.data() && words[0] == 0xc0001300u && words[1] == 3);
|
||||
Require(sceAgcDcbSetIndexCountGetSize() == 8);
|
||||
AgcDriver::QueueState queue;
|
||||
AgcDriver::Pm4::Validate({sizePacket,2},0);
|
||||
AgcDriver::Pm4::Execute({sizePacket,2},queue);
|
||||
Require(queue.indexBufferSize == 3);
|
||||
alignas(4) std::array<std::uint32_t,3> indices{2,0,1};
|
||||
auto* draw = sceAgcDcbDrawIndex(&buffer,3,indices.data(),0);
|
||||
Require(draw == words.data()+2 && draw[0] == 0xc0042700u && draw[1] == 3 && draw[4] == 3 && draw[5] == 0);
|
||||
Require(sceAgcDcbDrawIndexGetSize() == 24);
|
||||
Require(AgcDriver::Pm4::AccessesMemory(draw[0]));
|
||||
Require(AgcDriver::Pm4::DrawOpcode((draw[0] >> 8u) & 0xffu));
|
||||
queue.indexBase = 1;
|
||||
for (unsigned type=0;type<3;++type) {
|
||||
queue.indexType=type;
|
||||
const auto result=AgcDriver::Pm4::ResolveDraw({draw,6},queue);
|
||||
Require(result.indexAddress == reinterpret_cast<std::uintptr_t>(indices.data()));
|
||||
Require(result.indexCount == 3 && result.indexSize == (type==0?2:type==1?4:1) && result.indexed);
|
||||
}
|
||||
Require(queue.indexBase == 1);
|
||||
queue.indexType = 1;
|
||||
queue.indexBase = reinterpret_cast<std::uintptr_t>(indices.data());
|
||||
const std::array<std::uint32_t,5> offsetDraw{0xc0033500u,2,1,2,0};
|
||||
const auto offsetResult = AgcDriver::Pm4::ResolveDraw(offsetDraw,queue);
|
||||
Require(offsetResult.indexAddress == queue.indexBase+4 && offsetResult.indexCount == 2);
|
||||
draw[5] = 0x20;
|
||||
Require(AgcDriver::Pm4::ResolveDraw({draw,6},queue).flags == 0x20);
|
||||
draw[5] = 0;
|
||||
draw[1] = 0; draw[4] = 0;
|
||||
Require(AgcDriver::Pm4::ResolveDraw({draw,6},queue).indexCount == 0);
|
||||
draw[1] = 3; draw[4] = 3;
|
||||
draw[4]=4;
|
||||
Reject([&]{AgcDriver::Pm4::Validate({draw,6},0);});
|
||||
draw[4]=3;
|
||||
draw[5]=2;
|
||||
Reject([&]{AgcDriver::Pm4::Validate({draw,6},0);});
|
||||
draw[5]=0;
|
||||
Reject([&]{AgcDriver::Pm4::Validate({draw,5},0);});
|
||||
Reject([&]{AgcDriver::Pm4::Validate({draw,6},1);});
|
||||
draw[3]=0x10000;
|
||||
Reject([&]{AgcDriver::Pm4::Validate({draw,6},0);});
|
||||
draw[2]=1; draw[3]=0; queue.indexType=1;
|
||||
Reject([&]{AgcDriver::Pm4::ResolveDraw({draw,6},queue);});
|
||||
auto* saved=buffer.cursor_up;
|
||||
Reject([&]{sceAgcDcbDrawIndex(&buffer,3,nullptr,0);});
|
||||
Reject([&]{sceAgcDcbDrawIndex(&buffer,3,indices.data(),1ull<<40);});
|
||||
Require(buffer.cursor_up==saved);
|
||||
buffer.cursor_down=buffer.cursor_up+5;
|
||||
Reject([&]{sceAgcDcbDrawIndex(&buffer,3,indices.data(),0);});
|
||||
Require(buffer.cursor_up==saved);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#include <cstdlib>
|
||||
#include <thread>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
extern "C" void TouchHostThreadLocal();
|
||||
extern "C" unsigned DestroyedHostThreadLocals();
|
||||
|
||||
int main() {
|
||||
for (unsigned i = 0; i < 64; ++i) {
|
||||
std::thread worker([] { TouchHostThreadLocal(); TouchHostThreadLocal(); });
|
||||
worker.join();
|
||||
if (DestroyedHostThreadLocals() != (i + 1) * 2) std::abort();
|
||||
}
|
||||
#ifdef _WIN32
|
||||
for (unsigned i = 0; i < 64; ++i) {
|
||||
const auto worker = CreateThread(nullptr, 0, +[](void*) -> DWORD { TouchHostThreadLocal(); return 0; }, nullptr, 0, nullptr);
|
||||
if (!worker || WaitForSingleObject(worker, 5000) != WAIT_OBJECT_0) std::abort();
|
||||
CloseHandle(worker);
|
||||
if (DestroyedHostThreadLocals() != 128 + (i + 1) * 2) std::abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include "prx/libc/include/HostThreadLocal.hpp"
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
std::atomic<unsigned> destroyed{0};
|
||||
struct Value {
|
||||
std::vector<unsigned> data = std::vector<unsigned>(256, 42);
|
||||
~Value() {
|
||||
for (auto item : data) if (item != 42) std::abort();
|
||||
++destroyed;
|
||||
}
|
||||
};
|
||||
struct First {};
|
||||
struct Second {};
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#define TEST_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define TEST_EXPORT
|
||||
#endif
|
||||
|
||||
extern "C" TEST_EXPORT void TouchHostThreadLocal() {
|
||||
auto& first = HostThreadLocal<Value, First>();
|
||||
auto& second = HostThreadLocal<Value, Second>();
|
||||
if (&first == &second || &first != &HostThreadLocal<Value, First>()) std::abort();
|
||||
}
|
||||
|
||||
extern "C" TEST_EXPORT unsigned DestroyedHostThreadLocals() { return destroyed.load(); }
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
|
||||
static void Require(bool condition) { if (!condition) std::abort(); }
|
||||
template<typename TAction> static void Reject(TAction action) {
|
||||
bool rejected = false;
|
||||
try { action(); } catch (const std::exception&) { rejected = true; }
|
||||
Require(rejected);
|
||||
}
|
||||
|
||||
int main() {
|
||||
AgcDriver::DisplayBuffer buffer{65536, 0x8000000000000000ull, 2, 2, 1, 3};
|
||||
const std::array<unsigned char, 24> input{1,2,3,4, 5,6,7,8, 99,99,99,99,
|
||||
9,10,11,12, 13,14,15,16, 99,99,99,99};
|
||||
const auto source = std::as_bytes(std::span(input));
|
||||
Require(AgcDriver::DisplayBufferSize(buffer) == input.size());
|
||||
auto pixels = AgcDriver::DecodeDisplayBuffer(buffer, source);
|
||||
Require(pixels.size() == 16);
|
||||
for (unsigned i = 0; i < pixels.size(); ++i) Require(std::to_integer<unsigned>(pixels[i]) == i + 1);
|
||||
buffer.pixelFormat = 0x8000000022000000ull;
|
||||
pixels = AgcDriver::DecodeDisplayBuffer(buffer, source);
|
||||
const std::array<unsigned char, 16> swapped{3,2,1,4, 7,6,5,8, 11,10,9,12, 15,14,13,16};
|
||||
for (unsigned i = 0; i < pixels.size(); ++i) Require(std::to_integer<unsigned>(pixels[i]) == swapped[i]);
|
||||
Reject([&] { AgcDriver::DecodeDisplayBuffer(buffer, source.first(23)); });
|
||||
buffer.pitchInPixel = 1;
|
||||
Reject([&] { AgcDriver::DisplayBufferSize(buffer); });
|
||||
buffer.pitchInPixel = 0;
|
||||
Require(AgcDriver::DisplayBufferSize(buffer) == 16);
|
||||
buffer.tilingMode = 2;
|
||||
Reject([&] { AgcDriver::DisplayBufferSize(buffer); });
|
||||
buffer.tilingMode = 0;
|
||||
Require(AgcDriver::DisplayBufferSize(buffer) == 65536);
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
#include "Translation/TranslationContext.hpp"
|
||||
#include "RdnaDecoder/RdnaMemoryOpDecoder.hpp"
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace ShaderRecompiler;
|
||||
static void Require(bool value) { if (!value) throw std::runtime_error("buffer address operand regression"); }
|
||||
static void CheckRegister(IrValue* value, IrOpcode opcode, unsigned index) {
|
||||
Require(value->Opcode() == opcode);
|
||||
Require(value->Argument(0)->Register().index == index);
|
||||
}
|
||||
static void Check(bool typed, unsigned opcode, IrOpcode expected, bool idxen, bool offen, bool immediate) {
|
||||
const std::array<std::uint32_t,2> code{
|
||||
(typed ? (0x3au<<26)|(opcode<<16)|(4u<<19) : (0x38u<<26)|(opcode<<18))
|
||||
| (unsigned(idxen)<<13)|(unsigned(offen)<<12)|12u,
|
||||
((immediate?128u:20u)<<24)|(2u<<16)|(4u<<8)|10u
|
||||
};
|
||||
const auto instruction = typed ? DecodeRdnaMtbuf(0,code,0) : DecodeRdnaMubuf(0,code,0);
|
||||
IrProgram program;
|
||||
auto& block=program.CreateBlock();
|
||||
program.SetEntryBlock(block);
|
||||
TranslationContext context(program,block,256);
|
||||
context.TranslateInstruction(instruction);
|
||||
IrValue* memory=nullptr;
|
||||
for(auto* value:block.Instructions()) if(value->Opcode()==expected) memory=value;
|
||||
Require(memory!=nullptr);
|
||||
if(idxen) CheckRegister(memory->Argument(1),IrOpcode::GetVectorRegister,10);
|
||||
else Require(memory->Argument(1)->HasImmediate() && memory->Argument(1)->ImmediateU32()==0);
|
||||
if(offen) CheckRegister(memory->Argument(2),IrOpcode::GetVectorRegister,idxen?11:10);
|
||||
else Require(memory->Argument(2)->HasImmediate() && memory->Argument(2)->ImmediateU32()==0);
|
||||
if(immediate) Require(memory->Argument(3)->HasImmediate() && memory->Argument(3)->ImmediateU32()==0);
|
||||
else CheckRegister(memory->Argument(3),IrOpcode::GetScalarRegister,20);
|
||||
}
|
||||
int main() {
|
||||
for(bool idxen:{false,true}) for(bool offen:{false,true}) for(bool immediate:{false,true}) {
|
||||
Check(false,0x0c,IrOpcode::LoadBufferU32,idxen,offen,immediate);
|
||||
Check(false,0x1c,IrOpcode::StoreBufferU32,idxen,offen,immediate);
|
||||
Check(false,0x32,IrOpcode::BufferAtomicIAdd32,idxen,offen,immediate);
|
||||
Check(true,0,IrOpcode::LoadBufferU32,idxen,offen,immediate);
|
||||
Check(true,4,IrOpcode::StoreBufferU32,idxen,offen,immediate);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
|
||||
#include <cstdlib>
|
||||
|
||||
int main() {
|
||||
using namespace AgcDriver::Graphics;
|
||||
for (auto format : {VK_FORMAT_R8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R16G16B16A16_SFLOAT}) {
|
||||
const unsigned bytes = format == VK_FORMAT_R8_UNORM ? 1 : format == VK_FORMAT_R8G8B8A8_UNORM ? 4 : 8;
|
||||
const auto guest = FindGuestTextureFormat(format, bytes);
|
||||
if (!guest || ResolveTextureFormat(*guest) != format || BytesPerElement(*guest) != bytes) std::abort();
|
||||
if (FindGuestTextureFormat(format, bytes + 1)) std::abort();
|
||||
}
|
||||
if (FindGuestTextureFormat(VK_FORMAT_B8G8R8A8_UNORM, 4)) std::abort();
|
||||
if (FindGuestTextureFormat(VK_FORMAT_UNDEFINED, 4)) std::abort();
|
||||
if (FindGuestTextureFormat(VK_FORMAT_BC1_RGBA_UNORM_BLOCK, 8)) std::abort();
|
||||
}
|
||||
Loading…
Reference in New Issue