mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 00:35:47 +00:00
Merge main into fix/http-status-line-measured-ps5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
20ec66fe21
638 files changed
+29312
-6015
No files matched your search
@@ -18,15 +18,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Fetch pull request objects without checking out its code
|
||||
env:
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: git fetch --no-tags origin "$HEAD_SHA"
|
||||
- name: Check conventions with the trusted base script
|
||||
- name: Check conventions with the current base branch script
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: python3 -I tools/check_conventions.py --base "$BASE_SHA" --head "$HEAD_SHA"
|
||||
run: python3 -I tools/check_conventions.py --base HEAD --head "$HEAD_SHA"
|
||||
+1
-1
@@ -42,7 +42,7 @@ This manual run also builds and tests the relinker on Linux, Windows and macOS w
|
||||
|
||||
With `--field full=false`, the manual run skips the full builds and runs only the relinker matrix on Linux, Windows and macOS, if the pull request changes a file the relinker-only build uses, such as the Python tools and their tests in `tools/`.
|
||||
|
||||
The Conventions check runs automatically for every contributor, including first-time contributors. It uses the base branch checker and reads pull request Git objects without executing pull request code. The Conventions check runs on every pull request and fails when a rule on this page is broken. It accepts code comments only when the pull request also changes [technical debt](docs/dev/TechnicalDebt.md), the index or a file in `docs/dev/debt/`, and only UTF-8 text files. Run it locally before pushing:
|
||||
The Conventions check runs automatically for every contributor, including first-time contributors. It uses the checker from the current tip of the base branch, so rules added after a pull request was opened apply to its next run, and reads pull request Git objects without executing pull request code. The Conventions check runs on every pull request and fails when a rule on this page is broken. It accepts code comments only when the pull request also changes [technical debt](docs/dev/TechnicalDebt.md), the index or a file in `docs/dev/debt/`, and only UTF-8 text files. Run it locally before pushing:
|
||||
|
||||
```
|
||||
python3 tools/check_conventions.py --base origin/main
|
||||
|
||||
@@ -38,6 +38,8 @@ SDL-mapped game controllers are supported, including analog sticks and triggers.
|
||||
|
||||
This project is intended for interoperability, research, preservation, and compatibility purposes. It does not include, distribute, or require copyrighted software, firmware, cryptographic keys, or proprietary libraries. Users are responsible for ensuring that any binaries used with this project are obtained and used in accordance with applicable laws and their respective license terms.
|
||||
|
||||
AnyPS5 is an independent project and is not affiliated with, authorized or endorsed by Sony Interactive Entertainment. "PlayStation" and "PS5" are trademarks of Sony Interactive Entertainment Inc.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GNU General Public License version 2 only.
|
||||
@@ -19,6 +19,10 @@ struct Header {
|
||||
std::uint32_t width;
|
||||
std::uint32_t height;
|
||||
std::uint32_t channels;
|
||||
std::uint8_t componentIds[4]{};
|
||||
std::uint8_t samplingFactors[4]{};
|
||||
bool progressive = false;
|
||||
bool app0BeforeFrame = false;
|
||||
};
|
||||
|
||||
enum class Sampling {
|
||||
|
||||
@@ -126,7 +126,33 @@ std::optional<Header> ParseHeader(std::span<const std::uint8_t> jpeg) {
|
||||
int channels = 0;
|
||||
if (!stbi_info_from_memory(jpeg.data(), static_cast<int>(jpeg.size()), &width, &height, &channels)) return std::nullopt;
|
||||
if (width <= 0 || height <= 0 || channels <= 0) return std::nullopt;
|
||||
return Header{static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height), static_cast<std::uint32_t>(channels)};
|
||||
Header header{static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height), static_cast<std::uint32_t>(channels)};
|
||||
std::size_t offset = 2;
|
||||
while (offset < jpeg.size()) {
|
||||
if (jpeg[offset++] != 0xFF) return std::nullopt;
|
||||
while (offset < jpeg.size() && jpeg[offset] == 0xFF) ++offset;
|
||||
if (offset == jpeg.size()) return std::nullopt;
|
||||
const std::uint8_t marker = jpeg[offset++];
|
||||
if (marker == 0xD9 || marker == 0xDA) return std::nullopt;
|
||||
if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD8)) continue;
|
||||
if (jpeg.size() - offset < 2) return std::nullopt;
|
||||
const std::size_t length = (static_cast<std::size_t>(jpeg[offset]) << 8) | jpeg[offset + 1];
|
||||
if (length < 2 || length > jpeg.size() - offset) return std::nullopt;
|
||||
if (marker == 0xE0) header.app0BeforeFrame = true;
|
||||
if (marker == 0xC0 || marker == 0xC1 || marker == 0xC2) {
|
||||
if (length < 8) return std::nullopt;
|
||||
header.channels = jpeg[offset + 7];
|
||||
if (header.channels == 0 || header.channels > 4 || length != 8 + 3 * header.channels) return std::nullopt;
|
||||
header.progressive = marker == 0xC2;
|
||||
for (std::uint32_t i = 0; i < header.channels; ++i) {
|
||||
header.componentIds[i] = jpeg[offset + 8 + 3 * i];
|
||||
header.samplingFactors[i] = jpeg[offset + 9 + 3 * i];
|
||||
}
|
||||
return header;
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<Image> Decode(std::span<const std::uint8_t> jpeg) {
|
||||
|
||||
@@ -186,6 +186,8 @@ foreach(LIB_DIR ${LIBS_DIRS})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
target_link_libraries(libSceUsbStorageDialog PRIVATE libSceUserService)
|
||||
|
||||
if(APS5_ENABLE_TRACY)
|
||||
set_target_properties(TracyClient PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${UNPATCHED_DIR}
|
||||
@@ -382,7 +384,7 @@ if(BUILD_TESTING)
|
||||
|
||||
add_test_executable(guest_offline_imports_tests tests/GuestOfflineImports.cpp)
|
||||
target_include_directories(guest_offline_imports_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_offline_imports_tests PRIVATE libSceHttp libSceNpEntitlementAccess libSceRudp libc)
|
||||
target_link_libraries(guest_offline_imports_tests PRIVATE libSceHttp libSceNpEntitlementAccess libSceRudp libSceVideoRecordingP libc)
|
||||
configure_windows_unwind(guest_offline_imports_tests)
|
||||
add_test(NAME guest_offline_imports COMMAND guest_offline_imports_tests)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
@@ -406,7 +408,7 @@ if(BUILD_TESTING)
|
||||
|
||||
add_test_executable(guest_usb_storage_dialog_tests tests/GuestUsbStorageDialog.cpp)
|
||||
target_include_directories(guest_usb_storage_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_usb_storage_dialog_tests PRIVATE libSceUsbStorageDialog libc)
|
||||
target_link_libraries(guest_usb_storage_dialog_tests PRIVATE libSceUsbStorageDialog libSceUserService libc)
|
||||
configure_windows_unwind(guest_usb_storage_dialog_tests)
|
||||
add_test(NAME guest_usb_storage_dialog COMMAND guest_usb_storage_dialog_tests)
|
||||
if(WIN32)
|
||||
@@ -682,4 +684,14 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(guest_np_game_intent PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNpGameIntent>")
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_vorbis_dec_tests tests/GuestVorbisDec.cpp)
|
||||
target_include_directories(guest_vorbis_dec_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(guest_vorbis_dec_tests PRIVATE libSceVorbisDec libc)
|
||||
configure_windows_unwind(guest_vorbis_dec_tests)
|
||||
add_test(NAME guest_vorbis_dec COMMAND guest_vorbis_dec_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_vorbis_dec PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceVorbisDec>")
|
||||
endif()
|
||||
endif()
|
||||
+192
-21
@@ -333,7 +333,7 @@ struct VaList {
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
using FiberEntry = void* (*)(std::uint64_t, void*);
|
||||
using FiberEntry = void (APS5_VABI*)(std::uint64_t argOnInitialize, std::uint64_t argOnRun);
|
||||
|
||||
struct FiberOptParam {
|
||||
std::uint32_t magic;
|
||||
@@ -432,6 +432,48 @@ struct AcmBatchError {
|
||||
std::uint32_t reserved[8];
|
||||
};
|
||||
|
||||
struct AcmReverbBuffer {
|
||||
void* samples;
|
||||
const void* blockMetadata;
|
||||
};
|
||||
|
||||
struct AcmReverbInput {
|
||||
std::uint32_t blockSize;
|
||||
std::uint32_t blockCount;
|
||||
std::uint32_t channelCount;
|
||||
std::uint32_t sampleFormat;
|
||||
std::uint32_t currentBlock;
|
||||
std::uint32_t availableBlocks;
|
||||
const AcmReverbBuffer* const* history;
|
||||
const void* const* inputChannels;
|
||||
std::uint32_t pendingBlocks;
|
||||
};
|
||||
|
||||
struct AcmReverbFilter {
|
||||
std::uint32_t blockSize;
|
||||
std::uint32_t blockCount;
|
||||
std::uint32_t channelCount;
|
||||
std::uint32_t sampleFormat;
|
||||
const void* blockMetadata;
|
||||
const AcmReverbBuffer* const* channels;
|
||||
};
|
||||
|
||||
struct AcmReverbOutput {
|
||||
std::uint32_t blockSize;
|
||||
std::uint32_t channelCount;
|
||||
void* const* samples;
|
||||
void* const* workspace;
|
||||
float gain;
|
||||
float previousGain;
|
||||
void* const* previousSamples;
|
||||
};
|
||||
|
||||
static_assert(sizeof(AcmReverbBuffer) == 16);
|
||||
static_assert(offsetof(AcmReverbInput, history) == 24);
|
||||
static_assert(offsetof(AcmReverbInput, pendingBlocks) == 40);
|
||||
static_assert(sizeof(AcmReverbFilter) == 32);
|
||||
static_assert(offsetof(AcmReverbOutput, previousSamples) == 32);
|
||||
|
||||
struct AcmBatchInfo {
|
||||
void* buffer;
|
||||
std::size_t offset;
|
||||
@@ -933,13 +975,60 @@ struct AudiodecM4aacInfo {
|
||||
std::int32_t i_result;
|
||||
};
|
||||
|
||||
struct VoiceEvent {
|
||||
std::int32_t type;
|
||||
std::uint32_t reserved;
|
||||
void* userData;
|
||||
union {
|
||||
struct {
|
||||
std::uint16_t count;
|
||||
std::uint16_t ports[7];
|
||||
} dataReady;
|
||||
struct {
|
||||
std::uint16_t port;
|
||||
std::uint8_t state;
|
||||
std::uint8_t reserved[13];
|
||||
} deviceState;
|
||||
};
|
||||
};
|
||||
|
||||
using VoiceEventHandler = void (APS5_VABI*)(const VoiceEvent* event);
|
||||
|
||||
struct VoiceThreadsParams {
|
||||
std::uint32_t mode;
|
||||
std::int32_t firstPriority;
|
||||
std::uint64_t firstAffinity;
|
||||
std::int32_t secondPriority;
|
||||
std::uint32_t reserved20;
|
||||
std::uint64_t secondAffinity;
|
||||
std::int32_t thirdPriority;
|
||||
std::uint32_t reserved36;
|
||||
std::uint64_t thirdAffinity;
|
||||
};
|
||||
|
||||
static_assert(sizeof(VoiceEvent) == 32);
|
||||
static_assert(offsetof(VoiceEvent, userData) == 8);
|
||||
static_assert(offsetof(VoiceEvent, dataReady) == 16);
|
||||
static_assert(offsetof(VoiceEvent, deviceState) == 16);
|
||||
static_assert(sizeof(VoiceThreadsParams) == 48);
|
||||
static_assert(offsetof(VoiceThreadsParams, firstAffinity) == 8);
|
||||
static_assert(offsetof(VoiceThreadsParams, secondPriority) == 16);
|
||||
static_assert(offsetof(VoiceThreadsParams, secondAffinity) == 24);
|
||||
static_assert(offsetof(VoiceThreadsParams, thirdPriority) == 32);
|
||||
static_assert(offsetof(VoiceThreadsParams, thirdAffinity) == 40);
|
||||
|
||||
struct VoiceInitParam {
|
||||
std::int32_t app_type;
|
||||
std::uint64_t on_event;
|
||||
VoiceEventHandler on_event;
|
||||
void* user_data;
|
||||
std::uint8_t reserved[32 - sizeof(std::int32_t) - sizeof(std::uint64_t) - sizeof(void*)];
|
||||
std::uint8_t reserved[16];
|
||||
};
|
||||
|
||||
static_assert(sizeof(VoiceInitParam) == 40);
|
||||
static_assert(offsetof(VoiceInitParam, on_event) == 8);
|
||||
static_assert(offsetof(VoiceInitParam, user_data) == 16);
|
||||
static_assert(offsetof(VoiceInitParam, reserved) == 24);
|
||||
|
||||
struct VoicePortParam {
|
||||
std::int32_t port_type;
|
||||
std::uint16_t threshold;
|
||||
@@ -962,14 +1051,30 @@ struct VoicePortInfo {
|
||||
std::uint16_t reserved;
|
||||
};
|
||||
|
||||
struct VoiceResourceInfo {
|
||||
std::uint16_t max_voice_input_ports;
|
||||
std::uint16_t max_voice_output_ports;
|
||||
std::uint16_t max_device_input_ports;
|
||||
std::uint16_t max_device_output_ports;
|
||||
std::uint16_t max_ports;
|
||||
struct NpAuthClientId;
|
||||
|
||||
struct NpAuthAuthorizedAppCodeParameter {
|
||||
std::uint64_t size;
|
||||
std::int32_t userId;
|
||||
const NpAuthClientId* clientId;
|
||||
const char* scope;
|
||||
std::uint32_t option;
|
||||
};
|
||||
|
||||
static_assert(sizeof(NpAuthAuthorizedAppCodeParameter) == 40);
|
||||
static_assert(offsetof(NpAuthAuthorizedAppCodeParameter, clientId) == 16);
|
||||
static_assert(offsetof(NpAuthAuthorizedAppCodeParameter, scope) == 24);
|
||||
static_assert(offsetof(NpAuthAuthorizedAppCodeParameter, option) == 32);
|
||||
|
||||
struct VoiceResourceInfo {
|
||||
std::uint16_t maxVoiceInputPorts;
|
||||
std::uint16_t maxVoiceOutputPorts;
|
||||
std::uint16_t maxDeviceInputPorts;
|
||||
std::uint16_t maxDeviceOutputPorts;
|
||||
};
|
||||
|
||||
static_assert(sizeof(VoiceResourceInfo) == 8);
|
||||
|
||||
struct VoiceStartParam {
|
||||
void* container;
|
||||
std::uint32_t mem_size;
|
||||
@@ -1155,8 +1260,8 @@ struct ImeKeycode {
|
||||
std::uint64_t timestamp;
|
||||
};
|
||||
|
||||
using ImeTextFilter = std::int32_t (APS5_VABI *)(char16_t* out_text, std::uint32_t* out_text_length, const char16_t* source_text, std::uint32_t source_text_length);
|
||||
using ImeExtKeyboardFilter = int (APS5_VABI *)(const ImeKeycode* source_keycode, std::uint16_t* out_keycode, std::uint32_t* out_status, void* reserved);
|
||||
using ImeTextFilter = std::int32_t (APS5_VABI*)(char16_t* outText, std::uint32_t* outTextLength, const char16_t* sourceText, std::uint32_t sourceTextLength);
|
||||
using ImeExtKeyboardFilter = int (APS5_VABI*)(const ImeKeycode* sourceKeycode, std::uint16_t* outKeycode, std::uint32_t* outStatus, void* reserved);
|
||||
|
||||
struct ImeExtendedParam {
|
||||
std::uint32_t option;
|
||||
@@ -1318,7 +1423,12 @@ struct NetEpollEvent {
|
||||
NetEpollData data;
|
||||
};
|
||||
|
||||
struct NetCtlNatInfo { std::uint8_t opaque[128]; };
|
||||
struct NetCtlNatInfo {
|
||||
std::uint32_t size;
|
||||
std::uint8_t opaque[12];
|
||||
};
|
||||
|
||||
static_assert(sizeof(NetCtlNatInfo) == 16);
|
||||
|
||||
union NetCtlInfo { std::uint8_t opaque[256]; };
|
||||
|
||||
@@ -1333,10 +1443,10 @@ using NetCtlCallback = void (APS5_VABI *)(int, void*);
|
||||
|
||||
struct HttpEpoll;
|
||||
using HttpEpollHandle = HttpEpoll*;
|
||||
using HttpsCallback = int (*)(int, unsigned int, void* const*, int, void*);
|
||||
using HttpRedirectCallback = int (*)(int, std::int32_t, std::int32_t*, const char*, void*);
|
||||
using HttpCookieRecvCallback = int (*)(int, const char*, const char*, std::uint64_t, void*);
|
||||
using HttpAuthInfoCallback = int (*)(int, int, const char*, char*, char*, int, std::uint8_t**, std::uint64_t*, int*, void*);
|
||||
using HttpsCallback = int (APS5_VABI*)(int, unsigned int, void* const*, int, void*);
|
||||
using HttpRedirectCallback = int (APS5_VABI*)(int, std::int32_t, std::int32_t*, const char*, void*);
|
||||
using HttpCookieRecvCallback = int (APS5_VABI*)(int, const char*, const char*, std::uint64_t, void*);
|
||||
using HttpAuthInfoCallback = int (APS5_VABI*)(int, int, const char*, char*, char*, int, std::uint8_t**, std::uint64_t*, int*, void*);
|
||||
|
||||
struct HttpNBEvent { std::uint8_t opaque[64]; };
|
||||
|
||||
@@ -1395,6 +1505,24 @@ struct NpUnifiedEntitlementLabel {
|
||||
char padding[3];
|
||||
};
|
||||
|
||||
struct NpServiceEntitlementLabel {
|
||||
char data[7];
|
||||
std::uint8_t reserved[13];
|
||||
};
|
||||
|
||||
struct NpEntitlementAccessTransactionId {
|
||||
char data[65];
|
||||
std::uint8_t reserved[7];
|
||||
};
|
||||
|
||||
struct NpEntitlementAccessListParameter;
|
||||
struct NpEntitlementAccessUnifiedInfo;
|
||||
struct NpEntitlementAccessServiceInfo;
|
||||
|
||||
static_assert(sizeof(NpUnifiedEntitlementLabel) == 20);
|
||||
static_assert(sizeof(NpServiceEntitlementLabel) == 20);
|
||||
static_assert(sizeof(NpEntitlementAccessTransactionId) == 72);
|
||||
|
||||
struct NpEntitlementAccessInitParam { char reserved[32]; };
|
||||
struct NpEntitlementAccessBootParam { char reserved[32]; };
|
||||
|
||||
@@ -1690,6 +1818,34 @@ struct AppContentBootParam {
|
||||
|
||||
struct AppContentMountPoint { char data[16]; };
|
||||
|
||||
using ContentExportMallocCallback = void* (APS5_VABI *)(std::size_t size, void* userData);
|
||||
using ContentExportFreeCallback = void (APS5_VABI *)(void* memory, void* userData);
|
||||
using ContentExportDataCallback = std::int32_t (APS5_VABI *)(const void** data, std::size_t* size, void* userData);
|
||||
|
||||
struct ContentExportInitParam {
|
||||
ContentExportMallocCallback mallocFunc;
|
||||
ContentExportFreeCallback freeFunc;
|
||||
void* userData;
|
||||
};
|
||||
|
||||
static_assert(sizeof(ContentExportInitParam) == 24);
|
||||
static_assert(offsetof(ContentExportInitParam, freeFunc) == 8);
|
||||
static_assert(offsetof(ContentExportInitParam, userData) == 16);
|
||||
|
||||
struct ContentExportSfParam {
|
||||
std::size_t size;
|
||||
const char* metadata;
|
||||
const std::int64_t* contentIds;
|
||||
std::uint32_t contentCount;
|
||||
std::uint32_t option;
|
||||
};
|
||||
|
||||
static_assert(sizeof(ContentExportSfParam) == 32);
|
||||
static_assert(offsetof(ContentExportSfParam, metadata) == 8);
|
||||
static_assert(offsetof(ContentExportSfParam, contentIds) == 16);
|
||||
static_assert(offsetof(ContentExportSfParam, contentCount) == 24);
|
||||
static_assert(offsetof(ContentExportSfParam, option) == 28);
|
||||
|
||||
struct ContentExportInitParam2 {
|
||||
void* malloc_func;
|
||||
void* free_func;
|
||||
@@ -1897,11 +2053,22 @@ union PlayGoOptionalChunk {
|
||||
};
|
||||
|
||||
struct PlayGoDialogResult {
|
||||
std::int32_t errorCode;
|
||||
std::int32_t result;
|
||||
std::int32_t reserved[9];
|
||||
std::uint8_t reserved[32];
|
||||
};
|
||||
|
||||
using RudpEventHandler = void (*)(int ctx_id, int event_id, int error_code, void* arg);
|
||||
struct RudpPollEvent {
|
||||
std::int32_t contextId;
|
||||
std::uint16_t events;
|
||||
std::uint16_t returnedEvents;
|
||||
};
|
||||
|
||||
static_assert(sizeof(RudpPollEvent) == 8);
|
||||
static_assert(offsetof(RudpPollEvent, events) == 4);
|
||||
static_assert(offsetof(RudpPollEvent, returnedEvents) == 6);
|
||||
|
||||
using RudpEventHandler = void (APS5_VABI*)(int ctxId, int eventId, int errorCode, void* arg);
|
||||
|
||||
struct SystemServiceStatus {
|
||||
std::int32_t event_num = 0;
|
||||
@@ -1998,12 +2165,16 @@ struct UserServiceGamePresets {
|
||||
std::uint32_t audio_language;
|
||||
};
|
||||
|
||||
struct UltMutexOptParam {
|
||||
std::uint32_t reserved_header[2];
|
||||
struct alignas(8) UltMutexOptParam {
|
||||
std::uint32_t header[3];
|
||||
std::uint32_t buildVersion;
|
||||
std::uint32_t attribute;
|
||||
std::uint32_t reserved0;
|
||||
std::uint32_t reserved[27];
|
||||
};
|
||||
|
||||
static_assert(sizeof(UltMutexOptParam) == 128);
|
||||
static_assert(offsetof(UltMutexOptParam, attribute) == 16);
|
||||
|
||||
struct UltUlthreadRuntimeOptParam { std::uint8_t bytes[128]; };
|
||||
|
||||
using UltUlthreadEntry = std::int32_t (APS5_VABI *)(std::uint64_t);
|
||||
|
||||
@@ -5,6 +5,39 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
APS5_DUMMY_FUN
|
||||
int APS5_VABI sceAc3EncCreateEncoder() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAc3EncDeleteEncoder() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAc3EncEncode() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAc3EncEncodeAsync() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAc3EncFlush() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAc3EncIsEncoding() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAc3EncWaitEncode() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -79,19 +79,130 @@ int APS5_VABI sceAcmContextDestroy(AcmContextId context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAcm_ConvReverb_SharedInput(void) {
|
||||
int APS5_VABI sceAcm_ConvReverb_SharedInput(AcmBatchInfo* batch, std::uint32_t mode, AcmReverbInput* input, std::uint32_t outputCount, const AcmReverbFilter* const* filters, const float* gains, const AcmReverbOutput* const* outputs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAcm_FFT(void) {
|
||||
int APS5_VABI sceAcm_FFT(AcmBatchInfo* batch, std::int32_t transformSize, std::uint32_t transformCount, std::int32_t inputFormat, const void* const* inputs, std::int32_t outputFormat, void* const* outputs, std::int32_t flags) {
|
||||
if (!batch || transformCount == 0 || transformCount > 0x7FFFFFFF || !inputs || !outputs || (flags & 3) == 3 || inputFormat < 0 || inputFormat > 1 || outputFormat < 0 || outputFormat > 1) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
if (transformSize != 512 && transformSize != 1024 && transformSize != 2048) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
const bool realInput = (flags & 1) != 0;
|
||||
if (transformSize == 2048 && !realInput) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
const std::uint32_t inputBytes = transformSize * (inputFormat == 1 ? 2 : 4);
|
||||
const std::uint32_t outputBytes = transformSize * (outputFormat == 1 ? 2 : 4);
|
||||
const std::uint32_t inputBlocks = (inputBytes >> 9) >> (realInput ? 1 : 0);
|
||||
const std::uint32_t outputBlocks = outputBytes >> 9;
|
||||
std::uint32_t transformCommand;
|
||||
std::uint32_t inputCommand;
|
||||
std::uint32_t outputCommand;
|
||||
std::uint32_t setupCommand;
|
||||
std::uint32_t sizeCode;
|
||||
if (transformSize == 512) {
|
||||
sizeCode = 4;
|
||||
transformCommand = realInput ? 7 : 11;
|
||||
inputCommand = realInput ? 2 * inputFormat : 9 + inputFormat;
|
||||
outputCommand = 4 | outputFormat;
|
||||
setupCommand = realInput ? 6 : 12;
|
||||
} else if (transformSize == 1024) {
|
||||
sizeCode = 5;
|
||||
transformCommand = realInput ? 8 : 13;
|
||||
inputCommand = realInput ? 2 * inputFormat : 14 | inputFormat;
|
||||
outputCommand = 5 | (2 * outputFormat);
|
||||
setupCommand = realInput ? 10 : 12;
|
||||
} else {
|
||||
sizeCode = 6;
|
||||
transformCommand = 6;
|
||||
inputCommand = 2 * inputFormat;
|
||||
outputCommand = 4 | outputFormat;
|
||||
setupCommand = 8;
|
||||
}
|
||||
auto* buffer = static_cast<std::byte*>(batch->buffer);
|
||||
auto offset = batch->offset;
|
||||
const auto capacity = batch->buffer_size;
|
||||
const auto appendWord = [&](std::uint64_t value) {
|
||||
if (offset > capacity || capacity - offset < sizeof(value)) return false;
|
||||
std::memcpy(buffer + offset, &value, sizeof(value));
|
||||
offset += sizeof(value);
|
||||
return true;
|
||||
};
|
||||
std::uint64_t header = static_cast<std::uint64_t>(sizeCode) << 8;
|
||||
if (!(flags & 8)) {
|
||||
const auto encodedCount = transformCount < 64000 ? transformCount : 64000;
|
||||
header |= (std::uint64_t{1} << 16) | (static_cast<std::uint64_t>(transformSize) << 8) | (static_cast<std::uint64_t>(flags & 6) << 19) | (static_cast<std::uint64_t>(flags & 1) << 23) | (std::uint64_t{1} << (inputFormat == 1 ? 24 : 26)) | (std::uint64_t{1} << (outputFormat == 1 ? 25 : 27)) | (static_cast<std::uint64_t>(encodedCount) << 28);
|
||||
}
|
||||
constexpr int noSpace = static_cast<int>(0x81940009);
|
||||
constexpr int badAlignment = static_cast<int>(0x8194000A);
|
||||
if (!appendWord(header) || !appendWord(10 | (setupCommand << 8))) return noSpace;
|
||||
for (std::uint32_t index = 0; index < transformCount; ++index) {
|
||||
const auto inputIndex = static_cast<std::size_t>(index) * ((flags & 2) ? 2 : 1);
|
||||
const auto input = reinterpret_cast<std::uintptr_t>(inputs[inputIndex]);
|
||||
const auto secondInput = (flags & 2) ? reinterpret_cast<std::uintptr_t>(inputs[inputIndex + 1]) : 0;
|
||||
if (!(flags & 2) && !input) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
if ((input | secondInput) & 127) return badAlignment;
|
||||
const auto inputRecordSize = (flags & 2) ? 24u : 16u;
|
||||
if (offset > capacity || capacity - offset < inputRecordSize) return noSpace;
|
||||
appendWord(((flags & 2) ? 7 : 6) | (static_cast<std::uint64_t>(inputBlocks) << 32) | ((flags & 2) ? static_cast<std::uint64_t>(inputBytes / 2) << 48 : 0));
|
||||
appendWord(input);
|
||||
if (flags & 2) appendWord(secondInput);
|
||||
if (!appendWord(10 | (inputCommand << 8)) || !appendWord(10 | (transformCommand << 8)) || !appendWord(10 | (outputCommand << 8))) return noSpace;
|
||||
const auto outputIndex = static_cast<std::size_t>(index) * ((flags & 4) ? 2 : 1);
|
||||
const auto output = reinterpret_cast<std::uintptr_t>(outputs[outputIndex]);
|
||||
const auto secondOutput = (flags & 4) ? reinterpret_cast<std::uintptr_t>(outputs[outputIndex + 1]) : 0;
|
||||
if ((output | secondOutput) & 127) return badAlignment;
|
||||
const auto outputRecordSize = (flags & 4) ? 24u : 16u;
|
||||
if (offset > capacity || capacity - offset < outputRecordSize) return noSpace;
|
||||
appendWord(((flags & 4) ? 9 : 8) | (static_cast<std::uint64_t>(outputBlocks) << 32) | ((flags & 4) ? static_cast<std::uint64_t>(outputBytes / 2) << 48 : 0));
|
||||
appendWord(output);
|
||||
if (flags & 4) appendWord(secondOutput);
|
||||
}
|
||||
batch->offset = offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAcm_Panner(void) {
|
||||
int APS5_VABI sceAcm_Panner(AcmBatchInfo* batch, std::uint32_t sourceCount, const void* const* sources, std::uint32_t sourceMode, std::uint32_t outputMode, std::uint32_t channelCount, const void* const* sourceParameters, const void* const* sourceMatrices, const void* const* inputChannels, void* const* outputChannels) {
|
||||
if (!batch || sourceCount == 0 || sourceCount > 65535 || sourceMode > 2 || outputMode > 1 || channelCount == 0 || channelCount > 36 || !sources || !sourceParameters || !sourceMatrices || !outputChannels) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
std::uintptr_t alignment = reinterpret_cast<std::uintptr_t>(sources) | reinterpret_cast<std::uintptr_t>(sourceParameters) | reinterpret_cast<std::uintptr_t>(sourceMatrices) | reinterpret_cast<std::uintptr_t>(inputChannels) | reinterpret_cast<std::uintptr_t>(outputChannels);
|
||||
for (std::uint32_t index = 0; index < sourceCount; ++index) {
|
||||
if (!sources[index] || !sourceParameters[index] || !sourceMatrices[index]) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
std::uint32_t parameterMode;
|
||||
std::memcpy(¶meterMode, sourceParameters[index], sizeof(parameterMode));
|
||||
if (parameterMode > 1) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
alignment |= reinterpret_cast<std::uintptr_t>(sources[index]) | reinterpret_cast<std::uintptr_t>(sourceParameters[index]) | reinterpret_cast<std::uintptr_t>(sourceMatrices[index]);
|
||||
}
|
||||
for (std::uint32_t index = 0; index < channelCount; ++index) {
|
||||
if (!outputChannels[index]) return SCE_ACM_ERROR_BAD_POINTER;
|
||||
alignment |= reinterpret_cast<std::uintptr_t>(outputChannels[index]);
|
||||
if (inputChannels) alignment |= reinterpret_cast<std::uintptr_t>(inputChannels[index]);
|
||||
}
|
||||
if (alignment & 127) return static_cast<int>(0x8194000A);
|
||||
const auto offset = batch->offset;
|
||||
const auto capacity = batch->buffer_size;
|
||||
if (offset > capacity || capacity - offset < 8) return static_cast<int>(0x81940009);
|
||||
auto* buffer = static_cast<std::byte*>(batch->buffer);
|
||||
const auto writeWord = [buffer](std::size_t position, std::uint64_t value) { std::memcpy(buffer + position, &value, sizeof(value)); };
|
||||
const auto encodedCount = sourceCount < 255 ? sourceCount : 255;
|
||||
writeWord(offset, (static_cast<std::uint64_t>(channelCount) << 24) | (static_cast<std::uint64_t>(encodedCount) << 16) | (std::uint64_t{1} << (sourceMode + 32)) | (std::uint64_t{1} << (outputMode + 35)) | 0x300);
|
||||
if (capacity < 128 || capacity - offset < 152) return static_cast<int>(0x81940009);
|
||||
const auto descriptorOffset = capacity - 128;
|
||||
std::memset(buffer + descriptorOffset, 0, 128);
|
||||
writeWord(descriptorOffset, sourceCount);
|
||||
writeWord(descriptorOffset + 8, reinterpret_cast<std::uintptr_t>(inputChannels));
|
||||
writeWord(descriptorOffset + 16, reinterpret_cast<std::uintptr_t>(outputChannels));
|
||||
writeWord(descriptorOffset + 24, reinterpret_cast<std::uintptr_t>(sources));
|
||||
writeWord(descriptorOffset + 32, sourceMode | (static_cast<std::uint64_t>(outputMode) << 32));
|
||||
writeWord(descriptorOffset + 40, channelCount);
|
||||
writeWord(descriptorOffset + 48, reinterpret_cast<std::uintptr_t>(sourceParameters));
|
||||
writeWord(descriptorOffset + 56, reinterpret_cast<std::uintptr_t>(sourceMatrices));
|
||||
writeWord(offset + 8, 0x8000000000000D);
|
||||
writeWord(offset + 16, reinterpret_cast<std::uintptr_t>(buffer + descriptorOffset));
|
||||
if (capacity - offset < 160) return static_cast<int>(0x81940009);
|
||||
writeWord(offset + 24, 10);
|
||||
batch->offset = offset + 32;
|
||||
batch->buffer_size = descriptorOffset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAcmBatchJobPriority(void) {
|
||||
int APS5_VABI sceAcmBatchJobPriority(AcmBatchInfo* batch, std::uint32_t priority) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/RegisterDefaults.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Semaphore.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/ShaderFusion.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/StaticBuffer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Suspend.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Patch/src/CondExec.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Patch/src/Dma.cpp
|
||||
@@ -96,6 +97,19 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
configure_windows_unwind(libSceAgc)
|
||||
|
||||
add_test_executable(agc_static_buffer_tests EXCLUDE_FROM_ALL tests/StaticBuffer.cpp)
|
||||
target_include_directories(agc_static_buffer_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_static_buffer_tests PRIVATE libSceAgc libSceAgcDriver libc)
|
||||
configure_windows_unwind(agc_static_buffer_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_static_buffer_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_static_buffer COMMAND agc_static_buffer_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_static_buffer PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_platform_tests EXCLUDE_FROM_ALL tests/Platform.cpp)
|
||||
target_include_directories(agc_platform_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_platform_tests PRIVATE libSceAgc libc)
|
||||
|
||||
@@ -21,7 +21,7 @@ std::uint32_t* APS5_VABI sceAgcDcbGetLodStats(CommandBuffer* buf, std::uint8_t c
|
||||
return Agc::Command::Emit(buf, 0x10u, {bufferSizeInBytes, static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u), control}, __func__);
|
||||
}
|
||||
|
||||
std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize() {
|
||||
std::uint32_t APS5_VABI sceAgcDcbBeginOcclusionQueryGetSize(std::int32_t queryType) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_STATICBUFFER_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_STATICBUFFER_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include "SceTypes.hpp"
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceAgcSetStaticBuffer(std::uint32_t index, const void* buffer);
|
||||
int APS5_VABI sceAgcGetStaticBuffer(std::uint32_t index, void* buffer);
|
||||
int APS5_VABI sceAgcSetStaticQword(std::uint32_t index, std::uint64_t value);
|
||||
int APS5_VABI sceAgcGetStaticQword(std::uint32_t index, std::uint64_t* value);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -13,20 +13,12 @@ namespace {
|
||||
|
||||
constexpr std::uint64_t semaphoreMemoryAlignment = 0x4000u;
|
||||
constexpr std::uint64_t semaphoreLabelBytes = 32u;
|
||||
constexpr std::uint64_t semaphoreCounterBytes = 8u;
|
||||
|
||||
std::mutex semaphoreMutex;
|
||||
std::uint8_t* semaphoreMemory = nullptr;
|
||||
std::uint64_t semaphoreBytes = 0;
|
||||
|
||||
bool hasOnlyZeroLabelTails(const std::uint8_t* memory, std::uint64_t sizeInBytes) {
|
||||
for (std::uint64_t label = 0; label < sizeInBytes; label += semaphoreLabelBytes) {
|
||||
for (std::uint64_t byte = semaphoreCounterBytes; byte < semaphoreLabelBytes; ++byte) {
|
||||
if (memory[label + byte] != 0) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -35,15 +27,12 @@ extern "C" {
|
||||
int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(memory);
|
||||
std::lock_guard lock(semaphoreMutex);
|
||||
Agc::Command::Require(semaphoreMemory == nullptr, __func__, "the AMM semaphore memory is already registered; the console's error is unknown");
|
||||
if (semaphoreBytes != 0) return static_cast<int>(0x8a6c0048u);
|
||||
if (((address | sizeInBytes) & (semaphoreMemoryAlignment - 1u)) != 0) return static_cast<int>(0x8a6c0002u);
|
||||
Agc::Command::Require(memory != nullptr && sizeInBytes != 0, __func__, "null or empty AMM semaphore memory; the console's error is unknown");
|
||||
Agc::Command::Require(((address | sizeInBytes) & (semaphoreMemoryAlignment - 1u)) == 0, __func__, "AMM semaphore memory or size is not 16 KiB aligned; the console's error is unknown");
|
||||
Agc::Command::Require(sizeInBytes - 1u <= std::numeric_limits<std::uintptr_t>::max() - address, __func__, "AMM semaphore memory wraps the address space");
|
||||
auto* labels = static_cast<std::uint8_t*>(memory);
|
||||
Agc::Command::Require(hasOnlyZeroLabelTails(labels, sizeInBytes), __func__, "AMM semaphore labels hold data past their first 8 bytes; whether registration clears it is unknown");
|
||||
for (std::uint64_t label = 0; label < sizeInBytes; label += semaphoreLabelBytes) {
|
||||
std::memset(labels + label, 0, semaphoreCounterBytes);
|
||||
}
|
||||
std::memset(labels, 0, sizeInBytes);
|
||||
semaphoreMemory = labels;
|
||||
semaphoreBytes = sizeInBytes;
|
||||
return 0;
|
||||
@@ -51,9 +40,9 @@ int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInByte
|
||||
|
||||
int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label) {
|
||||
std::lock_guard lock(semaphoreMutex);
|
||||
Agc::Command::Require(semaphoreMemory != nullptr, __func__, "no AMM semaphore memory is registered; the console's error is unknown");
|
||||
Agc::Command::Require(index < semaphoreBytes / semaphoreLabelBytes, __func__, "the semaphore label lies outside the AMM semaphore memory; the console's error is unknown");
|
||||
Agc::Command::Require(label != nullptr, __func__, "null semaphore label output; the console's error is unknown");
|
||||
if (semaphoreBytes == 0) return static_cast<int>(0x8a6c0049u);
|
||||
if (index >= semaphoreBytes / semaphoreLabelBytes) return static_cast<int>(0x8a6c000bu);
|
||||
Agc::Command::Require(label != nullptr, __func__, "null semaphore label output");
|
||||
*label = semaphoreMemory + static_cast<std::uint64_t>(index) * semaphoreLabelBytes;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ constexpr std::size_t FusedCodeAlignment = 256;
|
||||
constexpr char TrailerMagic[8] = {'b', 'a', 'r', 'e', 'f', 'o', 'o', 't'};
|
||||
constexpr std::size_t TrailerCodeBytesOffset = sizeof(TrailerMagic) + 3 * sizeof(std::uint32_t);
|
||||
constexpr std::uint32_t Rsrc1VgprMask = 0x3fu;
|
||||
constexpr std::uint32_t Rsrc1SgprMask = 0xfu << 6u;
|
||||
constexpr std::uint32_t Rsrc2OffchipLdsEnable = 1u << 18u;
|
||||
constexpr std::uint32_t Rsrc2UserSgprMask = (0x1fu << 1u) | (1u << 27u);
|
||||
|
||||
@@ -121,15 +120,19 @@ std::uint32_t FrontProgramBytes(const char* function, const Shader* front) {
|
||||
return static_cast<std::uint32_t>((end - 1) * 4);
|
||||
}
|
||||
|
||||
std::uint32_t MergeRsrc1(std::uint32_t back, std::uint32_t front) {
|
||||
const auto vgprs = std::max(back & Rsrc1VgprMask, front & Rsrc1VgprMask);
|
||||
const auto sgprs = std::max(back & Rsrc1SgprMask, front & Rsrc1SgprMask);
|
||||
return (back & ~(Rsrc1VgprMask | Rsrc1SgprMask)) | vgprs | sgprs;
|
||||
}
|
||||
|
||||
std::uint32_t MergeRsrc2(const char* function, std::uint32_t back, std::uint32_t front) {
|
||||
if ((front & ~(Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable) & ~back) != 0) Fail(function, "front half requires hardware state the back half does not enable");
|
||||
return (back & ~Rsrc2UserSgprMask) | (front & (Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable));
|
||||
std::array<std::uint32_t, 2> mergeGeometryResources(std::uint32_t frontRsrc1, std::uint32_t frontRsrc2, std::uint32_t backRsrc1, std::uint32_t backRsrc2) {
|
||||
const auto frontVgprs = ((frontRsrc1 & Rsrc1VgprMask) + 1u) * 4u;
|
||||
const auto backVgprs = ((backRsrc1 & Rsrc1VgprMask) + 1u) * 4u;
|
||||
const auto frontTotal = frontVgprs + (frontRsrc2 >> 28u) * 8u;
|
||||
const auto backTotal = backVgprs + (backRsrc2 >> 28u) * 8u;
|
||||
const auto maxTotal = std::max(frontTotal, backTotal);
|
||||
const auto shared = std::max(frontVgprs, backVgprs) >= maxTotal ? 0u : (maxTotal - std::min(frontTotal, backTotal) + 7u) / 64u;
|
||||
const auto vgprs = std::max(frontRsrc1 & Rsrc1VgprMask, backRsrc1 & Rsrc1VgprMask);
|
||||
const auto rsrc1Field = std::max((frontRsrc1 >> 29u) & 3u, (backRsrc1 >> 29u) & 3u);
|
||||
const auto rsrc2Field = std::max((frontRsrc2 >> 16u) & 3u, (backRsrc2 >> 16u) & 3u);
|
||||
const auto rsrc1 = (backRsrc1 & ~(Rsrc1VgprMask | (3u << 29u))) | vgprs | (rsrc1Field << 29u);
|
||||
const auto rsrc2 = (backRsrc2 & ~(0xf0000000u | (3u << 16u) | Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable)) | ((shared & 15u) << 28u) | (rsrc2Field << 16u) | (frontRsrc2 & (Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable));
|
||||
return {rsrc1, rsrc2};
|
||||
}
|
||||
|
||||
const ShaderRegister* FindHalfRegister(const Shader* shader, std::uint32_t offset) {
|
||||
@@ -149,10 +152,14 @@ GeometryLayout ComputeGeometryLayout(const char* function, const Shader* front,
|
||||
const auto* frontRsrc1 = FindHalfRegister(front, SPI_SHADER_PGM_RSRC1_GS);
|
||||
const auto* frontRsrc2 = FindHalfRegister(front, SPI_SHADER_PGM_RSRC2_GS);
|
||||
if (frontRsrc1 == nullptr || frontRsrc2 == nullptr) Fail(function, "front half has no GS resource registers");
|
||||
const auto* backRsrc1 = FindHalfRegister(back, SPI_SHADER_PGM_RSRC1_GS);
|
||||
const auto* backRsrc2 = FindHalfRegister(back, SPI_SHADER_PGM_RSRC2_GS);
|
||||
if (backRsrc1 == nullptr || backRsrc2 == nullptr) Fail(function, "back half has no GS resource registers");
|
||||
const auto resources = mergeGeometryResources(frontRsrc1->value, frontRsrc2->value, backRsrc1->value, backRsrc2->value);
|
||||
layout.sh.assign(back->sh_registers, back->sh_registers + back->num_sh_registers);
|
||||
for (auto& reg : layout.sh) {
|
||||
if (reg.offset == SPI_SHADER_PGM_RSRC1_GS) reg.value = MergeRsrc1(reg.value, frontRsrc1->value);
|
||||
else if (reg.offset == SPI_SHADER_PGM_RSRC2_GS) reg.value = MergeRsrc2(function, reg.value, frontRsrc2->value);
|
||||
if (reg.offset == SPI_SHADER_PGM_RSRC1_GS) reg.value = resources[0];
|
||||
else if (reg.offset == SPI_SHADER_PGM_RSRC2_GS) reg.value = resources[1];
|
||||
}
|
||||
for (std::uint32_t i = 0; i < front->num_sh_registers; ++i) {
|
||||
const auto& reg = front->sh_registers[i];
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "prx/libSceAgc/Misc/include/StaticBuffer.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int GRAPHICS5_ERROR_INVALID_STATIC_BUFFER = static_cast<int>(0x8a6c000bu);
|
||||
constexpr std::uint32_t StaticBufferCount = 6;
|
||||
constexpr std::size_t StaticBufferBytes = 16;
|
||||
constexpr std::size_t StaticQwordOffset = 16;
|
||||
|
||||
std::uint8_t* StaticTable() {
|
||||
return static_cast<std::uint8_t*>(AgcDriverStaticTable_nid_postfix());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAgcSetStaticBuffer(std::uint32_t index, const void* buffer) {
|
||||
if (index >= StaticBufferCount) return GRAPHICS5_ERROR_INVALID_STATIC_BUFFER;
|
||||
std::memcpy(StaticTable() + std::size_t{index} * StaticBufferBytes, buffer, StaticBufferBytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcGetStaticBuffer(std::uint32_t index, void* buffer) {
|
||||
if (index >= StaticBufferCount) return GRAPHICS5_ERROR_INVALID_STATIC_BUFFER;
|
||||
std::memcpy(buffer, StaticTable() + std::size_t{index} * StaticBufferBytes, StaticBufferBytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcSetStaticQword(std::uint32_t index, std::uint64_t value) {
|
||||
std::memcpy(StaticTable() + StaticQwordOffset + std::size_t{index} * sizeof(value), &value, sizeof(value));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcGetStaticQword(std::uint32_t index, std::uint64_t* value) {
|
||||
std::memcpy(value, StaticTable() + StaticQwordOffset + std::size_t{index} * sizeof(*value), sizeof(*value));
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,34 +45,29 @@ extern "C" {
|
||||
int APS5_VABI sceAgcWaitRegMemPatchAddress(std::uint32_t* cmd, const volatile void* address) {
|
||||
auto* wait = Agc::Command::ValidateWait(cmd, __func__);
|
||||
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
|
||||
const auto alignment = ((wait[0] >> 8u) & 0xffu) == 0x3cu ? 4u : 8u;
|
||||
Agc::Command::CheckAddress(guestAddress, alignment, __func__);
|
||||
Agc::Command::CheckBits(guestAddress, 0xffffffffffffull, __func__);
|
||||
cmd[2] = (cmd[2] & 0xffff0000u) | static_cast<std::uint32_t>(guestAddress >> 32u);
|
||||
const auto lowMask = ((wait[0] >> 8u) & 0xffu) == 0x3cu ? 3u : 7u;
|
||||
cmd[2] = (cmd[2] & 0xffff0000u) | (static_cast<std::uint32_t>(guestAddress >> 32u) & 0xffffu);
|
||||
cmd[3] = static_cast<std::uint32_t>(guestAddress);
|
||||
wait[2] = static_cast<std::uint32_t>(guestAddress);
|
||||
wait[3] = static_cast<std::uint32_t>(guestAddress >> 32u);
|
||||
wait[2] = (static_cast<std::uint32_t>(guestAddress) & ~lowMask) | (wait[2] & lowMask);
|
||||
wait[3] = (static_cast<std::uint32_t>(guestAddress >> 32u) & 0x3ffffu) | (wait[3] & 0xfffc0000u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint64_t reference) {
|
||||
int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint32_t reference) {
|
||||
auto* wait = Agc::Command::ValidateWait(cmd, __func__);
|
||||
Agc::Command::CheckBits(reference, 0xffffffffu, __func__);
|
||||
wait[4] = static_cast<std::uint32_t>(reference);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint64_t mask) {
|
||||
int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint32_t mask) {
|
||||
auto* wait = Agc::Command::ValidateWait(cmd, __func__);
|
||||
Agc::Command::CheckBits(mask, 0xffffffffu, __func__);
|
||||
wait[((wait[0] >> 8u) & 0xffu) == 0x3cu ? 5 : 6] = static_cast<std::uint32_t>(mask);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcWaitRegMemPatchCompareFunction(std::uint32_t* cmd, std::uint8_t compareFunction) {
|
||||
auto* wait = Agc::Command::ValidateWait(cmd, __func__);
|
||||
Agc::Command::Require(compareFunction <= 6, __func__, "invalid wait comparison");
|
||||
wait[1] = (wait[1] & ~7u) | compareFunction;
|
||||
wait[1] = (wait[1] & ~7u) | (compareFunction & 7u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,42 +5,42 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAgcAcbPushMarkerSpan() {
|
||||
std::uint32_t* APS5_VABI sceAgcAcbPushMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcAcbSetMarkerSpan() {
|
||||
std::uint32_t* APS5_VABI sceAgcAcbSetMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcAcquireMemSetEngine() {
|
||||
int APS5_VABI sceAgcAcquireMemSetEngine(std::uint32_t* packet, std::uint8_t engine) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcDcbPushMarkerSpan() {
|
||||
std::uint32_t* APS5_VABI sceAgcDcbPushMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcDcbSetMarkerSpan() {
|
||||
std::uint32_t* APS5_VABI sceAgcDcbSetMarkerSpan(CommandBuffer* buffer, const char* text, std::uint32_t length, std::uint32_t color) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcSetSemaphoreMemory() {
|
||||
int APS5_VABI sceAgcSetSemaphoreMemory(void* memory, std::uint64_t size) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcCbMemsetExclusive() {
|
||||
std::uint32_t* APS5_VABI sceAgcCbMemsetExclusive(CommandBuffer* buffer, void* destination, const void* pattern, std::uint64_t size) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAgcSetShaderInstrumentation() {
|
||||
int APS5_VABI sceAgcSetShaderInstrumentation(std::uint32_t flags) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ extern "C" int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t ver
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexAuto(CommandBuffer* buf, std::uint32_t indexCount, std::uint64_t modifier);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirect(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint64_t modifier);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirectMulti(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint32_t countIndirect, std::uint32_t maxCountOrCount, const volatile void* countAddress, std::uint32_t strideInBytes, std::uint64_t modifier);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint64_t reference);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint64_t mask);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchAddress(std::uint32_t* cmd, const volatile void* address);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchCompareFunction(std::uint32_t* cmd, std::uint8_t compareFunction);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint32_t reference);
|
||||
extern "C" int APS5_VABI sceAgcWaitRegMemPatchMask(std::uint32_t* cmd, std::uint32_t mask);
|
||||
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddress_0090(std::uint32_t** addr, std::uint32_t* cmd, int type);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer* buf, std::uint32_t offset, const std::uint32_t* values, std::uint32_t numValues);
|
||||
extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateOp_0100(CommandBuffer* buf, std::uint32_t operation);
|
||||
@@ -531,18 +533,28 @@ void testMemory() {
|
||||
check(packet[8] == 0x55667788u && packet[9] == 0x11223344u && packet[12] == 2u, "wait reference or poll interval mismatch");
|
||||
sceAgcWaitRegMemPatchReference(packet, 7);
|
||||
check(packet[8] == 7 && packet[9] == 0x11223344u, "reference patch changed the high word");
|
||||
const auto before = storage.words;
|
||||
expectFailure([&] { sceAgcWaitRegMemPatchReference(packet, 0x100000000ull); });
|
||||
check(storage.words == before, "invalid memory operation modified packet memory");
|
||||
auto* truncated = Agc::Command::WriteWait(&storage.buffer, 0, 3, 0, 0, &value, 0x100000000ull, 0xffffffff00000001ull, 32, __func__);
|
||||
check(truncated[8] == 0u && truncated[9] == 1u, "32-bit wait did not keep the low halves of the reference and mask");
|
||||
sceAgcWaitRegMemPatchMask(packet, 0x0f0f0f0fu);
|
||||
check(packet[10] == 0x0f0f0f0fu && packet[11] == 0xffffffffu && packet[8] == 7, "64-bit mask patch changed the wrong word");
|
||||
sceAgcWaitRegMemPatchMask(truncated, 0xff00u);
|
||||
check(truncated[9] == 0xff00u && truncated[8] == 0u && truncated[10] == 2u, "32-bit mask patch changed the wrong word");
|
||||
for (auto* waitPacket : {packet, truncated}) {
|
||||
const auto lowMask = waitPacket == packet ? 7u : 3u;
|
||||
waitPacket[6] |= lowMask;
|
||||
waitPacket[7] = 0xa5fc0000u;
|
||||
const auto tag = waitPacket[2] & 0xffff0000u;
|
||||
const std::uintptr_t address = 0x1236abcd12345679ull;
|
||||
check(sceAgcWaitRegMemPatchAddress(waitPacket, reinterpret_cast<const volatile void*>(address)) == 0, "wait address patch failed");
|
||||
check(waitPacket[2] == (tag | 0xabcdu) && waitPacket[3] == 0x12345679u, "wait wrapper address patch mismatch");
|
||||
check(waitPacket[6] == ((0x12345679u & ~lowMask) | lowMask) && waitPacket[7] == 0xa5feabcdu, "wait address patch did not preserve control bits");
|
||||
for (std::uint32_t comparison = 0; comparison <= 255; ++comparison) {
|
||||
const auto control = waitPacket[5] & ~7u;
|
||||
check(sceAgcWaitRegMemPatchCompareFunction(waitPacket, static_cast<std::uint8_t>(comparison)) == 0, "wait comparison patch failed");
|
||||
check(waitPacket[5] == (control | (comparison & 7u)), "wait comparison patch changed control bits");
|
||||
}
|
||||
}
|
||||
const auto beforeMask = storage.words;
|
||||
expectFailure([&] { sceAgcWaitRegMemPatchMask(packet, 0x100000000ull); });
|
||||
expectFailure([&] { sceAgcWaitRegMemPatchMask(truncated, 0x100000000ull); });
|
||||
expectFailure([&] { sceAgcWaitRegMemPatchMask(packet + 4, 1); });
|
||||
check(storage.words == beforeMask, "invalid mask patch modified packet memory");
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ void fillStorage() {
|
||||
|
||||
void testUnregistered() {
|
||||
std::array<void*, 3> labels{storage.get(), storage.get(), storage.get()};
|
||||
expectFailure([&] { sceAgcGetSemaphoreLabel(0, &labels[1]); });
|
||||
expectFailure([] { sceAgcGetSemaphoreLabel(0, nullptr); });
|
||||
check(sceAgcGetSemaphoreLabel(0, &labels[1]) == static_cast<int>(0x8a6c0049u), "unregistered label error mismatch");
|
||||
check(sceAgcGetSemaphoreLabel(0, nullptr) == static_cast<int>(0x8a6c0049u), "unregistered null label error mismatch");
|
||||
check(labels[0] == storage.get() && labels[1] == storage.get() && labels[2] == storage.get(), "a rejected semaphore label changed the output");
|
||||
}
|
||||
|
||||
@@ -80,10 +80,10 @@ void testRegistrationRejections() {
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, memoryBytes); });
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, 0); });
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), 0); });
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory() + 1, memoryBytes); });
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory() + labelBytes, blockBytes); });
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), blockBytes - 1u); });
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), blockBytes + labelBytes); });
|
||||
check(sceAgcSetAmmSemaphoreMemory(memory() + 1, memoryBytes) == static_cast<int>(0x8a6c0002u), "unaligned semaphore memory error mismatch");
|
||||
check(sceAgcSetAmmSemaphoreMemory(memory() + labelBytes, blockBytes) == static_cast<int>(0x8a6c0002u), "unaligned semaphore memory error mismatch");
|
||||
check(sceAgcSetAmmSemaphoreMemory(memory(), blockBytes - 1u) == static_cast<int>(0x8a6c0002u), "unaligned semaphore memory error mismatch");
|
||||
check(sceAgcSetAmmSemaphoreMemory(memory(), blockBytes + labelBytes) == static_cast<int>(0x8a6c0002u), "unaligned semaphore memory error mismatch");
|
||||
expectFailure([] {
|
||||
const auto top = std::numeric_limits<std::uintptr_t>::max() & ~static_cast<std::uintptr_t>(blockBytes - 1u);
|
||||
sceAgcSetAmmSemaphoreMemory(reinterpret_cast<void*>(top), memoryBytes);
|
||||
@@ -92,18 +92,8 @@ void testRegistrationRejections() {
|
||||
testUnregistered();
|
||||
}
|
||||
|
||||
void testUnsettledClearing() {
|
||||
for (const std::uint64_t offset : {5u * labelBytes + counterBytes, memoryBytes - 1u}) {
|
||||
memory()[offset] = 0x77u;
|
||||
const auto before = snapshot();
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes); });
|
||||
checkUnchanged(before, "a registration with unsettled clearing changed memory");
|
||||
testUnregistered();
|
||||
memory()[offset] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void testRegistration() {
|
||||
std::fill(memory(), memory() + memoryBytes, std::uint8_t{0x77});
|
||||
const auto before = snapshot();
|
||||
check(sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes) == 0, "registering the AMM semaphore memory failed");
|
||||
check(std::all_of(memory(), memory() + memoryBytes, [](std::uint8_t byte) { return byte == 0; }), "registration did not clear the semaphore counters");
|
||||
@@ -123,7 +113,7 @@ void testLabels() {
|
||||
void testLabelRejections() {
|
||||
for (const std::uint32_t index : {labelCount, labelCount + 1u, std::numeric_limits<std::uint32_t>::max()}) {
|
||||
void* label = storage.get();
|
||||
expectFailure([&] { sceAgcGetSemaphoreLabel(index, &label); });
|
||||
check(sceAgcGetSemaphoreLabel(index, &label) == static_cast<int>(0x8a6c000bu), "out-of-range label error mismatch");
|
||||
check(label == storage.get(), "a rejected semaphore label changed the output");
|
||||
}
|
||||
expectFailure([] { sceAgcGetSemaphoreLabel(0, nullptr); });
|
||||
@@ -132,9 +122,9 @@ void testLabelRejections() {
|
||||
void testRepeatedRegistration() {
|
||||
std::fill(memory(), memory() + memoryBytes, std::uint8_t{0x3c});
|
||||
const auto before = snapshot();
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes); });
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(storage.get(), blockBytes); });
|
||||
expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, 0); });
|
||||
check(sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes) == static_cast<int>(0x8a6c0048u), "repeated registration error mismatch");
|
||||
check(sceAgcSetAmmSemaphoreMemory(storage.get(), blockBytes) == static_cast<int>(0x8a6c0048u), "repeated registration error mismatch");
|
||||
check(sceAgcSetAmmSemaphoreMemory(nullptr, 0) == static_cast<int>(0x8a6c0048u), "repeated registration error mismatch");
|
||||
checkUnchanged(before, "a repeated registration changed memory");
|
||||
void* label = nullptr;
|
||||
check(sceAgcGetSemaphoreLabel(labelCount - 1u, &label) == 0 && label == memory() + memoryBytes - labelBytes, "a repeated registration replaced the semaphore memory");
|
||||
@@ -198,7 +188,6 @@ int main() {
|
||||
fillStorage();
|
||||
testUnregistered();
|
||||
testRegistrationRejections();
|
||||
testUnsettledClearing();
|
||||
testRegistration();
|
||||
testLabels();
|
||||
testMemSemaphore();
|
||||
|
||||
@@ -147,7 +147,7 @@ void testFusion(std::size_t misalignment, std::uint32_t terminator = SSetpcS6) {
|
||||
const auto* frontLo = find(fused, 0x0C8u);
|
||||
check(backLo != nullptr && backLo->value == static_cast<std::uint32_t>((base + 256) >> 8u), "back program address was not patched");
|
||||
check(frontLo != nullptr && frontLo->value == static_cast<std::uint32_t>(base >> 8u), "fused program address was not patched");
|
||||
check(find(fused, 0x08Au)->value == ((1u << 24u) | (2u << 6u) | 5u), "RSRC1 register counts were not merged");
|
||||
check(find(fused, 0x08Au)->value == ((1u << 24u) | (1u << 6u) | 5u), "RSRC1 register counts were not merged");
|
||||
check(find(fused, 0x08Bu)->value == ((6u << 1u) | (1u << 7u) | (1u << 15u)), "RSRC2 user SGPRs were not taken from the front half");
|
||||
check(find(fused, 0x0B2u) != nullptr && find(fused, 0x0B2u)->value == 0x55u, "front-only SH register was lost");
|
||||
check(find(fused, 0x080u) == nullptr, "front program checksum was kept");
|
||||
@@ -208,7 +208,7 @@ void testOffchipLdsEnable() {
|
||||
Shader fused{};
|
||||
check(sceAgcFuseShaderHalves_0200(&fused, &halves.front, &halves.back, aligned(storage, 256)) == 0, "offchip LDS enable rejected by fusion");
|
||||
const auto* resource = find(fused, 0x08bu);
|
||||
check(resource != nullptr && resource->value == ((6u << 1u) | (1u << 7u) | (1u << 15u) | frontEnable | backEnable), "fusion lost offchip LDS enable or changed the back-half state or front user SGPRs");
|
||||
check(resource != nullptr && resource->value == ((6u << 1u) | (1u << 7u) | (1u << 15u) | frontEnable), "fusion did not take offchip LDS enable and user SGPRs from the front half");
|
||||
}
|
||||
}
|
||||
Halves reported;
|
||||
@@ -222,6 +222,38 @@ void testOffchipLdsEnable() {
|
||||
check(find(fused, 0x08bu)->value == 0x00af0004u, "reported shader resources did not retain the front offchip LDS enable");
|
||||
}
|
||||
|
||||
void testResourceMerging() {
|
||||
struct Case {
|
||||
std::uint32_t front1;
|
||||
std::uint32_t front2;
|
||||
std::uint32_t back1;
|
||||
std::uint32_t back2;
|
||||
std::uint32_t expected1;
|
||||
std::uint32_t expected2;
|
||||
};
|
||||
const Case cases[] = {
|
||||
{0u, 0x80000000u, 0u, 0u, 0u, 0x10000000u},
|
||||
{1u, 0xf0000000u, 3u, 0u, 3u, 0x10000000u},
|
||||
{0u, 0xf0000000u, 0u, 0xf0000000u, 0u, 0u},
|
||||
{0x400003c1u, 0x0006000cu, 0x61000043u, 0x00018001u, 0x61000043u, 0x0006800du},
|
||||
{0x60000007u, 0x00010000u, 0x21000041u, 0x00020000u, 0x61000047u, 0x00020000u}
|
||||
};
|
||||
for (const auto& entry : cases) {
|
||||
Halves halves;
|
||||
halves.frontSh[1].value = entry.front1;
|
||||
halves.frontSh[2].value = entry.front2;
|
||||
halves.backSh[2].value = entry.back1;
|
||||
halves.backSh[3].value = entry.back2;
|
||||
SizeAlign size{};
|
||||
check(sceAgcGetFusedShaderSize_0080(&size, &halves.front, &halves.back) == 0, "resource merge size query failed");
|
||||
std::vector<std::uint8_t> scratch(size.m_size);
|
||||
Shader fused{};
|
||||
check(sceAgcFuseShaderHalves_0200(&fused, &halves.front, &halves.back, scratch.data()) == 0, "resource merge failed");
|
||||
check(find(fused, 0x08au)->value == entry.expected1, "resource register 1 merge mismatch");
|
||||
check(find(fused, 0x08bu)->value == entry.expected2, "resource register 2 merge mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
void testRejections() {
|
||||
Halves halves;
|
||||
SizeAlign size{};
|
||||
@@ -249,11 +281,6 @@ void testRejections() {
|
||||
noTrailer.frontCode[5] = 0;
|
||||
expectFailure([&] { sceAgcGetFusedShaderSize_0080(&size, &noTrailer.front, &noTrailer.back); });
|
||||
}
|
||||
{
|
||||
Halves hardware;
|
||||
hardware.frontSh[2].value |= 1u << 16u;
|
||||
expectFailure([&] { sceAgcGetFusedShaderSize_0080(&size, &hardware.front, &hardware.back); });
|
||||
}
|
||||
{
|
||||
Halves constants;
|
||||
constants.back.embedded_constant_buffer_size_dqw = 1;
|
||||
@@ -305,6 +332,7 @@ int main() {
|
||||
testFusion(0, SSwappcNullS6);
|
||||
testScratchReuse();
|
||||
testOffchipLdsEnable();
|
||||
testResourceMerging();
|
||||
testRejections();
|
||||
testHullHalves();
|
||||
LibcRunShutdown_nid_postfix();
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "prx/libSceAgc/Misc/include/StaticBuffer.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uintptr_t StaticTable = 0xfe0040000ull + GuestArena::GuestAddressOffset;
|
||||
constexpr int InvalidStaticBuffer = static_cast<int>(0x8a6c000bu);
|
||||
|
||||
void check(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
std::array<std::uint64_t, 2> slot(std::uint32_t index) {
|
||||
std::array<std::uint64_t, 2> words{};
|
||||
std::memcpy(words.data(), reinterpret_cast<const void*>(StaticTable + index * 16u), sizeof(words));
|
||||
return words;
|
||||
}
|
||||
|
||||
void testBuffers() {
|
||||
std::array<std::uint64_t, 2> fetchShaderTable{};
|
||||
check(sceAgcGetStaticBuffer(0, fetchShaderTable.data()) == 0, "reading static buffer 0 failed");
|
||||
check(fetchShaderTable == slot(0) && fetchShaderTable[0] >> 48u == 16u, "static buffer 0 is not the driver's fetch shader table");
|
||||
const std::array<std::uint64_t, 2> written{0x1122334455667788ull, 0x99aabbccddeeff00ull};
|
||||
check(sceAgcSetStaticBuffer(5, written.data()) == 0, "writing static buffer 5 failed");
|
||||
check(slot(5) == written, "static buffer 5 was not stored at the sixth 16-byte slot");
|
||||
std::array<std::uint64_t, 2> read{};
|
||||
check(sceAgcGetStaticBuffer(5, read.data()) == 0 && read == written, "static buffer 5 did not read back");
|
||||
check(sceAgcSetStaticBuffer(6, written.data()) == InvalidStaticBuffer, "static buffer index 6 was accepted");
|
||||
check(sceAgcGetStaticBuffer(6, read.data()) == InvalidStaticBuffer, "static buffer index 6 was read");
|
||||
check(slot(0) == fetchShaderTable, "static buffer writes changed slot 0");
|
||||
}
|
||||
|
||||
void testQwords() {
|
||||
check(sceAgcSetStaticQword(0, 0x0123456789abcdefull) == 0, "writing static qword 0 failed");
|
||||
check(sceAgcSetStaticQword(3, 0xfedcba9876543210ull) == 0, "writing static qword 3 failed");
|
||||
check(slot(1)[0] == 0x0123456789abcdefull && slot(2)[1] == 0xfedcba9876543210ull, "static qwords are not stored after the first 16-byte slot");
|
||||
std::uint64_t value = 0;
|
||||
check(sceAgcGetStaticQword(3, &value) == 0 && value == 0xfedcba9876543210ull, "static qword 3 did not read back");
|
||||
std::array<std::uint64_t, 2> buffer{};
|
||||
check(sceAgcGetStaticBuffer(1, buffer.data()) == 0 && buffer[0] == 0x0123456789abcdefull, "static qword 0 is not the first half of static buffer 1");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
#ifdef ANYPS5_ENABLE_ASAN
|
||||
std::puts("skipped: the static table lives at the PS5's fixed 0xfe0040000, which an ANYPS5_ENABLE_ASAN build cannot map");
|
||||
return 0;
|
||||
#endif
|
||||
try {
|
||||
testBuffers();
|
||||
testQwords();
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -79,6 +79,18 @@ foreach(agcReadbackKind IN ITEMS Uint Depth)
|
||||
list(APPEND agcReadbackHeaders ${agcReadbackHeader})
|
||||
endforeach()
|
||||
|
||||
set(agcMultisampleUploadSpv ${agcTextureDetileGeneratedDir}/MultisampleUpload.spv)
|
||||
set(agcMultisampleUploadHeader ${agcTextureDetileGeneratedDir}/MultisampleUpload_spv.h)
|
||||
add_custom_command(
|
||||
OUTPUT "${agcMultisampleUploadHeader}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}"
|
||||
COMMAND "$<TARGET_FILE:glslang-standalone>" -V --target-env vulkan1.1 -o "${agcMultisampleUploadSpv}" "${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/MultisampleUpload.comp"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${agcMultisampleUploadSpv} -DOUTPUT=${agcMultisampleUploadHeader}
|
||||
-DSYMBOL=MULTISAMPLE_UPLOAD_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/MultisampleUpload.comp" glslang-standalone
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set(agcMeshArgumentsShaderSpv ${agcTextureDetileGeneratedDir}/MeshArguments.spv)
|
||||
set(agcMeshArgumentsShaderHeader ${agcTextureDetileGeneratedDir}/MeshArguments_spv.h)
|
||||
add_custom_command(
|
||||
@@ -130,6 +142,28 @@ add_custom_command(
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set(agcDepthSampleShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/DepthSample.comp)
|
||||
foreach(agcDepthSampleKind IN ITEMS Single Multisample)
|
||||
string(TOUPPER ${agcDepthSampleKind} agcDepthSampleSymbol)
|
||||
if(agcDepthSampleKind STREQUAL "Multisample")
|
||||
set(agcDepthSampleMultisampled 1)
|
||||
else()
|
||||
set(agcDepthSampleMultisampled 0)
|
||||
endif()
|
||||
set(agcDepthSampleShaderSpv ${agcTextureDetileGeneratedDir}/DepthSample${agcDepthSampleKind}.spv)
|
||||
set(agcDepthSampleShaderHeader ${agcTextureDetileGeneratedDir}/DepthSample${agcDepthSampleKind}_spv.h)
|
||||
add_custom_command(
|
||||
OUTPUT "${agcDepthSampleShaderHeader}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}"
|
||||
COMMAND "$<TARGET_FILE:glslang-standalone>" -V --target-env vulkan1.1 -DMULTISAMPLED=${agcDepthSampleMultisampled} -o "${agcDepthSampleShaderSpv}" "${agcDepthSampleShaderSource}"
|
||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${agcDepthSampleShaderSpv} -DOUTPUT=${agcDepthSampleShaderHeader}
|
||||
-DSYMBOL=DEPTH_SAMPLE_${agcDepthSampleSymbol}_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
|
||||
DEPENDS "${agcDepthSampleShaderSource}" glslang-standalone
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND agcDepthSampleShaderHeaders ${agcDepthSampleShaderHeader})
|
||||
endforeach()
|
||||
|
||||
set(agcSampleCounterShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/SampleCounter.comp)
|
||||
set(agcSampleCounterShaderSpv ${agcTextureDetileGeneratedDir}/SampleCounter.spv)
|
||||
set(agcSampleCounterShaderHeader ${agcTextureDetileGeneratedDir}/SampleCounter_spv.h)
|
||||
@@ -312,8 +346,10 @@ set(agcDriverSources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/TextureFormat.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/DccMetadata.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/TextureTiling.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/MultisampleTextureLayout.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/TextureDetiler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/MultisampleReadback.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/MultisampleUpload.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Texture.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/PassHazards.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/SampleOrder.cpp
|
||||
@@ -323,7 +359,9 @@ set(agcDriverSources
|
||||
${agcSampleOrderShaderHeaders}
|
||||
${agcTextureDetileShaderHeader}
|
||||
${agcReadbackHeaders}
|
||||
${agcMultisampleUploadHeader}
|
||||
${agcCmaskClearShaderHeader}
|
||||
${agcDepthSampleShaderHeaders}
|
||||
${agcMeshArgumentsShaderHeader}
|
||||
${agcTextureDetileImageShaderHeader}
|
||||
${agcTextureRepackShaderHeader}
|
||||
@@ -733,15 +771,6 @@ if(BUILD_TESTING)
|
||||
add_test(NAME agc_pipeline_link_optimization COMMAND agc_pipeline_link_optimization_tests)
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_robust_buffer_access_tests EXCLUDE_FROM_ALL tests/RobustBufferAccess.cpp)
|
||||
target_include_directories(agc_robust_buffer_access_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
|
||||
set_target_properties(agc_robust_buffer_access_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_robust_buffer_access_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_robust_buffer_access_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_robust_buffer_access COMMAND agc_robust_buffer_access_tests)
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_draw_inputs_in_place_driver_tests EXCLUDE_FROM_ALL tests/DrawInputsInPlace.cpp)
|
||||
target_include_directories(agc_draw_inputs_in_place_driver_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
|
||||
set_target_properties(agc_draw_inputs_in_place_driver_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
@@ -1427,9 +1456,9 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_test(NAME agc_reimport_bench COMMAND agc_driver_recorder_tests --reimport-bench)
|
||||
add_test(NAME agc_trimmed_import COMMAND agc_driver_recorder_tests --trimmed-import-only)
|
||||
set_tests_properties(agc_reimport_bench agc_trimmed_import PROPERTIES SKIP_RETURN_CODE 77)
|
||||
set_tests_properties(agc_reimport_bench agc_trimmed_import PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
add_test(NAME agc_host_import_cut_lifetime COMMAND agc_driver_recorder_tests --host-import-cut-only)
|
||||
set_tests_properties(agc_reimport_bench agc_host_import_cut_lifetime PROPERTIES SKIP_RETURN_CODE 77)
|
||||
set_tests_properties(agc_reimport_bench agc_host_import_cut_lifetime PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
add_test(NAME agc_unchanged_cpu_stamps COMMAND agc_driver_recorder_tests --unchanged-cpu-stamps-only)
|
||||
@@ -1438,6 +1467,25 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(agc_unchanged_cpu_stamps PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
foreach(mode IN ITEMS map ring shared uncached)
|
||||
add_test(NAME agc_collect_mapping_history_${mode} COMMAND agc_driver_recorder_tests --collect-mapping-history-only)
|
||||
set_tests_properties(agc_collect_mapping_history_${mode} PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_collect_mapping_history_${mode} PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endforeach()
|
||||
set_tests_properties(agc_collect_mapping_history_ring PROPERTIES ENVIRONMENT "APS5_NO_EPOCH_COLLECT_MAP=1")
|
||||
set_tests_properties(agc_collect_mapping_history_shared PROPERTIES ENVIRONMENT "APS5_SHARED_COLLECT_MEMO=1")
|
||||
set_tests_properties(agc_collect_mapping_history_uncached PROPERTIES ENVIRONMENT "APS5_NO_COLLECT_MEMO=1")
|
||||
if(WIN32)
|
||||
foreach(mode IN ITEMS cached uncached)
|
||||
add_test(NAME agc_hint_mapping_history_${mode} COMMAND agc_driver_recorder_tests --hint-mapping-history-only)
|
||||
set_tests_properties(agc_hint_mapping_history_${mode} PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30
|
||||
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endforeach()
|
||||
set_tests_properties(agc_hint_mapping_history_uncached PROPERTIES ENVIRONMENT "APS5_NO_COLLECT_MEMO=1")
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_test(NAME agc_partial_watch_restoration COMMAND agc_driver_recorder_tests --partial-watch-restoration-only)
|
||||
set_tests_properties(agc_partial_watch_restoration PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT_MODIFICATION
|
||||
@@ -1542,6 +1590,8 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(agc_sparse_run_import_copied PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
|
||||
add_test(NAME agc_unit_shadow COMMAND agc_driver_recorder_tests --unit-shadow-only)
|
||||
set_tests_properties(agc_unit_shadow PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000")
|
||||
add_test(NAME agc_idle_publish_wait COMMAND agc_driver_recorder_tests --idle-publish-wait-only)
|
||||
set_tests_properties(agc_idle_publish_wait PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000;APS5_WRITE_WATCH_IMPORTS=watch")
|
||||
if(WIN32)
|
||||
add_test(NAME agc_storage_cpu_snapshot COMMAND agc_driver_recorder_tests --storage-cpu-snapshot-only)
|
||||
set_tests_properties(agc_storage_cpu_snapshot PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 SKIP_RETURN_CODE 77
|
||||
@@ -1575,7 +1625,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME agc_metadata_pass COMMAND agc_driver_recorder_tests --metadata-pass-only)
|
||||
set_tests_properties(agc_metadata_pass PROPERTIES SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_recorder_read_list agc_host_import_unmap agc_parked_import_unmap agc_host_import_retirement agc_sparse_run_import agc_sparse_run_import_copied agc_unit_shadow agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_draw_snapshot_epoch agc_multisample_guest_access agc_draw_inputs_in_place agc_draw_inputs_copied agc_resident_present agc_slice_dirty agc_queued_write_groups agc_metadata_pass agc_tiled_resolve PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
set_tests_properties(agc_recorder_read_list agc_host_import_unmap agc_parked_import_unmap agc_host_import_retirement agc_sparse_run_import agc_sparse_run_import_copied agc_unit_shadow agc_idle_publish_wait agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_draw_snapshot_epoch agc_multisample_guest_access agc_draw_inputs_in_place agc_draw_inputs_copied agc_resident_present agc_slice_dirty agc_queued_write_groups agc_metadata_pass agc_tiled_resolve PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
add_test(NAME agc_host_import_second_mapping COMMAND agc_driver_recorder_tests --host-import-second-mapping-only)
|
||||
set_tests_properties(agc_host_import_second_mapping PROPERTIES SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION
|
||||
@@ -1609,6 +1659,13 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
set_property(TEST agc_driver_host_import_lifetime APPEND PROPERTY ENVIRONMENT_MODIFICATION
|
||||
"APS5_RELEASE_UNDER_LOCK=unset:" "APS5_RELEASE_ON_UNLOCK=unset:")
|
||||
if(WIN32)
|
||||
add_test(NAME agc_driver_host_import_lifetime_other_family COMMAND agc_driver_recorder_tests --host-import-lifetime-other-family-only)
|
||||
set_tests_properties(agc_driver_host_import_lifetime_other_family PROPERTIES
|
||||
ENVIRONMENT "APS5_PIN_WAIT_MS=2000" SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
set_property(TEST agc_driver_host_import_lifetime_other_family APPEND PROPERTY ENVIRONMENT_MODIFICATION
|
||||
"APS5_RELEASE_UNDER_LOCK=unset:" "APS5_RELEASE_ON_UNLOCK=unset:")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_test_executable(agc_recorder_kept_bytes_tests EXCLUDE_FROM_ALL tests/RecorderKeptBytes.cpp)
|
||||
@@ -1823,6 +1880,28 @@ endif()
|
||||
set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_graphics_tests)
|
||||
|
||||
add_test_executable(agc_driver_msaa_layout_tests EXCLUDE_FROM_ALL
|
||||
tests/MultisampleTextureLayout.cpp
|
||||
Graphics/src/MultisampleTextureLayout.cpp
|
||||
)
|
||||
target_include_directories(agc_driver_msaa_layout_tests PRIVATE
|
||||
${LIBS_INCLUDE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/core/shader/recompiler
|
||||
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
|
||||
)
|
||||
target_link_libraries(agc_driver_msaa_layout_tests PRIVATE libc)
|
||||
set_target_properties(agc_driver_msaa_layout_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_msaa_layout_tests)
|
||||
if(BUILD_TESTING)
|
||||
set_target_properties(agc_driver_msaa_layout_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
|
||||
add_test(NAME agc_driver_msaa_layout COMMAND agc_driver_msaa_layout_tests)
|
||||
set_tests_properties(agc_driver_msaa_layout PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_msaa_layout PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
foreach(agcVertexRangeShader IN ITEMS VertexRange.vert VertexRangeProducer.frag)
|
||||
string(REPLACE "." "_" agcVertexRangeName "${agcVertexRangeShader}")
|
||||
string(TOUPPER "${agcVertexRangeName}_SPV" agcVertexRangeSymbol)
|
||||
@@ -2249,7 +2328,7 @@ if(BUILD_TESTING)
|
||||
set_tests_properties(agc_driver_queued_raw_shader PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=8" TIMEOUT 30)
|
||||
add_test(NAME agc_driver_partially_resident_pending_stores COMMAND agc_driver_partially_resident_snapshot_tests --pending-stores)
|
||||
set_tests_properties(agc_driver_multisample_readback PROPERTIES TIMEOUT 20)
|
||||
foreach(agcReadbackCase IN ITEMS uint8 uint16 uint64 uint128 depth)
|
||||
foreach(agcReadbackCase IN ITEMS uint8 uint16 uint64 uint128 depth upload retile depth_retile)
|
||||
add_test(NAME agc_driver_multisample_readback_${agcReadbackCase} COMMAND agc_driver_multisample_readback_tests ${agcReadbackCase})
|
||||
set_tests_properties(agc_driver_multisample_readback_${agcReadbackCase} PROPERTIES TIMEOUT 20 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||
if(WIN32)
|
||||
@@ -2700,6 +2779,13 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
add_test(NAME agc_driver_array_layer_target_cpu COMMAND agc_driver_array_layer_target_tests)
|
||||
set_tests_properties(agc_driver_array_layer_target_cpu PROPERTIES
|
||||
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_HOST_IMPORT=1" TIMEOUT 30 SKIP_RETURN_CODE 77)
|
||||
if(WIN32)
|
||||
set_tests_properties(agc_driver_array_layer_target_cpu PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable registered_workgroup registered_workgroup_sync abi_deferred_compute abi_deferred_pixel abi_deferred_stages abi_deferred_rectangle abi_deferred_undecodable_state abi_deferred_registration_failure abi_deferred_vertex_inputs abi_deferred_rectangle_pairs lazy lazy_sync)
|
||||
set(preparedTest "agc_driver_prepared_shaders_${preparedMode}")
|
||||
if(preparedMode MATCHES "^abi_deferred_")
|
||||
|
||||
@@ -24,5 +24,6 @@ extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader);
|
||||
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
|
||||
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive);
|
||||
extern "C" void AgcDriverResolveGraphicsAbi_nid_postfix(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType);
|
||||
extern "C" void* AgcDriverStaticTable_nid_postfix();
|
||||
|
||||
#endif
|
||||
@@ -1,14 +0,0 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_ROBUSTBUFFERACCESS_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_ROBUSTBUFFERACCESS_HPP
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
inline bool RobustBufferAccess2Usable(VkDriverId driver) {
|
||||
return driver != VK_DRIVER_ID_AMD_PROPRIETARY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
VulkanDevice& operator=(const VulkanDevice&) = delete;
|
||||
std::string DeviceName() const;
|
||||
std::string DriverInfo() const;
|
||||
bool SupportsImageFormat(VkFormat format, VkImageUsageFlags usage) const;
|
||||
bool CalibrateClock(double& gpuNs, std::chrono::steady_clock::time_point& cpu) const;
|
||||
ShaderRecompiler::SpirvTarget Target() const;
|
||||
Graphics::SampleOrder HostSampleOrder(const Graphics::State& graphics) const;
|
||||
@@ -322,6 +323,7 @@ public:
|
||||
bool DrawInputsInPlace() const;
|
||||
std::optional<std::string> KnownDrawRejection(const Graphics::State& graphics, std::span<const Graphics::CompiledShader> shaders) const;
|
||||
void ColorMetadataPass(const Graphics::ColorMetadataPass& pass);
|
||||
void DepthCopyPass(const Graphics::DepthCopyPass& pass);
|
||||
// A draw-cache hit recorded from its recipe (Graphics::DrawWithRecipe), under the mutex after
|
||||
// the packet's labels; Rebuild when the recipe's device is not this one or DrawWithRecipe
|
||||
// missed (nothing recorded: the caller runs Draw with the hit's stages and re-attaches).
|
||||
|
||||
@@ -25,6 +25,17 @@ std::optional<DrawVerdict> Driver::precheckDraw(const QueueState& queue, const S
|
||||
localDevice->ColorMetadataPass(*pass);
|
||||
return DrawVerdict::Drawn;
|
||||
}
|
||||
if (const auto copy = Graphics::DecodeDepthCopyPass(queue)) {
|
||||
require(!drawParameters.indirect, "indirect DB depth copies are unsupported");
|
||||
if (DrawPipeline::Active()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, (packet[0] >> 8u) & 0xffu);
|
||||
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Draw);
|
||||
std::lock_guard gpuLock(GuestMemory::GpuMutex());
|
||||
if (device == nullptr) device = std::make_shared<VulkanDevice>();
|
||||
const std::shared_ptr<VulkanDevice> localDevice = device;
|
||||
recordLabelsForPacket(localDevice.get(), submission.queue);
|
||||
localDevice->DepthCopyPass(*copy);
|
||||
return DrawVerdict::Drawn;
|
||||
}
|
||||
if (Graphics::DepthResummarizeDrawsNothing(queue)) return DrawVerdict::Nothing;
|
||||
rejected = Graphics::DepthMaintenanceRejection(queue);
|
||||
if (!rejected.empty()) return DrawVerdict::Rejected;
|
||||
|
||||
@@ -291,3 +291,8 @@ std::unique_lock<std::mutex> HoldAsyncLog() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" void* AgcDriverStaticTable_nid_postfix() {
|
||||
static_cast<void>(AgcDriver::DriverDetail::Driver::Get());
|
||||
return reinterpret_cast<void*>(AgcDriver::DriverDetail::FsTableAddress);
|
||||
}
|
||||
@@ -181,10 +181,11 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> QueuedDrawReadRanges(const
|
||||
}
|
||||
if (stages.empty() || stages.front().program == nullptr) return ranges;
|
||||
for (const auto& attribute : stages.front().program->vertexAttributes) {
|
||||
if (Graphics::NullVertexDescriptor(attribute) || Graphics::VertexFetchOutOfRange(attribute)) continue;
|
||||
if (Graphics::NullVertexDescriptor(attribute)) continue;
|
||||
const auto address = attribute.resource.fields[0] | (static_cast<std::uint64_t>(attribute.resource.fields[1] & 0xffffu) << 32u);
|
||||
std::uint64_t bytes = 0;
|
||||
try {
|
||||
if (Graphics::VertexFetchOutOfRange(attribute)) continue;
|
||||
bytes = Graphics::VertexBufferExtent(attribute);
|
||||
} catch (const std::runtime_error&) {
|
||||
continue;
|
||||
|
||||
@@ -813,18 +813,33 @@ constexpr std::size_t WritePagesPerBlock = WriteBlockBytes / WritePageBytes;
|
||||
|
||||
enum class StampKind : std::uint8_t { Cpu, Driver, ImportWindow, Fresh };
|
||||
|
||||
constexpr std::array<std::byte, WritePageBytes> ZeroPage{};
|
||||
std::uint64_t HashPage(const std::byte* page) {
|
||||
constexpr std::uint64_t prime1 = 0x9E3779B185EBCA87ull;
|
||||
constexpr std::uint64_t prime2 = 0xC2B2AE3D27D4EB4Full;
|
||||
constexpr std::uint64_t prime3 = 0x165667B19E3779F9ull;
|
||||
constexpr std::uint64_t prime4 = 0x85EBCA77C2B2AE63ull;
|
||||
const auto round = [](std::uint64_t accumulator, std::uint64_t word) { return std::rotl(accumulator + word * prime2, 31) * prime1; };
|
||||
std::array<std::uint64_t, 4> lanes{prime1 + prime2, prime2, 0, 0 - prime1};
|
||||
for (std::size_t offset = 0; offset < WritePageBytes; offset += 4 * sizeof(std::uint64_t)) {
|
||||
std::array<std::uint64_t, 4> words;
|
||||
std::memcpy(words.data(), page + offset, sizeof(words));
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) lanes[lane] = round(lanes[lane], words[lane]);
|
||||
}
|
||||
std::uint64_t hash = std::rotl(lanes[0], 1) + std::rotl(lanes[1], 7) + std::rotl(lanes[2], 12) + std::rotl(lanes[3], 18);
|
||||
for (const auto lane : lanes) hash = (hash ^ round(0, lane)) * prime1 + prime4;
|
||||
hash ^= hash >> 33;
|
||||
hash *= prime2;
|
||||
hash ^= hash >> 29;
|
||||
hash *= prime3;
|
||||
return hash ^ (hash >> 32);
|
||||
}
|
||||
|
||||
struct WindowCopy {
|
||||
static constexpr std::uint32_t Zero = std::numeric_limits<std::uint32_t>::max();
|
||||
std::uint64_t first;
|
||||
std::span<const std::byte> bytes;
|
||||
std::span<const std::uint32_t> slots;
|
||||
std::span<const std::uint64_t> hashes;
|
||||
|
||||
bool Holds(std::uintptr_t page) const {
|
||||
const auto slot = slots[(page - first) / WritePageBytes];
|
||||
const auto* expected = slot == Zero ? ZeroPage.data() : bytes.data() + static_cast<std::size_t>(slot) * WritePageBytes;
|
||||
return std::memcmp(reinterpret_cast<const void*>(page), expected, WritePageBytes) == 0;
|
||||
return HashPage(reinterpret_cast<const std::byte*>(page)) == hashes[(page - first) / WritePageBytes];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -873,6 +888,7 @@ struct WriteTracker {
|
||||
std::uintptr_t end = 0;
|
||||
std::uint64_t epoch = 0;
|
||||
std::uint64_t unwatched = 0;
|
||||
std::uint64_t mapping = 0;
|
||||
};
|
||||
std::array<Memo, 256> memo{};
|
||||
std::size_t nextMemo = 0;
|
||||
@@ -1348,19 +1364,20 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi
|
||||
// must not differ between them.
|
||||
const auto epoch = currentCollectEpoch();
|
||||
const auto unwatched = unwatchSerial.load(std::memory_order_acquire);
|
||||
const auto mapping = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
const bool useMemo = memoized && collectMemoEnabled() && bytes != 0;
|
||||
if (useMemo && !sharedCollectMemo()) {
|
||||
// An entry exists only for a completed walk of an in-arena range, so the tracker is
|
||||
// initialized and watched; nothing below the lock needs asking.
|
||||
if (epochCollectMapEnabled()) {
|
||||
const auto& map = epochCollectMap(epoch);
|
||||
if (const auto found = map.ranges.find(first); found != map.ranges.end() && found->second.unwatched == unwatched && stop <= found->second.end) {
|
||||
if (const auto found = map.ranges.find(first); found != map.ranges.end() && found->second.unwatched == unwatched && found->second.mapping == mapping && stop <= found->second.end) {
|
||||
collectMemoHits.fetch_add(1, std::memory_order_relaxed);
|
||||
return tracker.generation.load(std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
for (const auto& entry : threadCollectMemo.entries) {
|
||||
if (entry.epoch == epoch && entry.unwatched == unwatched && entry.begin <= first && stop <= entry.end) {
|
||||
if (entry.epoch == epoch && entry.unwatched == unwatched && entry.mapping == mapping && entry.begin <= first && stop <= entry.end) {
|
||||
// The current generation, not the memoized one: blocks stamped since (MarkWritten, an
|
||||
// overlapping collect) were written before this caller reads, and an older value would
|
||||
// make every later UnchangedSince fail until the epoch ends.
|
||||
@@ -1375,7 +1392,7 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi
|
||||
auto cursor = first;
|
||||
if (useMemo && sharedCollectMemo()) {
|
||||
for (const auto& entry : tracker.memo) {
|
||||
if (entry.epoch == epoch && entry.unwatched == unwatchSerial.load(std::memory_order_relaxed) && entry.begin <= cursor && stop <= entry.end) {
|
||||
if (entry.epoch == epoch && entry.unwatched == unwatchSerial.load(std::memory_order_relaxed) && entry.mapping == mapping && entry.begin <= cursor && stop <= entry.end) {
|
||||
collectMemoHits.fetch_add(1, std::memory_order_relaxed);
|
||||
return tracker.generation;
|
||||
}
|
||||
@@ -1396,12 +1413,12 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi
|
||||
// Only a completed walk is remembered; a failed one (uncommitted pages) returned 0 above.
|
||||
if (collectMemoEnabled()) {
|
||||
const auto serial = unwatchSerial.load(std::memory_order_relaxed);
|
||||
if (sharedCollectMemo()) tracker.memo[tracker.nextMemo++ % tracker.memo.size()] = {first, stop, epoch, serial};
|
||||
if (sharedCollectMemo()) tracker.memo[tracker.nextMemo++ % tracker.memo.size()] = {first, stop, epoch, serial, mapping};
|
||||
else {
|
||||
threadCollectMemo.entries[threadCollectMemo.next++ % threadCollectMemo.entries.size()] = {first, stop, epoch, serial};
|
||||
threadCollectMemo.entries[threadCollectMemo.next++ % threadCollectMemo.entries.size()] = {first, stop, epoch, serial, mapping};
|
||||
if (epochCollectMapEnabled()) {
|
||||
auto& entry = epochCollectMap(epoch).ranges[first];
|
||||
if (entry.unwatched != serial || entry.end < stop) entry = {first, stop, epoch, serial};
|
||||
if (entry.unwatched != serial || entry.mapping != mapping || entry.end < stop) entry = {first, stop, epoch, serial, mapping};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1567,23 +1584,16 @@ bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function
|
||||
begin = end;
|
||||
}
|
||||
}
|
||||
std::vector<std::uint32_t> slots(static_cast<std::size_t>((stop - first) / page), WindowCopy::Zero);
|
||||
std::size_t kept = 0;
|
||||
std::vector<std::uint64_t> hashes(static_cast<std::size_t>((stop - first) / page), 0);
|
||||
for (const auto& [begin, end] : spans) {
|
||||
CheckRange(reinterpret_cast<const void*>(begin), static_cast<std::size_t>(end - begin), 1, false);
|
||||
for (auto cursor = begin; cursor < end; cursor += page) {
|
||||
if (std::memcmp(reinterpret_cast<const void*>(cursor), ZeroPage.data(), page) != 0) slots[static_cast<std::size_t>((cursor - first) / page)] = static_cast<std::uint32_t>(kept++);
|
||||
hashes[static_cast<std::size_t>((cursor - first) / page)] = HashPage(reinterpret_cast<const std::byte*>(cursor));
|
||||
}
|
||||
}
|
||||
std::vector<std::byte> saved;
|
||||
saved.reserve(kept * page);
|
||||
const auto* source = reinterpret_cast<const std::byte*>(first);
|
||||
for (std::size_t index = 0; index < slots.size(); ++index) {
|
||||
if (slots[index] != WindowCopy::Zero) saved.insert(saved.end(), source + index * page, source + (index + 1) * page);
|
||||
}
|
||||
std::erase_if(spans, [&](const auto& span) { return !walkWrites(tracker, span.first, span.second, StampKind::Cpu, true); });
|
||||
if (!runImport()) return false;
|
||||
const WindowCopy window{first, saved, slots};
|
||||
const WindowCopy window{first, hashes};
|
||||
auto unwatchedFrom = first;
|
||||
for (const auto& [begin, end] : spans) {
|
||||
if (!walkWrites(tracker, begin, end, StampKind::Cpu, true, &window)) continue;
|
||||
@@ -1827,15 +1837,16 @@ std::uint64_t UntrackedHint(std::uint64_t address, std::size_t bytes) {
|
||||
tracker.initialize();
|
||||
if (!tracker.watched || !tracker.hinted(first, stop)) return 0;
|
||||
const auto unwatched = unwatchSerial.load(std::memory_order_relaxed);
|
||||
const auto mapping = GuestAllocations::GuestAllocationsGeneration_nid_postfix();
|
||||
const bool memoized = collectMemoEnabled() && std::any_of(threadHintMemo.entries.begin(), threadHintMemo.entries.end(), [&](const WriteTracker::Memo& entry) {
|
||||
return entry.epoch == epoch && entry.unwatched == unwatched && entry.begin <= first && stop <= entry.end;
|
||||
return entry.epoch == epoch && entry.unwatched == unwatched && entry.mapping == mapping && entry.begin <= first && stop <= entry.end;
|
||||
});
|
||||
if (memoized) {
|
||||
collectMemoHits.fetch_add(1, std::memory_order_relaxed);
|
||||
} else {
|
||||
const TimedAccess timed(CounterCollect, bytes);
|
||||
if (!walkHints(tracker, first, stop)) return 0;
|
||||
if (collectMemoEnabled()) threadHintMemo.entries[threadHintMemo.next++ % threadHintMemo.entries.size()] = {first, stop, epoch, unwatched};
|
||||
if (collectMemoEnabled()) threadHintMemo.entries[threadHintMemo.next++ % threadHintMemo.entries.size()] = {first, stop, epoch, unwatched, mapping};
|
||||
}
|
||||
return tracker.newestHint(first, stop);
|
||||
#else
|
||||
|
||||
@@ -470,6 +470,8 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
|
||||
case 0x49: {
|
||||
size(8);
|
||||
const auto dataSelect = packet[2] >> 29u;
|
||||
require(dataSelect != 4, "RELEASE_MEM of the 64-bit system counter (DATA_SEL 4) is not implemented");
|
||||
require(dataSelect <= 5, "reserved RELEASE_MEM DATA_SEL");
|
||||
require(dataSelect <= 3, "GDS RELEASE_MEM data is not implemented");
|
||||
require(dataSelect == 0 || address(packet[3], packet[4]) == 0 || (packet[3] & (dataSelect == 1 ? 3u : 7u)) == 0, "misaligned RELEASE_MEM destination");
|
||||
break;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/DrawInputsInPlace.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/SubgroupClock.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ShaderDeviceProfile.hpp"
|
||||
@@ -316,6 +315,7 @@ struct VulkanDevice::State {
|
||||
bool geometryShader = false;
|
||||
bool sampleRateShading = false;
|
||||
bool storageImageMultisample = false;
|
||||
bool storageImageWriteWithoutFormat = false;
|
||||
bool shaderClipDistance = false;
|
||||
bool shaderCullDistance = false;
|
||||
bool shaderClock = false;
|
||||
@@ -1693,6 +1693,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
if (enabled.shaderStorageImageMultisample) state->capabilities.push_back(spv::CapabilityStorageImageMultisample);
|
||||
state->capabilities.push_back(spv::CapabilityImageMSArray);
|
||||
enabled.shaderStorageImageWriteWithoutFormat = available.shaderStorageImageWriteWithoutFormat;
|
||||
state->storageImageWriteWithoutFormat = enabled.shaderStorageImageWriteWithoutFormat == VK_TRUE;
|
||||
enabled.shaderStorageImageReadWithoutFormat = available.shaderStorageImageReadWithoutFormat;
|
||||
// Gathers with non-constant offsets (ImageGatherExtended).
|
||||
enabled.shaderImageGatherExtended = available.shaderImageGatherExtended;
|
||||
@@ -1943,14 +1944,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
|
||||
VkPhysicalDeviceFeatures2 queried{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &robustness2};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &queried);
|
||||
}
|
||||
VkDriverId robustnessDriver = VK_DRIVER_ID_MAX_ENUM;
|
||||
if (hasExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) {
|
||||
VkPhysicalDeviceDriverProperties driver{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
|
||||
VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver};
|
||||
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &driverProperties);
|
||||
robustnessDriver = driver.driverID;
|
||||
}
|
||||
state->robustBufferAccess2 = robustness2.robustBufferAccess2 == VK_TRUE && enabled.robustBufferAccess == VK_TRUE && RobustBufferAccess2Usable(robustnessDriver);
|
||||
state->robustBufferAccess2 = robustness2.robustBufferAccess2 == VK_TRUE && enabled.robustBufferAccess == VK_TRUE;
|
||||
if (robustness2.nullDescriptor == VK_TRUE || state->robustBufferAccess2) {
|
||||
const bool nullDescriptor = robustness2.nullDescriptor == VK_TRUE;
|
||||
robustness2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT};
|
||||
@@ -3220,8 +3214,9 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
|
||||
static const bool noAdopt = std::getenv("APS5_NO_PRESENT_EPOCH") != nullptr;
|
||||
if (!noAdopt) GuestMemory::AdoptLatestCollectEpoch();
|
||||
GuestMemory::CollectWritesUncached(buffer.address, bytes);
|
||||
if (!GuestMemory::UnchangedSince(buffer.address, bytes, resident->Generation()) || (buffer.dccAddress != 0 && ResidentKeysMoved(*resident))) {
|
||||
resident->Refresh();
|
||||
if (!GuestMemory::UnchangedSince(buffer.address, bytes, resident->GenerationFor(buffer.address, bytes)) || (buffer.dccAddress != 0 && ResidentKeysMoved(*resident))) {
|
||||
if (resident->ImageLayers() > 1) resident->RefreshLayers(layer, 1);
|
||||
else resident->Refresh();
|
||||
++refreshedPresents;
|
||||
}
|
||||
++residentPresents;
|
||||
@@ -3671,6 +3666,12 @@ std::uint32_t VulkanDevice::VendorId() const {
|
||||
return state->properties.vendorID;
|
||||
}
|
||||
|
||||
bool VulkanDevice::SupportsImageFormat(VkFormat format, VkImageUsageFlags usage) const {
|
||||
VkImageFormatProperties properties{};
|
||||
const auto query = state->InstanceFunction<PFN_vkGetPhysicalDeviceImageFormatProperties>("vkGetPhysicalDeviceImageFormatProperties");
|
||||
return query(state->physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage, 0, &properties) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
ShaderRecompiler::SpirvTarget VulkanDevice::Target() const {
|
||||
require(state->shaderProfile != nullptr, "shader device profile is unavailable");
|
||||
auto target = state->shaderProfile->Target();
|
||||
@@ -3833,6 +3834,7 @@ Graphics::Context VulkanDevice::buildContext() const {
|
||||
context.fillModeNonSolid = state->fillModeNonSolid;
|
||||
context.maintenance5 = state->maintenance5;
|
||||
context.storageImageMultisample = state->storageImageMultisample;
|
||||
context.storageImageWriteWithoutFormat = state->storageImageWriteWithoutFormat;
|
||||
context.shaderClipDistance = state->shaderClipDistance;
|
||||
context.shaderCullDistance = state->shaderCullDistance;
|
||||
context.shaderTerminateInvocation = state->shaderTerminateInvocation;
|
||||
@@ -3865,6 +3867,10 @@ std::optional<std::string> VulkanDevice::KnownDrawRejection(const Graphics::Stat
|
||||
return Graphics::KnownValidationFailure(graphicsContext(), shaders, graphics);
|
||||
}
|
||||
|
||||
void VulkanDevice::DepthCopyPass(const Graphics::DepthCopyPass& pass) {
|
||||
Graphics::RunDepthCopyPass(graphicsContext(), pass);
|
||||
}
|
||||
|
||||
void VulkanDevice::ColorMetadataPass(const Graphics::ColorMetadataPass& pass) {
|
||||
Graphics::RunColorMetadataPass(graphicsContext(), pass);
|
||||
}
|
||||
@@ -4741,6 +4747,14 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
|
||||
auto& resources = *record.resources;
|
||||
const auto arguments = record.arguments;
|
||||
const auto* argumentImport = record.argumentImport;
|
||||
const auto* limit = state->properties.limits.maxComputeWorkGroupCount;
|
||||
if (argumentImport == nullptr && record.shader->program->workgroupMemoryDwords != 0 && (record.x > limit[0] || record.y > limit[1] || record.z > limit[2])) {
|
||||
char counted[128] = "";
|
||||
if (record.cpuArguments != 0) std::snprintf(counted, sizeof(counted), " (read on the CPU from the indirect arguments at 0x%llx, cpuReason %d)", static_cast<unsigned long long>(record.cpuArguments), record.cpuReason);
|
||||
char text[384];
|
||||
std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]);
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
// The record phase split (APS5_PROFILE_DRAW, rows "record: ..." of the [dispatch] totals):
|
||||
// opening the batch, the keeps, the data refresh, the barriers with the bind and the dispatch
|
||||
// itself, the pending-write notes and marks (MarkGpuWrites), and the completion registration.
|
||||
@@ -4875,17 +4889,11 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) {
|
||||
if (argumentImport != nullptr) {
|
||||
context.Resolved(&Graphics::DeviceFunctions::cmdDispatchIndirect, "vkCmdDispatchIndirect")(commands, indirectBuffer, indirectOffset);
|
||||
} else {
|
||||
const auto* limit = state->properties.limits.maxComputeWorkGroupCount;
|
||||
if (record.x <= limit[0] && record.y <= limit[1] && record.z <= limit[2]) {
|
||||
context.Resolved(&Graphics::DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, record.x, record.y, record.z);
|
||||
} else {
|
||||
char counted[128] = "";
|
||||
if (record.cpuArguments != 0) std::snprintf(counted, sizeof(counted), " (read on the CPU from the indirect arguments at 0x%llx, cpuReason %d)", static_cast<unsigned long long>(record.cpuArguments), record.cpuReason);
|
||||
if (record.shader->program->workgroupMemoryDwords != 0) {
|
||||
char text[384];
|
||||
std::snprintf(text, sizeof(text), "Vulkan dispatch: workgroup count %ux%ux%u%s exceeds device limits %ux%ux%u and a split aliases the device-memory LDS slots, whose base is computed from gl_NumWorkGroups of a single chunk", record.x, record.y, record.z, counted, limit[0], limit[1], limit[2]);
|
||||
throw std::runtime_error(text);
|
||||
}
|
||||
const auto offsets = std::ranges::count_if(record.shader->program->specialization, [](const auto& constant) { return ShaderRecompiler::PipelineSpecialization::IsWorkgroupOffset(constant.id); });
|
||||
if (offsets != ShaderRecompiler::PipelineSpecialization::WorkgroupOffsetWords) {
|
||||
char text[320];
|
||||
|
||||
@@ -241,6 +241,7 @@ struct Context {
|
||||
bool fillModeNonSolid = false;
|
||||
bool maintenance5 = false;
|
||||
bool storageImageMultisample = false;
|
||||
bool storageImageWriteWithoutFormat = false;
|
||||
bool shaderClipDistance = false;
|
||||
bool shaderCullDistance = false;
|
||||
bool nullDescriptors = false;
|
||||
|
||||
@@ -20,8 +20,15 @@ class StorageTexture;
|
||||
VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten = true);
|
||||
SampleOrderTarget DepthSurfaceOrderTarget(const Context& context, const DepthTarget& target);
|
||||
DepthSampleLocations DepthSurfaceSampleLocations(const Context& context, const DepthTarget& target);
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> FlushDepthPlanes(std::uint64_t address, std::size_t bytes);
|
||||
enum class DepthPlaneScope { Overlapped, Whole };
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> FlushDepthPlanes(std::uint64_t address, std::size_t bytes, DepthPlaneScope scope = DepthPlaneScope::Overlapped);
|
||||
bool DepthPlanesStale();
|
||||
struct DepthPlaneStoreStatistics {
|
||||
std::uint64_t stores = 0;
|
||||
std::uint64_t units = 0;
|
||||
std::uint64_t bytes = 0;
|
||||
};
|
||||
DepthPlaneStoreStatistics DepthPlaneStoreCounts();
|
||||
bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples);
|
||||
std::uint64_t DepthSurfacesSerial();
|
||||
std::uint64_t DepthSurfacesReleased();
|
||||
@@ -65,6 +72,7 @@ VkFormat HostDepthFormat(const Context& context, VkFormat format);
|
||||
float HostDepthBiasConstant(const Context& context, VkFormat format, float constant);
|
||||
std::shared_ptr<StorageTexture> DepthSurfaceStorage(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip);
|
||||
bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage);
|
||||
void CopyDepthSample(const Context& context, std::uint64_t depthAddress, std::uint32_t samples, std::uint32_t sample, StorageTexture& destination, VkImageView destinationView, VkExtent2D extent);
|
||||
|
||||
struct StorageImageClear {
|
||||
std::array<std::uint32_t, 8> image{};
|
||||
|
||||
@@ -127,6 +127,7 @@ void CountDrawSkip(DrawSkip kind, double us);
|
||||
std::shared_ptr<std::vector<std::shared_ptr<ShaderResources>>> DrawCopiedWriters();
|
||||
|
||||
void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass);
|
||||
void RunDepthCopyPass(const Context& context, const DepthCopyPass& pass);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ void SetHostImportDeferredSettles(bool enabled);
|
||||
// that heap: the other half stays for the buffers, mirrors and textures that share it.
|
||||
std::uint64_t HostImportBudget(const VkPhysicalDeviceMemoryProperties& memory, std::uint64_t physicalBytes);
|
||||
std::uint64_t HostImportLiveBytes();
|
||||
std::uint64_t HostImportBudgetRemaining(const Context& context);
|
||||
struct HostImportSettleCounts {
|
||||
std::uint64_t submitted = 0;
|
||||
std::uint64_t waited = 0;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MULTISAMPLESWIZZLEEQUATIONS_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MULTISAMPLESWIZZLEEQUATIONS_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
struct MultisampleSwizzleEquation {
|
||||
TextureTileMode tileMode;
|
||||
std::uint32_t elementBytes;
|
||||
std::uint32_t fragments;
|
||||
std::array<std::uint64_t, 16> bits;
|
||||
};
|
||||
|
||||
inline constexpr MultisampleSwizzleEquation kMultisampleSwizzleEquations[] = {
|
||||
{TextureTileMode::kZ64KBX, 1u, 2u, {0x0001000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000100000ull, 0x0000000000400000ull, 0x0000000000000040ull, 0x0000000000800000ull}},
|
||||
{TextureTileMode::kZ64KBX, 2u, 2u, {0x0000000000000000ull, 0x0001000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000000040000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0000000000000040ull}},
|
||||
{TextureTileMode::kZ64KBX, 4u, 2u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0001000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0000000000840000ull}},
|
||||
{TextureTileMode::kZ64KBX, 1u, 4u, {0x0001000000000000ull, 0x0002000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000000040000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0000000000000040ull}},
|
||||
{TextureTileMode::kZ64KBX, 2u, 4u, {0x0000000000000000ull, 0x0001000000000000ull, 0x0002000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000000000040ull, 0x0000000000440000ull}},
|
||||
{TextureTileMode::kZ64KBX, 4u, 4u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0001000000000000ull, 0x0002000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000440000ull, 0x0000000000800004ull}},
|
||||
{TextureTileMode::kZ64KBX, 1u, 8u, {0x0001000000000000ull, 0x0002000000000000ull, 0x0004000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000000000004ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000800400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0000000000840000ull}},
|
||||
{TextureTileMode::kZ64KBX, 2u, 8u, {0x0000000000000000ull, 0x0001000000000000ull, 0x0002000000000000ull, 0x0004000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000800400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000440000ull, 0x0000000000800004ull}},
|
||||
{TextureTileMode::kZ64KBX, 4u, 8u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0001000000000000ull, 0x0002000000000000ull, 0x0004000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000000440020ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000000400004ull, 0x0000000000820000ull}},
|
||||
{TextureTileMode::kR64KBX, 1u, 2u, {0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000000010000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000400000ull, 0x0000000000000040ull, 0x0000000000800000ull, 0x0001000001000000ull}},
|
||||
{TextureTileMode::kR64KBX, 2u, 2u, {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000100000ull, 0x0000000000000040ull, 0x0000000000400000ull, 0x0001000000800000ull}},
|
||||
{TextureTileMode::kR64KBX, 4u, 2u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0000000000400000ull, 0x0001000000800000ull}},
|
||||
{TextureTileMode::kR64KBX, 8u, 2u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0001000000400000ull}},
|
||||
{TextureTileMode::kR64KBX, 16u, 2u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0001000000400020ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000000080000ull, 0x0000000000000010ull}},
|
||||
{TextureTileMode::kR64KBX, 1u, 4u, {0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000000010000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000400000ull, 0x0000000000000040ull, 0x0001000000800000ull, 0x0002000001000000ull}},
|
||||
{TextureTileMode::kR64KBX, 2u, 4u, {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000100000ull, 0x0000000000000040ull, 0x0001000000400000ull, 0x0002000000800000ull}},
|
||||
{TextureTileMode::kR64KBX, 4u, 4u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000800080008ull, 0x0000000400100010ull, 0x0000000200200040ull, 0x0000000100400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0001000000400000ull, 0x0002000000800000ull}},
|
||||
{TextureTileMode::kR64KBX, 8u, 4u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0002000000200040ull, 0x0000000100400020ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0001000000400000ull}},
|
||||
{TextureTileMode::kR64KBX, 16u, 4u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000200080008ull, 0x0000000100100010ull, 0x0002000000200040ull, 0x0001000000400020ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000000080000ull, 0x0000000000000010ull}},
|
||||
{TextureTileMode::kR64KBX, 1u, 8u, {0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000000010000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000800400020ull, 0x0000000000400000ull, 0x0001000000800000ull, 0x0002000001000000ull, 0x0004000002000000ull}},
|
||||
{TextureTileMode::kR64KBX, 2u, 8u, {0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0000000800400020ull, 0x0000000000100000ull, 0x0001000000400000ull, 0x0002000000800000ull, 0x0004000001000000ull}},
|
||||
{TextureTileMode::kR64KBX, 4u, 8u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000000002ull, 0x0000000000010000ull, 0x0000000000020000ull, 0x0000000000040000ull, 0x0000000000000004ull, 0x0000000400080008ull, 0x0000000200100010ull, 0x0000000100200040ull, 0x0004000000400020ull, 0x0000000000080000ull, 0x0000000000000010ull, 0x0001000000400000ull, 0x0002000000800000ull}},
|
||||
{TextureTileMode::kR64KBX, 8u, 8u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000000004ull, 0x0000000000020000ull, 0x0000000200080008ull, 0x0000000100100010ull, 0x0004000000200040ull, 0x0002000000400020ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0000000000100000ull, 0x0001000000400000ull}},
|
||||
{TextureTileMode::kR64KBX, 16u, 8u, {0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0x0000000000000001ull, 0x0000000000010000ull, 0x0000000000000002ull, 0x0000000000020000ull, 0x0000000200080008ull, 0x0000000100100010ull, 0x0004000000200040ull, 0x0002000000400020ull, 0x0000000000000004ull, 0x0000000000040000ull, 0x0000000000000008ull, 0x0001000000400000ull}},
|
||||
};
|
||||
|
||||
constexpr const MultisampleSwizzleEquation* FindMultisampleSwizzleEquation(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t fragments) {
|
||||
for (const auto& equation : kMultisampleSwizzleEquations) {
|
||||
if (equation.tileMode == tileMode && equation.elementBytes == elementBytes && equation.fragments == fragments) return &equation;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MULTISAMPLETEXTURELAYOUT_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MULTISAMPLETEXTURELAYOUT_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
class MultisampleTextureLayout {
|
||||
public:
|
||||
static constexpr std::uint32_t Alignment = 65536u;
|
||||
|
||||
MultisampleTextureLayout(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t fragments, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t pipeBankXor = 0);
|
||||
std::uint64_t Bytes() const { return sliceBytes * layers; }
|
||||
std::uint64_t SliceBytes() const { return sliceBytes; }
|
||||
std::uint64_t LinearBytes() const { return static_cast<std::uint64_t>(width) * height * layers * fragments * elementBytes; }
|
||||
std::uint32_t Pitch() const { return pitch; }
|
||||
std::uint32_t PaddedHeight() const { return paddedHeight; }
|
||||
std::uint32_t BlockWidth() const { return blockWidth; }
|
||||
std::uint32_t BlockHeight() const { return blockHeight; }
|
||||
const std::array<std::uint32_t, 16>& InverseBlockEquation() const { return inverseBlockEquation; }
|
||||
std::uint64_t Offset(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t fragment) const;
|
||||
void Detile(std::span<const std::byte> source, std::span<std::byte> destination) const;
|
||||
void Tile(std::span<const std::byte> source, std::span<std::byte> destination) const;
|
||||
|
||||
private:
|
||||
std::uint64_t offset(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t fragment) const;
|
||||
void transfer(std::span<const std::byte> source, std::span<std::byte> destination, bool retile) const;
|
||||
|
||||
std::uint32_t elementBytes;
|
||||
std::uint32_t fragments;
|
||||
std::uint32_t width;
|
||||
std::uint32_t height;
|
||||
std::uint32_t layers;
|
||||
std::uint32_t pipeBankXor;
|
||||
std::uint32_t blockWidth;
|
||||
std::uint32_t blockHeight;
|
||||
std::uint32_t pitch;
|
||||
std::uint32_t paddedHeight;
|
||||
std::uint64_t sliceBytes;
|
||||
std::array<std::uint32_t, 16> inverseBlockEquation;
|
||||
std::vector<std::uint32_t> xOffsets;
|
||||
std::vector<std::uint32_t> yOffsets;
|
||||
std::vector<std::uint32_t> layerOffsets;
|
||||
std::vector<std::uint32_t> fragmentOffsets;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -52,6 +52,7 @@ bool SpirvStoresThroughAddresses(std::span<const std::uint32_t> words);
|
||||
// The cached storage image of a surface (render targets use it as their resident image); brought up
|
||||
// to date with guest memory before it is returned.
|
||||
std::shared_ptr<StorageTexture> CachedStorageSurface(const Context& context, const GuestTextureResource& resource);
|
||||
std::shared_ptr<StorageTexture> CachedStorageLayers(const Context& context, const GuestTextureResource& resource, std::uint32_t firstLayer, std::uint32_t layerCount);
|
||||
// Whether `image` is still the storage cache's image of its surface (what CachedStorageSurface
|
||||
// would return); a true answer counts as a use for the cache's eviction order, as a lookup would.
|
||||
bool StorageImageCached(const Context& context, const StorageTexture* image);
|
||||
|
||||
@@ -201,6 +201,14 @@ struct ColorMetadataPass {
|
||||
std::optional<ColorTarget> source;
|
||||
};
|
||||
std::optional<ColorMetadataPass> DecodeColorMetadataPass(const QueueState& queue);
|
||||
struct DepthCopyPass {
|
||||
std::uint64_t depthAddress;
|
||||
VkExtent2D extent;
|
||||
std::uint32_t samples;
|
||||
std::uint32_t sample;
|
||||
ColorTarget color;
|
||||
};
|
||||
std::optional<DepthCopyPass> DecodeDepthCopyPass(const QueueState& queue);
|
||||
std::string DepthMaintenanceRejection(const QueueState& queue);
|
||||
bool DepthResummarizeDrawsNothing(const QueueState& queue);
|
||||
// The message DecodeState (or the pixel stage decode after it) would throw for the register rules
|
||||
|
||||
@@ -198,6 +198,10 @@ public:
|
||||
// Deferred write-back (APS5_EAGER_WRITEBACK=1 stores at once instead).
|
||||
void MarkDirty();
|
||||
void MarkWholeWritten();
|
||||
void MarkUnitsWritten(const std::vector<bool>& units);
|
||||
std::vector<VkRect2D> UnitRects(const std::vector<bool>& units) const;
|
||||
bool BlockUnits() const { return blockUnits; }
|
||||
std::uint32_t TrackedUnits() const { return trackedLayers; }
|
||||
void MarkSliceDirty(std::uint32_t slice, std::uint32_t count = 1);
|
||||
void Flush();
|
||||
bool Dirty() const;
|
||||
@@ -350,6 +354,7 @@ public:
|
||||
// validates textures copied from this image.
|
||||
std::uint64_t Version() const { return version; }
|
||||
std::uint64_t Generation() const { return generation; }
|
||||
std::uint64_t GenerationFor(std::uint64_t address, std::uint64_t bytes) const;
|
||||
// The DCC keys the content was uploaded under and the last scan of the surface's keys
|
||||
// (ProvedClearKeys): Refresh's own key rule for the fast revalidation. Under
|
||||
// GuestMemory::GpuMutex only, as Refresh is; never from a build's stage A.
|
||||
@@ -364,6 +369,7 @@ public:
|
||||
// was still current (nothing uploaded).
|
||||
// Keeps the image current with guest memory (see GuestMemory::CollectWrites).
|
||||
bool Refresh();
|
||||
bool RefreshLayers(std::uint32_t first, std::uint32_t count);
|
||||
static std::uint64_t SinglePassMoves();
|
||||
static std::uint64_t SlabRetiles();
|
||||
static std::uint64_t RefreshesProved();
|
||||
@@ -380,9 +386,8 @@ private:
|
||||
StorageTexture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, std::uint32_t mipLevel);
|
||||
// The regions of every array layer, or of the tracked layers `layers` selects.
|
||||
std::vector<VkBufferImageCopy> CopyRegions(const std::vector<bool>* layers = nullptr) const;
|
||||
// Uploads the surface, or only the tracked layers `layers` selects (the direct path; the others
|
||||
// upload everything).
|
||||
void upload(const std::vector<bool>* layers = nullptr);
|
||||
std::vector<bool> cpuUploadLayers(const std::vector<bool>& units) const;
|
||||
void captureGuestBytes(const std::vector<bool>* layers);
|
||||
bool originalHolds(std::uint64_t address, std::size_t bytes) const;
|
||||
bool compareUntracked(std::uint64_t address, std::size_t bytes, std::span<std::uint8_t> changed, bool memoize = false) const;
|
||||
@@ -477,7 +482,10 @@ private:
|
||||
// layer generation (the whole-surface fast check), `dirty` whether any layer is pending.
|
||||
std::uint64_t layerBegin(std::uint32_t layer) const { return descriptor.baseAddress + static_cast<std::uint64_t>(layer) * trackedLayerBytes; }
|
||||
bool anyLayerPending() const;
|
||||
bool refresh(std::uint32_t firstLayer, std::uint32_t lastLayer);
|
||||
void noteLayersBehind(std::uint32_t firstLayer, std::uint32_t lastLayer, const std::vector<bool>& outside);
|
||||
void refreshGeneration();
|
||||
std::uint64_t collectedLayerGeneration(std::uint32_t layer, std::uint64_t whole, bool uncached = false) const;
|
||||
bool refreshProved();
|
||||
void takeRefreshProof(bool aliased);
|
||||
bool otherPendingOverlaps() const;
|
||||
@@ -555,7 +563,13 @@ private:
|
||||
std::uint64_t sliceLinearBytes = 0;
|
||||
SurfaceGeometry geometry;
|
||||
std::vector<std::byte> original;
|
||||
mutable std::array<std::uint64_t, 5> comparedGuestBytes{};
|
||||
struct ComparedRange {
|
||||
std::uint64_t address = 0;
|
||||
std::size_t bytes = 0;
|
||||
std::array<std::uint64_t, 5> stamp{};
|
||||
};
|
||||
mutable std::array<ComparedRange, 4> comparedRanges{};
|
||||
mutable std::uint32_t nextComparedRange = 0;
|
||||
std::vector<std::byte> generationBaseline;
|
||||
// DCC keys the image content was uploaded under: a fast-cleared surface starts as its clear value.
|
||||
DccKeys uploadedKeys = DccKeys::Uncompressed;
|
||||
@@ -581,6 +595,7 @@ private:
|
||||
std::uint64_t trackedLayerBytes = 0;
|
||||
std::vector<std::uint64_t> layerGeneration;
|
||||
std::vector<bool> layerPending;
|
||||
std::vector<bool> layerBehind;
|
||||
bool blockUnits = false;
|
||||
// Write-back hysteresis (block units, see writeBack): partial stores in the current window of
|
||||
// presents, its first present, and the present until which every pending unit is stored.
|
||||
|
||||
@@ -56,9 +56,12 @@ namespace AgcDriver::Graphics {
|
||||
void DispatchCmaskClear(VkCommandBuffer commands, VkBuffer cmask, std::uint64_t cmaskOffset, std::size_t cmaskBytes, VkImageView view, std::uint32_t width, std::uint32_t height, std::uint32_t elementBytes, const std::array<std::uint32_t, 2>& clearWords, bool write);
|
||||
std::uint32_t CmaskErrors();
|
||||
void DispatchMultisampleReadback(VkCommandBuffer commands, VkFormat format, VkBuffer destination, std::uint64_t offset, std::uint64_t bytes, VkImageView arrayView, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t fragments, std::uint32_t firstElement, std::uint32_t elements);
|
||||
void DispatchDepthSample(VkCommandBuffer commands, VkImageView depth, bool multisampled, VkImageView target, std::uint32_t width, std::uint32_t height, std::uint32_t sample);
|
||||
void DispatchMultisampleImage(VkCommandBuffer commands, std::uint32_t elementBytes, VkBuffer source, std::uint64_t offset, std::uint64_t bytes, VkImageView arrayView, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t fragments, std::uint32_t firstElement, std::uint32_t elements);
|
||||
|
||||
private:
|
||||
VkPipeline multisampleReadbackPipeline(std::uint32_t elementBytes, bool depth);
|
||||
VkPipeline multisampleUploadPipeline(std::uint32_t elementBytes);
|
||||
VkPipeline pipeline(TextureTileMode tileMode, std::uint32_t elementBytes, bool retile, bool thick, bool image = false);
|
||||
void release() noexcept;
|
||||
struct DescriptorRing {
|
||||
@@ -82,6 +85,11 @@ namespace AgcDriver::Graphics {
|
||||
VkPipelineLayout cmaskPipelineLayout = VK_NULL_HANDLE;
|
||||
VkPipeline cmaskPipeline = VK_NULL_HANDLE;
|
||||
DescriptorRing cmaskDescriptorSets;
|
||||
VkDescriptorSetLayout depthSampleLayout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout depthSamplePipelineLayout = VK_NULL_HANDLE;
|
||||
std::array<VkPipeline, 2> depthSamplePipelines{};
|
||||
VkSampler depthSampler = VK_NULL_HANDLE;
|
||||
DescriptorRing depthSampleSets;
|
||||
std::unique_ptr<Buffer> cmaskErrors;
|
||||
VkDescriptorSetLayout imageDescriptorLayout = VK_NULL_HANDLE;
|
||||
VkPipelineLayout imagePipelineLayout = VK_NULL_HANDLE;
|
||||
@@ -94,6 +102,7 @@ namespace AgcDriver::Graphics {
|
||||
std::array<VkShaderModule, 2> readbackModules{};
|
||||
std::vector<std::pair<std::uint32_t, VkPipeline>> readbackPipelines;
|
||||
DescriptorRing readbackDescriptorSets;
|
||||
VkShaderModule multisampleUploadModule = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -230,14 +230,16 @@ inline std::uint32_t VertexBufferOutOfBoundsSelect(const ShaderRecompiler::Verte
|
||||
|
||||
inline bool VertexFetchOutOfRange(const ShaderRecompiler::VertexAttribute& attribute) {
|
||||
const auto stride = (attribute.resource.fields[1] >> 16u) & 0x3fffu;
|
||||
if (stride != 0 || NullVertexDescriptor(attribute)) return false;
|
||||
const auto select = VertexBufferOutOfBoundsSelect(attribute);
|
||||
return stride == 0 && (select == 2u || select == 3u) && attribute.resource.fields[2] == 0;
|
||||
const auto records = attribute.resource.fields[2];
|
||||
if (select == 0u) return true;
|
||||
if (select == 3u) return records < DecodeVertexFormat(attribute).bytes;
|
||||
return records == 0;
|
||||
}
|
||||
|
||||
inline std::uint64_t ZeroStrideAvailableBytes(const ShaderRecompiler::VertexAttribute& attribute) {
|
||||
const auto records = attribute.resource.fields[2];
|
||||
if (VertexBufferOutOfBoundsSelect(attribute) == 2u && records != 0) return std::max<std::uint64_t>(records, DecodeVertexFormat(attribute).bytes);
|
||||
return records;
|
||||
return VertexFetchOutOfRange(attribute) ? 0u : DecodeVertexFormat(attribute).bytes;
|
||||
}
|
||||
|
||||
inline std::size_t VertexBufferExtent(const ShaderRecompiler::VertexAttribute& attribute) {
|
||||
@@ -261,7 +263,9 @@ inline std::size_t VertexBufferReadSize(const ShaderRecompiler::VertexAttribute&
|
||||
Require(attribute.fetchIndex <= 1, "unsupported vertex fetch index");
|
||||
const auto index = attribute.fetchIndex == 0 ? maxIndex : firstInstance + instances - 1u;
|
||||
const auto bytes = DecodeVertexFormat(attribute).bytes;
|
||||
Require(stride == 0 || VertexBufferOutOfBoundsSelect(attribute) <= 1u || index < records, "vertex fetch exceeds descriptor record count");
|
||||
const auto select = VertexBufferOutOfBoundsSelect(attribute);
|
||||
const auto recordsUnchecked = stride == 0 ? select != 1u : select <= 1u;
|
||||
Require(recordsUnchecked || index < records, "vertex fetch exceeds descriptor record count");
|
||||
const auto required = static_cast<std::uint64_t>(stride) * index + bytes;
|
||||
Require(stride != 0 || required <= ZeroStrideAvailableBytes(attribute), "vertex fetch exceeds descriptor byte range");
|
||||
Require(required <= std::numeric_limits<std::size_t>::max(), "vertex fetch exceeds addressable byte range");
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#version 450
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
#if MULTISAMPLED
|
||||
layout(set = 0, binding = 0) uniform sampler2DMS depth;
|
||||
#else
|
||||
layout(set = 0, binding = 0) uniform sampler2D depth;
|
||||
#endif
|
||||
layout(set = 0, binding = 1, r32f) writeonly uniform image2D target;
|
||||
|
||||
layout(push_constant) uniform Push {
|
||||
uint width;
|
||||
uint height;
|
||||
int sampleIndex;
|
||||
} push;
|
||||
|
||||
void main() {
|
||||
uvec2 pixel = gl_GlobalInvocationID.xy;
|
||||
if (pixel.x >= push.width || pixel.y >= push.height) return;
|
||||
#if MULTISAMPLED
|
||||
float value = texelFetch(depth, ivec2(pixel), push.sampleIndex).r;
|
||||
#else
|
||||
float value = texelFetch(depth, ivec2(pixel), 0).r;
|
||||
#endif
|
||||
imageStore(target, ivec2(pixel), vec4(value, 0.0, 0.0, 0.0));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#version 450
|
||||
#extension GL_EXT_shader_image_load_formatted : require
|
||||
|
||||
layout(constant_id = 0) const uint ELEMENT_BYTES = 4u;
|
||||
layout(local_size_x = 64) in;
|
||||
layout(set = 0, binding = 0, std430) readonly buffer Input { uint data[]; } sourceBuffer;
|
||||
layout(set = 0, binding = 1) uniform uimage2DMSArray destinationImage;
|
||||
layout(push_constant) uniform Push {
|
||||
uint width;
|
||||
uint height;
|
||||
uint layers;
|
||||
uint fragments;
|
||||
uint firstElement;
|
||||
uint elements;
|
||||
uint rowThreads;
|
||||
} params;
|
||||
|
||||
void main() {
|
||||
uint index = gl_GlobalInvocationID.y * params.rowThreads + gl_GlobalInvocationID.x;
|
||||
if (index >= params.elements) return;
|
||||
uint element = params.firstElement + index;
|
||||
uint pixel = element / params.fragments;
|
||||
ivec3 coordinate = ivec3(pixel % params.width, (pixel / params.width) % params.height, pixel / (params.width * params.height));
|
||||
uint address = index * ELEMENT_BYTES;
|
||||
uvec4 value = uvec4(0u);
|
||||
if (ELEMENT_BYTES < 4u) {
|
||||
value.x = (sourceBuffer.data[address >> 2u] >> ((address & 3u) * 8u)) & (ELEMENT_BYTES == 1u ? 255u : 65535u);
|
||||
} else {
|
||||
for (uint component = 0u; component < ELEMENT_BYTES / 4u; ++component) value[component] = sourceBuffer.data[(address >> 2u) + component];
|
||||
}
|
||||
imageStore(destinationImage, coordinate, int(element % params.fragments), value);
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <limits>
|
||||
#include <shared_mutex>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -150,6 +151,21 @@ DepthClearCounters& depthClearCounters() {
|
||||
return counters;
|
||||
}
|
||||
|
||||
struct DepthPlaneStoreCounters {
|
||||
std::atomic<std::uint64_t> stores{0};
|
||||
std::atomic<std::uint64_t> units{0};
|
||||
std::atomic<std::uint64_t> bytes{0};
|
||||
};
|
||||
|
||||
DepthPlaneStoreCounters& depthPlaneStoreCounters() {
|
||||
static DepthPlaneStoreCounters counters;
|
||||
return counters;
|
||||
}
|
||||
|
||||
bool anyUnit(const std::vector<bool>& units) {
|
||||
return std::find(units.begin(), units.end(), true) != units.end();
|
||||
}
|
||||
|
||||
thread_local DepthPlaneOverwrite* currentOverwrite = nullptr;
|
||||
|
||||
std::optional<DepthPlaneOverwrite> decodeOverwrite(const StorageImageClear& clear) {
|
||||
@@ -491,6 +507,30 @@ public:
|
||||
else Recorder::CountBarriers(Recorder::CommandClass::Draw, 2);
|
||||
}
|
||||
|
||||
void CopySample(std::uint32_t sample, StorageTexture& destination, VkImageView destinationView) {
|
||||
auto* recorder = Recorder::Active();
|
||||
Require(recorder != nullptr, "depth sample copies need an active recorder");
|
||||
Require(sampleable, "DB depth copy from a depth surface the device cannot sample at its sample count");
|
||||
ApplyFastClear();
|
||||
if (useSampleShadow) {
|
||||
if (sampleShadow == nullptr) createSampleShadow();
|
||||
refreshSampleShadow(false);
|
||||
recorder->Keep(sampleShadow);
|
||||
}
|
||||
if (depthView == VK_NULL_HANDLE) {
|
||||
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
viewInfo.image = useSampleShadow ? sampleShadow->image : image;
|
||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
viewInfo.format = hostFormat;
|
||||
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
||||
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &depthView), "vkCreateImageView depth sample copy");
|
||||
}
|
||||
Require(context.detiler != nullptr, "depth sample copies need the texture detiler");
|
||||
context.detiler->DispatchDepthSample(recorder->Commands(), depthView, target.samples > 1, destinationView, target.extent.width, target.extent.height, sample);
|
||||
Recorder::CountBarriers(Recorder::CommandClass::Copy, 2);
|
||||
destination.MarkDirty();
|
||||
}
|
||||
|
||||
bool Holds(const Texture* texture) const {
|
||||
const auto held = [&](const auto& entry) { return entry.second.get() == texture; };
|
||||
return std::any_of(textures.begin(), textures.end(), held) || std::any_of(stencilCopy.textures.begin(), stencilCopy.textures.end(), held);
|
||||
@@ -698,48 +738,173 @@ public:
|
||||
else Recorder::CountBarriers(Recorder::CommandClass::Copy, 3);
|
||||
}
|
||||
|
||||
std::shared_ptr<StorageTexture> StoreDepthPlane(const GuestTextureResource& resource) {
|
||||
ApplyFastClear();
|
||||
std::shared_ptr<StorageTexture> Mirror(const GuestTextureResource& resource) {
|
||||
if (mirrorPlane.storage == nullptr) {
|
||||
mirrorPlane.storage = StorageTexture::Create(context, *context.detiler, resource, 0);
|
||||
++mirrorEpoch;
|
||||
}
|
||||
copyPlane(mirrorPlane, mirrorPlane.storage, false, true, true);
|
||||
return mirrorPlane.storage;
|
||||
}
|
||||
|
||||
bool SyncFromMemory(const GuestTextureResource& resource) {
|
||||
bool StoresUnits(const StorageTexture& mirror) const {
|
||||
return mirror.BlockUnits() && mirror.TrackedUnits() == staleUnits.size();
|
||||
}
|
||||
|
||||
std::shared_ptr<StorageTexture> StoreDepthPlane(const GuestTextureResource& resource, std::vector<bool>& units) {
|
||||
ApplyFastClear();
|
||||
auto mirror = Mirror(resource);
|
||||
auto& counters = depthPlaneStoreCounters();
|
||||
++counters.stores;
|
||||
const auto planeBytes = mirror->GuestBytes();
|
||||
if (!StoresUnits(*mirror)) {
|
||||
units.assign(units.size(), true);
|
||||
copyPlane(mirrorPlane, mirror, false, true, true);
|
||||
mirror->MarkWholeWritten();
|
||||
counters.units += units.size();
|
||||
counters.bytes += planeBytes;
|
||||
return mirror;
|
||||
}
|
||||
const auto rects = mirror->UnitRects(units);
|
||||
copyPlane(mirrorPlane, mirror, false, true, true, &rects);
|
||||
mirror->MarkUnitsWritten(units);
|
||||
for (std::size_t unit = 0; unit < units.size(); ++unit) {
|
||||
if (!units[unit]) continue;
|
||||
++counters.units;
|
||||
counters.bytes += std::min<std::uint64_t>(unitBytes, planeBytes - unit * unitBytes);
|
||||
}
|
||||
return mirror;
|
||||
}
|
||||
|
||||
void SizeUnits(std::uint64_t planeBytes) {
|
||||
static const bool whole = std::getenv("APS5_DEPTH_PLANE_WHOLE_STORE") != nullptr;
|
||||
const std::uint64_t bytes = !whole && target.address % 65536u == 0 ? std::uint64_t{65536} : std::max<std::uint64_t>(planeBytes, 1u);
|
||||
const auto count = static_cast<std::size_t>(std::max<std::uint64_t>((planeBytes + bytes - 1u) / bytes, 1u));
|
||||
if (unitBytes == bytes && staleUnits.size() == count) return;
|
||||
unitBytes = bytes;
|
||||
staleUnits.assign(count, false);
|
||||
staleCount = 0;
|
||||
unitGeneration.assign(count, 0);
|
||||
}
|
||||
|
||||
bool AnyStale() const {
|
||||
return staleCount != 0;
|
||||
}
|
||||
|
||||
void MarkAllStale(std::uint64_t planeBytes) {
|
||||
SizeUnits(planeBytes);
|
||||
staleUnits.assign(staleUnits.size(), true);
|
||||
staleCount = staleUnits.size();
|
||||
}
|
||||
|
||||
void ClearStale() {
|
||||
staleUnits.assign(staleUnits.size(), false);
|
||||
staleCount = 0;
|
||||
}
|
||||
|
||||
void ClearStale(const std::vector<bool>& units) {
|
||||
for (std::size_t unit = 0; unit < units.size() && unit < staleUnits.size(); ++unit) {
|
||||
if (!units[unit] || !staleUnits[unit]) continue;
|
||||
staleUnits[unit] = false;
|
||||
--staleCount;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<bool> StaleUnitsIn(std::uint64_t planeBytes, std::uint64_t begin, std::uint64_t end) const {
|
||||
std::vector<bool> units(staleUnits.size(), false);
|
||||
const auto plane = target.address;
|
||||
if (units.empty() || unitBytes == 0 || begin >= end || begin >= plane + planeBytes || plane >= end) return units;
|
||||
const auto from = std::max(begin, plane) - plane;
|
||||
const auto to = std::min(end, plane + planeBytes) - plane;
|
||||
const auto last = std::min<std::uint64_t>((to - 1u) / unitBytes, units.size() - 1u);
|
||||
for (auto unit = from / unitBytes; unit <= last; ++unit) units[static_cast<std::size_t>(unit)] = staleUnits[static_cast<std::size_t>(unit)];
|
||||
return units;
|
||||
}
|
||||
|
||||
std::pair<std::uint64_t, std::uint64_t> UnitHull(const std::vector<bool>& units, std::uint64_t planeBytes) const {
|
||||
std::uint64_t first = planeBytes;
|
||||
std::uint64_t last = 0;
|
||||
for (std::size_t unit = 0; unit < units.size(); ++unit) {
|
||||
if (!units[unit]) continue;
|
||||
first = std::min<std::uint64_t>(first, unit * unitBytes);
|
||||
last = std::max<std::uint64_t>(last, std::min<std::uint64_t>(planeBytes, (unit + 1u) * unitBytes));
|
||||
}
|
||||
return {target.address + first, target.address + std::max(first, last)};
|
||||
}
|
||||
|
||||
void NoteStored(const std::vector<bool>& units, std::uint64_t generation) {
|
||||
for (std::size_t unit = 0; unit < units.size() && unit < unitGeneration.size(); ++unit) {
|
||||
if (units[unit]) unitGeneration[unit] = generation;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<bool> WrittenUnits(const GuestTextureResource& resource, std::uint64_t& generation) {
|
||||
const auto bytes = static_cast<std::size_t>(DescribeSurface(resource).guestBytes);
|
||||
SizeUnits(bytes);
|
||||
GuestMemory::CollectWrites(resource.baseAddress, bytes);
|
||||
if (syncedGeneration == 0 || (pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 || !GuestMemory::WrittenSince(resource.baseAddress, bytes, syncedGeneration)) {
|
||||
syncedGeneration = GuestMemory::TrackerGeneration();
|
||||
return false;
|
||||
generation = GuestMemory::TrackerGeneration();
|
||||
std::vector<bool> changed(unitGeneration.size(), false);
|
||||
if (unitGeneration.size() == 1u) {
|
||||
changed[0] = GuestMemory::WrittenSince(resource.baseAddress, bytes, unitGeneration[0]);
|
||||
return changed;
|
||||
}
|
||||
memoryStale = false;
|
||||
if (mirrorPlane.storage == nullptr) {
|
||||
mirrorPlane.storage = StorageTexture::Create(context, *context.detiler, resource, 0);
|
||||
++mirrorEpoch;
|
||||
const auto oldest = *std::min_element(unitGeneration.begin(), unitGeneration.end());
|
||||
if (oldest != 0u && !GuestMemory::WrittenSince(resource.baseAddress, bytes, oldest)) return changed;
|
||||
std::vector<std::uint8_t> stamped(unitGeneration.size(), GuestMemory::BlockUnchanged);
|
||||
if (!GuestMemory::ChangedBlocks(resource.baseAddress, bytes, unitGeneration, stamped)) return changed;
|
||||
for (std::size_t unit = 0; unit < changed.size(); ++unit) changed[unit] = unitGeneration[unit] != 0u && stamped[unit] == GuestMemory::BlockWritten;
|
||||
return changed;
|
||||
}
|
||||
|
||||
void SyncFromMemory(const GuestTextureResource& resource) {
|
||||
std::uint64_t generation = 0;
|
||||
const auto changed = WrittenUnits(resource, generation);
|
||||
if ((pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 || !anyUnit(changed)) {
|
||||
unitGeneration.assign(unitGeneration.size(), generation);
|
||||
return;
|
||||
}
|
||||
ClearStale(changed);
|
||||
{
|
||||
struct Syncing {
|
||||
bool& flag;
|
||||
~Syncing() { flag = false; }
|
||||
} guard{syncing};
|
||||
syncing = true;
|
||||
if (mirrorPlane.storage == nullptr) {
|
||||
mirrorPlane.storage = StorageTexture::Create(context, *context.detiler, resource, 0);
|
||||
++mirrorEpoch;
|
||||
} else {
|
||||
static_cast<void>(mirrorPlane.storage->Refresh());
|
||||
}
|
||||
}
|
||||
unitGeneration.assign(unitGeneration.size(), generation);
|
||||
const auto mirror = mirrorPlane.storage;
|
||||
if (StoresUnits(*mirror)) {
|
||||
const auto rects = mirror->UnitRects(changed);
|
||||
copyPlane(mirrorPlane, mirror, false, false, true, &rects);
|
||||
} else {
|
||||
ClearStale();
|
||||
copyPlane(mirrorPlane, mirror, false, false, true);
|
||||
}
|
||||
else static_cast<void>(mirrorPlane.storage->Refresh());
|
||||
syncedGeneration = GuestMemory::TrackerGeneration();
|
||||
copyPlane(mirrorPlane, mirrorPlane.storage, false, false, true);
|
||||
written = ++depthWrites;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DropMirror() {
|
||||
mirrorPlane = {};
|
||||
}
|
||||
|
||||
bool memoryStale = false;
|
||||
std::vector<bool> staleUnits;
|
||||
std::size_t staleCount = 0;
|
||||
std::vector<std::uint64_t> unitGeneration;
|
||||
std::uint64_t unitBytes = 0;
|
||||
bool syncing = false;
|
||||
std::uint64_t scannedEpoch = 0;
|
||||
std::uint64_t syncedGeneration = 0;
|
||||
|
||||
const Context context;
|
||||
const DepthTarget target;
|
||||
const VkFormat hostFormat;
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkImageView view = VK_NULL_HANDLE;
|
||||
VkImageView depthView = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
std::shared_ptr<std::atomic<bool>> active = std::make_shared<std::atomic<bool>>(true);
|
||||
bool sampleable = true;
|
||||
@@ -943,33 +1108,52 @@ private:
|
||||
return ResolveTextureFormat(resource.format) == (stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT);
|
||||
}
|
||||
|
||||
void copyPlane(PlaneStorage& plane, const std::shared_ptr<StorageTexture>& storage, bool stencil, bool toStorage, bool ownBatch = false) {
|
||||
void copyPlane(PlaneStorage& plane, const std::shared_ptr<StorageTexture>& storage, bool stencil, bool toStorage, bool ownBatch = false, const std::vector<VkRect2D>* rects = nullptr) {
|
||||
if (rects != nullptr && rects->empty()) return;
|
||||
auto* recorder = Recorder::Active();
|
||||
Require(recorder != nullptr || ownBatch, "depth surface storage copies need an active recorder");
|
||||
std::unique_ptr<CommandBatch> batch;
|
||||
if (recorder == nullptr) batch = std::make_unique<CommandBatch>(context);
|
||||
const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT;
|
||||
const auto bytes = static_cast<std::size_t>(target.extent.width) * target.extent.height * (stencil ? 1u : d16 ? 2u : 4u);
|
||||
const std::uint32_t texelBytes = stencil ? 1u : d16 ? 2u : 4u;
|
||||
const auto bytes = static_cast<std::size_t>(target.extent.width) * target.extent.height * texelBytes;
|
||||
if (plane.staging == nullptr) plane.staging = std::make_unique<DeviceBuffer>(context, bytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
const auto commands = recorder != nullptr ? recorder->Commands() : batch->Handle();
|
||||
if (recorder != nullptr) recorder->Keep(storage);
|
||||
const std::uint32_t timing = recorder != nullptr ? recorder->BeginGpuTiming(Recorder::CommandClass::Copy) : 0u;
|
||||
VkBufferImageCopy planeRegion{0, 0, 0, {stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}, {0, 0, 0}, {target.extent.width, target.extent.height, 1}};
|
||||
VkBufferImageCopy colorRegion = planeRegion;
|
||||
colorRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const VkBufferImageCopy planeRegion{0, 0, 0, {stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}, {0, 0, 0}, {target.extent.width, target.extent.height, 1}};
|
||||
std::vector<VkBufferImageCopy> planeRegions;
|
||||
std::size_t moved = bytes;
|
||||
if (rects == nullptr) {
|
||||
planeRegions.push_back(planeRegion);
|
||||
} else {
|
||||
moved = 0;
|
||||
for (const auto& rect : *rects) {
|
||||
auto region = planeRegion;
|
||||
region.bufferOffset = (static_cast<VkDeviceSize>(rect.offset.y) * target.extent.width + static_cast<VkDeviceSize>(rect.offset.x)) * texelBytes;
|
||||
region.bufferRowLength = target.extent.width;
|
||||
region.imageOffset = {rect.offset.x, rect.offset.y, 0};
|
||||
region.imageExtent = {rect.extent.width, rect.extent.height, 1};
|
||||
planeRegions.push_back(region);
|
||||
moved += static_cast<std::size_t>(rect.extent.width) * rect.extent.height * texelBytes;
|
||||
}
|
||||
}
|
||||
auto colorRegions = planeRegions;
|
||||
for (auto& region : colorRegions) region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const auto count = static_cast<std::uint32_t>(planeRegions.size());
|
||||
const auto sourceImage = toStorage ? image : storage->Image();
|
||||
const auto destinationImage = toStorage ? storage->Image() : image;
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, sourceImage, VK_IMAGE_LAYOUT_GENERAL, plane.staging->Handle(), 1, toStorage ? &planeRegion : &colorRegion);
|
||||
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, sourceImage, VK_IMAGE_LAYOUT_GENERAL, plane.staging->Handle(), count, toStorage ? planeRegions.data() : colorRegions.data());
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, plane.staging->Handle(), destinationImage, VK_IMAGE_LAYOUT_GENERAL, 1, toStorage ? &colorRegion : &planeRegion);
|
||||
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, plane.staging->Handle(), destinationImage, VK_IMAGE_LAYOUT_GENERAL, count, toStorage ? colorRegions.data() : planeRegions.data());
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT);
|
||||
if (batch) {
|
||||
batch->SubmitAndWait();
|
||||
return;
|
||||
}
|
||||
Recorder::CountBarriers(Recorder::CommandClass::Copy, 3);
|
||||
recorder->EndGpuTiming(timing, bytes);
|
||||
recorder->EndGpuTiming(timing, moved);
|
||||
}
|
||||
|
||||
void release() noexcept {
|
||||
@@ -978,6 +1162,7 @@ private:
|
||||
releaseStencilCopy();
|
||||
mirrorPlane = {};
|
||||
textures.clear();
|
||||
if (depthView) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, depthView, nullptr);
|
||||
if (view) context.Function<PFN_vkDestroyImageView>("vkDestroyImageView")(context.device, view, nullptr);
|
||||
if (owned != nullptr) owned.reset();
|
||||
else {
|
||||
@@ -1578,15 +1763,36 @@ void retire(DepthSurface& surface) {
|
||||
for (auto* owners : {&depthOwners(), &stencilOwners()}) {
|
||||
std::erase_if(*owners, [&](const auto& entry) { return entry.second.surface == &surface; });
|
||||
}
|
||||
if (!surface.memoryStale) return;
|
||||
surface.memoryStale = false;
|
||||
if (!surface.AnyStale()) return;
|
||||
surface.ClearStale();
|
||||
const auto plane = stalePlaneRange(surface);
|
||||
if (std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& other) { return other->memoryStale && stalePlaneRange(*other) == plane; })) return;
|
||||
if (std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& other) { return other->AnyStale() && stalePlaneRange(*other) == plane; })) return;
|
||||
auto& planes = stalePlanes();
|
||||
std::lock_guard lock(planes.mutex);
|
||||
std::erase(planes.ranges, plane);
|
||||
publishStaleHull(planes);
|
||||
}
|
||||
|
||||
void retireStoringStale(DepthSurface& surface) {
|
||||
if (!surface.AnyStale() || !mirrorable(surface.target)) {
|
||||
retire(surface);
|
||||
return;
|
||||
}
|
||||
const auto resource = mirrorResource(surface.target);
|
||||
const auto planeBytes = DescribeSurface(resource).guestBytes;
|
||||
std::uint64_t generation = 0;
|
||||
const auto written = surface.WrittenUnits(resource, generation);
|
||||
auto units = surface.staleUnits;
|
||||
for (std::size_t unit = 0; unit < units.size() && unit < written.size(); ++unit) {
|
||||
if (written[unit]) units[unit] = false;
|
||||
}
|
||||
std::shared_ptr<StorageTexture> mirror;
|
||||
if (anyUnit(units) && (!anyUnit(written) || surface.StoresUnits(*surface.Mirror(resource)))) mirror = surface.StoreDepthPlane(resource, units);
|
||||
retire(surface);
|
||||
if (mirror == nullptr) return;
|
||||
const auto [begin, end] = surface.UnitHull(units, planeBytes);
|
||||
if (StorageTexture::FlushPending(begin, static_cast<std::size_t>(end - begin), nullptr, "depth plane store")) surface.NoteStored(units, GuestMemory::TrackerGeneration());
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t DepthSurfacesSerial() {
|
||||
@@ -1749,7 +1955,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target,
|
||||
if (!created) surface->NoteHtileClear(VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
}
|
||||
if (!created) surface->NoteHtileClear(htileCleared);
|
||||
if (mirrorable(target)) static_cast<void>(surface->SyncFromMemory(mirrorResource(target)));
|
||||
if (mirrorable(target)) surface->SyncFromMemory(mirrorResource(target));
|
||||
surface->ApplyFastClear();
|
||||
if (drawnDepth != nullptr && drawnDepth != surface) drawnDepth->written = ++depthWrites;
|
||||
surface->written = ++depthWrites;
|
||||
@@ -1761,14 +1967,15 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target,
|
||||
if (other.get() == surface || other->context.device != context.device || other->written >= surface->written || !mirrorable(other->target)) continue;
|
||||
const auto otherPlane = stalePlaneRange(*other);
|
||||
if (otherPlane == plane || otherPlane.first >= plane.second || plane.first >= otherPlane.second) continue;
|
||||
other->memoryStale = false;
|
||||
other->ClearStale();
|
||||
other->DropMirror();
|
||||
}
|
||||
}
|
||||
if ((depthWritten || cleared) && mirrorable(target)) {
|
||||
surface->memoryStale = true;
|
||||
const auto plane = target.address;
|
||||
noteStalePlane(plane, plane + DescribeSurface(mirrorResource(target)).guestBytes);
|
||||
const auto planeBytes = DescribeSurface(mirrorResource(target)).guestBytes;
|
||||
surface->MarkAllStale(planeBytes);
|
||||
noteStalePlane(plane, plane + planeBytes);
|
||||
}
|
||||
drawnDepth = surface;
|
||||
return surface->view;
|
||||
@@ -1805,52 +2012,78 @@ bool DepthPlanesStale() {
|
||||
return planes.begin.load(std::memory_order_acquire) < planes.end.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> FlushDepthPlanes(std::uint64_t address, std::size_t bytes) {
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> FlushDepthPlanes(std::uint64_t address, std::size_t bytes, DepthPlaneScope scope) {
|
||||
static thread_local bool flushing = false;
|
||||
const auto end = bytes > std::numeric_limits<std::uint64_t>::max() - address ? std::numeric_limits<std::uint64_t>::max() : address + bytes;
|
||||
if (flushing || !overlapsStalePlane(address, end)) return {};
|
||||
if (scope == DepthPlaneScope::Overlapped) {
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
const auto staleInRange = [&](const auto& surface) { return surface->AnyStale() && !surface->syncing && ((surface->pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0 || anyUnit(surface->StaleUnitsIn(DescribeSurface(mirrorResource(surface->target)).guestBytes, address, end))); };
|
||||
if (std::none_of(surfaces().begin(), surfaces().end(), staleInRange)) return {};
|
||||
}
|
||||
flushing = true;
|
||||
struct Reset {
|
||||
~Reset() { flushing = false; }
|
||||
} reset;
|
||||
std::vector<std::pair<DepthSurface*, std::shared_ptr<StorageTexture>>> stored;
|
||||
struct Stored {
|
||||
DepthSurface* surface;
|
||||
std::shared_ptr<StorageTexture> mirror;
|
||||
std::vector<bool> units;
|
||||
std::uint64_t begin;
|
||||
std::uint64_t end;
|
||||
bool flushed;
|
||||
};
|
||||
std::vector<Stored> stored;
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> ranges;
|
||||
{
|
||||
std::lock_guard gpu(GuestMemory::GpuMutex());
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> remaining;
|
||||
for (const auto& surface : surfaces()) {
|
||||
if (!surface->memoryStale) continue;
|
||||
if (!surface->AnyStale()) continue;
|
||||
const auto resource = mirrorResource(surface->target);
|
||||
const auto plane = surface->target.address;
|
||||
const auto planeBytes = DescribeSurface(resource).guestBytes;
|
||||
if (plane >= end || address >= plane + planeBytes) {
|
||||
remaining.emplace_back(plane, plane + planeBytes);
|
||||
continue;
|
||||
const bool overlapped = !surface->syncing && plane < end && address < plane + planeBytes;
|
||||
if (overlapped && (surface->pendingClear & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) surface->MarkAllStale(planeBytes);
|
||||
if (overlapped && (scope == DepthPlaneScope::Whole || anyUnit(surface->StaleUnitsIn(planeBytes, address, end)))) {
|
||||
surface->SyncFromMemory(resource);
|
||||
auto units = scope == DepthPlaneScope::Whole ? surface->staleUnits : surface->StaleUnitsIn(planeBytes, address, end);
|
||||
if (anyUnit(units)) {
|
||||
auto mirror = surface->StoreDepthPlane(resource, units);
|
||||
surface->ClearStale(units);
|
||||
const auto hull = surface->UnitHull(units, planeBytes);
|
||||
ranges.emplace_back(hull.first, hull.second - hull.first);
|
||||
stored.push_back({surface.get(), std::move(mirror), std::move(units), hull.first, hull.second, false});
|
||||
}
|
||||
}
|
||||
surface->memoryStale = false;
|
||||
if (surface->SyncFromMemory(resource)) continue;
|
||||
stored.emplace_back(surface.get(), surface->StoreDepthPlane(resource));
|
||||
ranges.emplace_back(plane, planeBytes);
|
||||
if (surface->AnyStale()) remaining.emplace_back(plane, plane + planeBytes);
|
||||
}
|
||||
auto& planes = stalePlanes();
|
||||
std::lock_guard staleLock(planes.mutex);
|
||||
planes.ranges = std::move(remaining);
|
||||
publishStaleHull(planes);
|
||||
}
|
||||
for (const auto& entry : stored) entry.second->MarkWholeWritten();
|
||||
for (const auto& [plane, planeBytes] : ranges) StorageTexture::FlushPending(plane, static_cast<std::size_t>(planeBytes), nullptr, "depth plane store");
|
||||
for (auto& entry : stored) entry.flushed = StorageTexture::FlushPending(entry.begin, static_cast<std::size_t>(entry.end - entry.begin), nullptr, "depth plane store");
|
||||
const auto generation = GuestMemory::TrackerGeneration();
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
for (const auto& entry : stored) {
|
||||
if (std::any_of(surfaces().begin(), surfaces().end(), [&](const auto& candidate) { return candidate.get() == entry.first; })) {
|
||||
entry.first->syncedGeneration = generation;
|
||||
entry.first->NoteMemory();
|
||||
}
|
||||
if (!entry.flushed || std::none_of(surfaces().begin(), surfaces().end(), [&](const auto& candidate) { return candidate.get() == entry.surface; })) continue;
|
||||
entry.surface->NoteStored(entry.units, generation);
|
||||
entry.surface->NoteMemory();
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
DepthPlaneStoreStatistics DepthPlaneStoreCounts() {
|
||||
const auto& counters = depthPlaneStoreCounters();
|
||||
DepthPlaneStoreStatistics statistics;
|
||||
statistics.stores = counters.stores.load();
|
||||
statistics.units = counters.units.load();
|
||||
statistics.bytes = counters.bytes.load();
|
||||
return statistics;
|
||||
}
|
||||
|
||||
std::uint64_t HtileDepthClearAddress(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const std::array<std::uint32_t, 3>& numThreads) {
|
||||
static constexpr std::array<std::uint32_t, 17> htileMaskKernel{0xd7460000u, 0x04010c06u, 0x34000084u, 0xdc388000u, 0x04020000u, 0xbf8c3f70u, 0xd7710008u, 0x00120805u, 0xd7710009u, 0x00120a05u, 0xd771000au, 0x00120c05u, 0xd771000bu, 0x00120e05u, 0xdc788000u, 0x00020800u, 0xbf810000u};
|
||||
if (userData.size() < 6 || numThreads[0] != 64 || numThreads[1] != 1 || numThreads[2] != 1) return 0;
|
||||
@@ -2027,7 +2260,7 @@ std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<c
|
||||
const auto copyFormat = integer ? VK_FORMAT_R16_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT;
|
||||
const bool planes = (format == copyFormat || depthBits) && resource.width == base.extent.width && resource.height == base.extent.height && resource.baseLevel == 0 && resource.lastLevel == 0 && !(resource.baseArray != 0 && (cube || !layered || resource.baseArray > resource.depthOrLastArray)) && !(cube && resource.depthOrLastArray != 0 && resource.depthOrLastArray != 5) && (resource.dimension == TextureDimension::k2D || resource.dimension == TextureDimension::k2DArray || cube);
|
||||
if (!(sampled ? found->SampledAccepts(words, resource) : planes) && found->OverwrittenInMemory()) {
|
||||
retire(*found);
|
||||
retireStoringStale(*found);
|
||||
return nullptr;
|
||||
}
|
||||
found->ApplyFastClear();
|
||||
@@ -2204,6 +2437,17 @@ std::shared_ptr<StorageTexture> DepthSurfaceStorage(const Context& context, std:
|
||||
return layeredDepthStorage(context, surface, resource, mip);
|
||||
}
|
||||
|
||||
void CopyDepthSample(const Context& context, std::uint64_t depthAddress, std::uint32_t samples, std::uint32_t sample, StorageTexture& destination, VkImageView destinationView, VkExtent2D extent) {
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
auto* found = planeSurface(context.device, depthAddress);
|
||||
Require(found != nullptr && found->target.address == depthAddress, "DB depth copy from a depth plane no draw rendered");
|
||||
Require(found->target.format == VK_FORMAT_D32_SFLOAT || found->target.format == VK_FORMAT_D32_SFLOAT_S8_UINT, "DB depth copy from a depth surface that is not 32-bit float");
|
||||
Require(found->target.samples == samples, "DB depth copy from a depth surface of another sample count");
|
||||
Require(found->target.extent.width == extent.width && found->target.extent.height == extent.height, "DB depth copy from a depth surface of another size");
|
||||
Require(sample < found->target.samples, "DB depth copy of a sample the depth surface does not have");
|
||||
found->CopySample(sample, destination, destinationView);
|
||||
}
|
||||
|
||||
bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage) {
|
||||
std::lock_guard lock(surfacesMutex());
|
||||
for (auto& [key, entry] : layeredStorages()) {
|
||||
|
||||
@@ -2240,7 +2240,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
||||
}
|
||||
APS5_LOG_OUT_DEBUG("Creating color target %zu address=0x%llx bytes=%llu extent=%ux%u", index, static_cast<unsigned long long>(color.address), static_cast<unsigned long long>(color.bytes), color.extent.width, color.extent.height);
|
||||
const ColorTargetLayout colorLayout(color.extent.width, color.extent.height, color.tileMode, color.elementBytes);
|
||||
static_cast<void>(FlushDepthPlanes(color.address, colorLayout.Bytes()));
|
||||
static_cast<void>(FlushDepthPlanes(color.address, colorLayout.Bytes(), DepthPlaneScope::Whole));
|
||||
RetireDepthSurfaces(context.device, color.address, colorLayout.Bytes());
|
||||
constexpr VkBufferUsageFlags copies = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
timer.phase(PhaseSetup);
|
||||
@@ -2264,7 +2264,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
|
||||
const auto geometry = DescribeSurface(array);
|
||||
for (std::uint32_t slice = 0; slice <= color.arrayLastSlice; ++slice) Require(geometry.GuestLayerOffset(slice) == slice * sliceBytes, "the array image's layer pitch differs from the color view's slice offset");
|
||||
Require(color.surfaceAddress == color.arrayAddress + color.arraySlice * sliceBytes, "the array image's layer pitch differs from the color view's slice offset");
|
||||
auto resident = CachedStorageSurface(context, array);
|
||||
auto resident = CachedStorageLayers(context, array, color.arraySlice, color.layers);
|
||||
Require(resident->Attachable(), "storage format cannot be a color attachment");
|
||||
return resident;
|
||||
}
|
||||
@@ -2888,7 +2888,8 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con
|
||||
auto stored = recipe.targets[index].lock();
|
||||
if (stored == nullptr || !StorageImageCached(context, stored.get()) || !StorageImageServesKeys(*stored, state.colors[index].dccAddress)) return miss(DrawRecipeMiss::TargetGone);
|
||||
auto resident = refreshResidentTarget(context, state, state.colors[index], outcome, profile, [&] {
|
||||
stored->Refresh();
|
||||
if (state.colors[index].arrayLastSlice != 0) stored->RefreshLayers(state.colors[index].arraySlice, 1);
|
||||
else stored->Refresh();
|
||||
return stored;
|
||||
});
|
||||
timer.phase(PhaseReadTarget);
|
||||
@@ -2973,6 +2974,12 @@ std::shared_ptr<StorageTexture> metadataPassResident(const Context& context, con
|
||||
|
||||
}
|
||||
|
||||
void RunDepthCopyPass(const Context& context, const DepthCopyPass& pass) {
|
||||
Require(pass.color.tileMode != ColorTileMode::Linear && context.detiler != nullptr, "DB depth copy into a target without a resident image is not implemented");
|
||||
const auto resident = CachedStorageSurface(context, SurfaceForTarget(pass.color));
|
||||
CopyDepthSample(context, pass.depthAddress, pass.samples, pass.sample, *resident, resident->StorageView(0, false), pass.extent);
|
||||
}
|
||||
|
||||
void RunColorMetadataPass(const Context& context, const ColorMetadataPass& pass) {
|
||||
if (pass.mode == ColorMetadataPass::Mode::Resolve) {
|
||||
Require(pass.source.has_value() && pass.targets.size() == 1, "CB resolve without its source and destination");
|
||||
|
||||
@@ -232,7 +232,6 @@ struct HostImports {
|
||||
PFN_vkDestroyBuffer destroyBuffer = nullptr;
|
||||
PFN_vkFreeMemory freeMemory = nullptr;
|
||||
std::map<std::uint64_t, HostImport> imports;
|
||||
std::map<std::uint64_t, HostImport> trimmed;
|
||||
std::map<VkDevice, std::vector<std::shared_ptr<RetiredImport>>> parked;
|
||||
std::uint64_t liveBytes = 0;
|
||||
std::map<std::uint64_t, ImportFailure> failed;
|
||||
@@ -300,7 +299,7 @@ SettleBatch* forgetSettle(HostImports& state, std::uint64_t base) {
|
||||
SettleBatch* running = nullptr;
|
||||
if (const auto settle = state.settles.find(found->second.device); found->second.phase == SettlePhase::Submitted && settle != state.settles.end()) {
|
||||
auto& batch = settle->second.batches[found->second.batch];
|
||||
if (settleBatchRunning(settle->second, settle->first, batch)) running = &batch;
|
||||
if (batch.busy) running = &batch;
|
||||
}
|
||||
state.pendingSettles.erase(found);
|
||||
return running;
|
||||
@@ -427,6 +426,8 @@ bool releaseSettleRetired(HostImports& state, std::uint64_t begin, std::uint64_t
|
||||
++state.settleUnmapWaits;
|
||||
waitSettleBatch(settle, device, batch);
|
||||
}
|
||||
for (const auto alias : batch.aliases) settle.settleDestroyBuffer(device, alias, nullptr);
|
||||
batch.aliases.clear();
|
||||
for (auto& holder : batch.retired) released.push_back(std::move(holder));
|
||||
batch.retired.clear();
|
||||
}
|
||||
@@ -458,64 +459,6 @@ void shareDmaBufImport(const Context& context, VkBufferCreateInfo& info, ImportF
|
||||
}
|
||||
#endif
|
||||
|
||||
void releaseTrimmed(HostImports& state, ImportNode node) {
|
||||
state.liveBytes -= node.mapped().pinnedBytes;
|
||||
auto holder = std::make_shared<RetiredImport>(state.device, state.destroyBuffer, state.freeMemory, std::move(node));
|
||||
auto* recorder = Recorder::Active();
|
||||
if (recorder != nullptr) recorder->KeepThroughHold(std::move(holder));
|
||||
}
|
||||
|
||||
void dropTrimmed(HostImports& state, std::uint64_t begin, std::uint64_t end) {
|
||||
for (auto it = state.trimmed.begin(); it != state.trimmed.end();) {
|
||||
const auto next = std::next(it);
|
||||
if (it->first < end && begin < it->first + it->second.bytes) releaseTrimmed(state, state.trimmed.extract(it));
|
||||
it = next;
|
||||
}
|
||||
}
|
||||
|
||||
void retireOrTrim(VkDevice device, HostImports& state, std::map<std::uint64_t, HostImport>::iterator it, const GuestAllocations::Lease& lease, std::uint64_t begin, std::uint64_t end) {
|
||||
const auto& entry = it->second;
|
||||
const auto entryEnd = entry.base + entry.bytes;
|
||||
std::uint64_t first = 0;
|
||||
std::uint64_t last = 0;
|
||||
if (begin <= entry.base && end > entry.base && end < entryEnd) {
|
||||
first = end;
|
||||
last = entryEnd;
|
||||
} else if (begin > entry.base && begin < entryEnd && end >= entryEnd) {
|
||||
first = entry.base;
|
||||
last = begin;
|
||||
} else if (begin > entry.base && begin < end && end < entryEnd) {
|
||||
const bool tail = entryEnd - end >= begin - entry.base;
|
||||
first = tail ? end : entry.base;
|
||||
last = tail ? entryEnd : begin;
|
||||
}
|
||||
const bool plain = entry.alias == nullptr && !entry.dmaBuf && entry.chunk == nullptr && !entry.joined && entry.ranges.size() == 1 && entry.backingSerial != 0 && entry.memoryType != std::numeric_limits<std::uint32_t>::max() && entry.base == entry.range->address && entry.bytes == entry.range->bytes;
|
||||
#ifdef _WIN32
|
||||
const bool settling = state.pendingSettles.contains(it->first);
|
||||
#else
|
||||
const bool settling = false;
|
||||
#endif
|
||||
if (first == last || !plain || settling || state.trimmed.contains(first)) {
|
||||
retireImport(device, state, it, lease, false);
|
||||
return;
|
||||
}
|
||||
RetireShadow(device, entry, [&lease](std::uint64_t from, std::uint64_t to) { return runCovers(lease, from, to); });
|
||||
++state.epoch;
|
||||
auto node = state.imports.extract(it);
|
||||
auto& kept = node.mapped();
|
||||
std::shared_ptr<void> holder(nullptr, [device = state.device, destroy = state.destroyBuffer, buffer = std::exchange(kept.buffer, VK_NULL_HANDLE)](void*) { destroy(device, buffer, nullptr); });
|
||||
kept.range.reset();
|
||||
kept.ranges.clear();
|
||||
kept.base = first;
|
||||
kept.bytes = last - first;
|
||||
kept.address = 0;
|
||||
kept.serial = 0;
|
||||
node.key() = first;
|
||||
state.trimmed.insert(std::move(node));
|
||||
auto* recorder = Recorder::Active();
|
||||
if (recorder != nullptr && !recorder->Idle()) recorder->Keep(std::move(holder));
|
||||
}
|
||||
|
||||
VkResult createImportBuffer(const Context& context, VkExternalMemoryHandleTypeFlagBits handleType, VkDeviceSize size, VkBuffer* buffer) {
|
||||
const VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO, nullptr, static_cast<VkExternalMemoryHandleTypeFlags>(handleType)};
|
||||
VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external};
|
||||
@@ -1407,9 +1350,6 @@ bool sameRun(const Entry& entry, const ImportRun& run) {
|
||||
|
||||
bool wouldRetireImport(const HostImports& state, const ImportRun& run) {
|
||||
if (run.ranges.empty()) return false;
|
||||
for (const auto& [base, entry] : state.trimmed) {
|
||||
if (base < run.base + run.bytes && run.base < base + entry.bytes) return true;
|
||||
}
|
||||
const auto next = state.imports.lower_bound(run.base);
|
||||
if (next != state.imports.end()) {
|
||||
if (next->first == run.base && sameRun(next->second, run)) return false;
|
||||
@@ -1686,7 +1626,6 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import
|
||||
if (sameRun(failed->second, run)) return nullptr;
|
||||
failures.erase(failed);
|
||||
}
|
||||
dropTrimmed(state, base, base + bytes);
|
||||
const auto budget = effectiveBudget(context, state);
|
||||
const auto containing = std::find_if(run.ranges.begin(), run.ranges.end(), [address](const auto& range) { return address >= range->address && address < range->address + range->bytes; });
|
||||
if (const auto remembered = state.evictingRefusals.find({base, bytes}); evict && remembered != state.evictingRefusals.end()) {
|
||||
@@ -1920,46 +1859,6 @@ const GuestAllocations::Range* containingRange(const GuestAllocations::Lease& le
|
||||
return range->readable && begin >= range->address && end <= range->address + range->bytes ? range.get() : nullptr;
|
||||
}
|
||||
|
||||
bool reattachTrimmed(const Context& context, HostImports& state, HostImport& entry, const GuestAllocations::Lease& lease) {
|
||||
const auto first = entry.base;
|
||||
const auto last = entry.base + entry.bytes;
|
||||
std::shared_ptr<const GuestAllocations::Range> kept;
|
||||
for (auto it = std::lower_bound(lease.begin(), lease.end(), first, [](const auto& range, std::uint64_t value) { return range->address < value; }); it != lease.end() && (*it)->address < last; ++it) {
|
||||
if ((*it)->backingSerial == entry.backingSerial && (*it)->address + (*it)->bytes <= last && (kept == nullptr || (*it)->bytes > kept->bytes)) kept = *it;
|
||||
}
|
||||
if (kept == nullptr) return false;
|
||||
const auto next = state.imports.lower_bound(kept->address);
|
||||
if (next != state.imports.end() && next->first < kept->address + kept->bytes) return false;
|
||||
if (next != state.imports.begin() && std::prev(next)->second.base + std::prev(next)->second.bytes > kept->address) return false;
|
||||
HostImport candidate{kept->address, kept->bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0};
|
||||
candidate.range = kept;
|
||||
candidate.ranges = {kept};
|
||||
if (!sameRange(candidate, lease)) return false;
|
||||
const auto offset = kept->address - entry.memoryBase;
|
||||
VkBuffer buffer = VK_NULL_HANDLE;
|
||||
if (createImportBuffer(context, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, kept->bytes, &buffer) != VK_SUCCESS) return false;
|
||||
const auto refuse = [&] {
|
||||
context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(context.device, buffer, nullptr);
|
||||
return false;
|
||||
};
|
||||
VkMemoryRequirements requirements{};
|
||||
context.Function<PFN_vkGetBufferMemoryRequirements>("vkGetBufferMemoryRequirements")(context.device, buffer, &requirements);
|
||||
if (((requirements.memoryTypeBits >> entry.memoryType) & 1u) == 0 || requirements.alignment == 0 || offset % requirements.alignment != 0 || requirements.size > entry.pinnedBytes - offset) return refuse();
|
||||
if (context.Function<PFN_vkBindBufferMemory>("vkBindBufferMemory")(context.device, buffer, entry.memory, offset) != VK_SUCCESS) return refuse();
|
||||
const VkBufferDeviceAddressInfo addressInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, buffer};
|
||||
const auto address = context.Function<PFN_vkGetBufferDeviceAddressKHR>("vkGetBufferDeviceAddressKHR")(context.device, &addressInfo);
|
||||
if (address == 0) return refuse();
|
||||
entry.base = kept->address;
|
||||
entry.bytes = kept->bytes;
|
||||
entry.buffer = buffer;
|
||||
entry.address = address;
|
||||
entry.range = kept;
|
||||
entry.ranges = {kept};
|
||||
entry.serial = 0;
|
||||
entry.lastUse = ++state.useCounter;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Drops imports whose registered range changed or disappeared: their pages may no longer back the
|
||||
// guest addresses. Walks the imports only when the registry changed since the last walk.
|
||||
void refreshImports(const Context& context, HostImports& state, const GuestAllocations::Lease& lease) {
|
||||
@@ -1967,17 +1866,15 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc
|
||||
#ifdef _WIN32
|
||||
drainSettles(state);
|
||||
#endif
|
||||
for (auto* held : {&state.imports, &state.trimmed}) {
|
||||
while (!held->empty()) {
|
||||
auto node = held->extract(held->begin());
|
||||
if (state.device != VK_NULL_HANDLE && state.destroyBuffer != nullptr && state.freeMemory != nullptr) {
|
||||
state.parked[state.device].push_back(std::make_shared<RetiredImport>(state.device, state.destroyBuffer, state.freeMemory, std::move(node)));
|
||||
continue;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaUnmapAlias_nid_postfix(node.mapped().alias);
|
||||
#endif
|
||||
while (!state.imports.empty()) {
|
||||
auto node = state.imports.extract(state.imports.begin());
|
||||
if (state.device != VK_NULL_HANDLE && state.destroyBuffer != nullptr && state.freeMemory != nullptr) {
|
||||
state.parked[state.device].push_back(std::make_shared<RetiredImport>(state.device, state.destroyBuffer, state.freeMemory, std::move(node)));
|
||||
continue;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaUnmapAlias_nid_postfix(node.mapped().alias);
|
||||
#endif
|
||||
}
|
||||
state.liveBytes = 0;
|
||||
state.failed.clear();
|
||||
@@ -2008,16 +1905,6 @@ void refreshImports(const Context& context, HostImports& state, const GuestAlloc
|
||||
};
|
||||
std::erase_if(state.sparseRunEnds, [&](const auto& entry) { return !starts(entry.first); });
|
||||
std::erase_if(state.sparseRunJoins, [&](const auto& entry) { return !starts(entry.first); });
|
||||
while (!state.trimmed.empty()) {
|
||||
auto node = state.trimmed.extract(state.trimmed.begin());
|
||||
if (!reattachTrimmed(context, state, node.mapped(), lease)) {
|
||||
releaseTrimmed(state, std::move(node));
|
||||
continue;
|
||||
}
|
||||
node.key() = node.mapped().base;
|
||||
++state.epoch;
|
||||
state.imports.insert(std::move(node));
|
||||
}
|
||||
const auto budget = effectiveBudget(context, state);
|
||||
for (auto it = state.imports.begin(); it != state.imports.end();) {
|
||||
if (sameRange(it->second, lease, budget)) {
|
||||
@@ -2857,6 +2744,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) {
|
||||
bool synced = false;
|
||||
bool drained = false;
|
||||
bool retiredImport = false;
|
||||
bool droppedAddressSpace = false;
|
||||
// A range pinned only by the cached address space is released by dropping the cache's
|
||||
// reference: no GPU wait, no device lock (the mutating thread may be a driver thread holding
|
||||
// it). The registry rescans; a range still pinned by a build or a batch holding the space comes
|
||||
@@ -2865,12 +2753,24 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) {
|
||||
Spaces().droppedByWaiter.store(true, std::memory_order_relaxed);
|
||||
Spaces().waiterDrops.fetch_add(1, std::memory_order_relaxed);
|
||||
dropped.reset();
|
||||
auto& state = Leases();
|
||||
std::lock_guard lock(state.mutex);
|
||||
++state.stats.contentionWaits;
|
||||
++state.stats.cacheDrops;
|
||||
state.stats.contentionMs += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
return true;
|
||||
droppedAddressSpace = true;
|
||||
auto& imports = Imports();
|
||||
const auto end = static_cast<std::uint64_t>(address) + bytes;
|
||||
bool overlapsImport = false;
|
||||
{
|
||||
std::lock_guard importsLock(imports.mutex);
|
||||
const auto next = imports.imports.lower_bound(address);
|
||||
const auto previous = next == imports.imports.begin() ? imports.imports.end() : std::prev(next);
|
||||
overlapsImport = (next != imports.imports.end() && next->first < end) || (previous != imports.imports.end() && previous->first + previous->second.bytes > address);
|
||||
}
|
||||
if (!overlapsImport) {
|
||||
auto& state = Leases();
|
||||
std::lock_guard lock(state.mutex);
|
||||
++state.stats.contentionWaits;
|
||||
++state.stats.cacheDrops;
|
||||
state.stats.contentionMs += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (Recorder::InCompletion()) throw std::runtime_error("guest allocation mutation cannot wait for an active GPU completion");
|
||||
if (GuestMemory::GpuMutex().HeldByThisThread()) {
|
||||
@@ -2895,7 +2795,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) {
|
||||
continue;
|
||||
}
|
||||
const auto next = std::next(it);
|
||||
retireOrTrim(imports.device, imports, it, lease, address, end);
|
||||
retireImport(imports.device, imports, it, lease, false);
|
||||
it = next;
|
||||
retiredImport = true;
|
||||
}
|
||||
@@ -2961,7 +2861,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) {
|
||||
continue;
|
||||
}
|
||||
const auto next = std::next(it);
|
||||
retireOrTrim(imports.device, imports, it, lease, address, end);
|
||||
retireImport(imports.device, imports, it, lease, false);
|
||||
it = next;
|
||||
retiredImport = true;
|
||||
}
|
||||
@@ -2980,6 +2880,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) {
|
||||
auto& state = Leases();
|
||||
std::lock_guard lock(state.mutex);
|
||||
++state.stats.contentionWaits;
|
||||
if (droppedAddressSpace) ++state.stats.cacheDrops;
|
||||
if (synced) ++state.stats.contentionSyncs;
|
||||
if (drained) ++state.stats.contentionDrains;
|
||||
state.stats.contentionMs += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
@@ -3141,9 +3042,7 @@ void ClearHostImports(VkDevice device) {
|
||||
GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
|
||||
#endif
|
||||
}
|
||||
for (const auto& [address, entry] : state.trimmed) state.freeMemory(state.device, entry.memory, nullptr);
|
||||
state.imports.clear();
|
||||
state.trimmed.clear();
|
||||
state.liveBytes = 0;
|
||||
state.failed.clear();
|
||||
state.failedJoins.clear();
|
||||
@@ -3436,6 +3335,13 @@ std::uint64_t HostImportLiveBytes() {
|
||||
return state.liveBytes;
|
||||
}
|
||||
|
||||
std::uint64_t HostImportBudgetRemaining(const Context& context) {
|
||||
auto& state = Imports();
|
||||
std::lock_guard lock(state.mutex);
|
||||
const auto budget = effectiveBudget(context, state);
|
||||
return budget > state.liveBytes ? budget - state.liveBytes : 0;
|
||||
}
|
||||
|
||||
const char* ImportRequesterName(ImportRequester requester) {
|
||||
switch (requester) {
|
||||
case ImportRequester::Map: return "map";
|
||||
@@ -4636,12 +4542,6 @@ void GuestBufferMemory::UploadFinish(bool addressable) {
|
||||
if (profile) refreshUs += microsecondsSince(refreshStart);
|
||||
continue;
|
||||
}
|
||||
if (sparseHeapCandidate(region)) {
|
||||
const auto refreshStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
const bool bound = bindSparseMirror(region, importRanges());
|
||||
if (profile) refreshUs += microsecondsSince(refreshStart);
|
||||
if (bound) continue;
|
||||
}
|
||||
bool copyOnGpu = false;
|
||||
if (context.hostImportAlignment != 0) {
|
||||
auto& state = Imports();
|
||||
@@ -4728,6 +4628,12 @@ void GuestBufferMemory::UploadFinish(bool addressable) {
|
||||
gpuCopies.push_back(®ion);
|
||||
continue;
|
||||
}
|
||||
if (sparseHeapCandidate(region)) {
|
||||
const auto refreshStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
const bool bound = bindSparseMirror(region, importRanges());
|
||||
if (profile) refreshUs += microsecondsSince(refreshStart);
|
||||
if (bound) continue;
|
||||
}
|
||||
if (region.deviceLocal) {
|
||||
// The staging shadow UploadPrepare made cannot take the CPU copy (no host mapping): the
|
||||
// region falls back to a host buffer of its own.
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleSwizzleEquations.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
namespace {
|
||||
|
||||
std::array<std::uint32_t, 16> inverseBlock(const MultisampleSwizzleEquation& equation, std::uint32_t byteBits, std::uint32_t fragmentBits, std::uint32_t widthBits) {
|
||||
std::array<std::uint32_t, 16> rows{}, inverse{};
|
||||
for (std::uint32_t input = 0; input < 16u; ++input) {
|
||||
std::uint32_t address = 0u;
|
||||
if (input < byteBits) address = 1u << input;
|
||||
else {
|
||||
const auto coordinateBit = input < byteBits + fragmentBits ? 48u + input - byteBits : input < byteBits + fragmentBits + widthBits ? input - byteBits - fragmentBits : 16u + input - byteBits - fragmentBits - widthBits;
|
||||
const auto coordinate = std::uint64_t{1} << coordinateBit;
|
||||
for (std::uint32_t output = 0; output < 16u; ++output) address |= static_cast<std::uint32_t>(std::popcount(coordinate & equation.bits[output]) & 1u) << output;
|
||||
}
|
||||
for (std::uint32_t output = 0; output < 16u; ++output) rows[output] |= ((address >> output) & 1u) << input;
|
||||
inverse[input] = 1u << input;
|
||||
}
|
||||
for (std::uint32_t input = 0; input < 16u; ++input) {
|
||||
auto pivot = input;
|
||||
while (pivot < 16u && (rows[pivot] & (1u << input)) == 0u) ++pivot;
|
||||
Require(pivot < 16u, "multisample block address equation is not invertible");
|
||||
std::swap(rows[input], rows[pivot]);
|
||||
std::swap(inverse[input], inverse[pivot]);
|
||||
for (std::uint32_t row = 0; row < 16u; ++row) {
|
||||
if (row == input || (rows[row] & (1u << input)) == 0u) continue;
|
||||
rows[row] ^= rows[input];
|
||||
inverse[row] ^= inverse[input];
|
||||
}
|
||||
}
|
||||
return inverse;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> axisOffsets(const MultisampleSwizzleEquation& equation, std::uint32_t count, std::uint32_t shift) {
|
||||
std::vector<std::uint32_t> offsets(count);
|
||||
for (std::uint32_t value = 0; value < count; ++value) {
|
||||
const auto coordinate = static_cast<std::uint64_t>(value) << shift;
|
||||
for (std::uint32_t bit = 0; bit < equation.bits.size(); ++bit) {
|
||||
offsets[value] |= static_cast<std::uint32_t>(std::popcount(coordinate & equation.bits[bit]) & 1u) << bit;
|
||||
}
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MultisampleTextureLayout::MultisampleTextureLayout(TextureTileMode tileMode, std::uint32_t elementBytes, std::uint32_t fragments, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t pipeBankXor) : elementBytes(elementBytes), fragments(fragments), width(width), height(height), layers(layers), pipeBankXor(pipeBankXor) {
|
||||
Require(width != 0 && height != 0 && width <= 16384u && height <= 16384u && layers != 0 && layers <= 8192u, "invalid multisampled surface extent or layer count");
|
||||
Require(std::has_single_bit(elementBytes) && elementBytes <= 16u, "unsupported multisampled surface element size");
|
||||
Require(fragments == 2u || fragments == 4u || fragments == 8u, "unsupported multisampled surface fragment count");
|
||||
Require((pipeBankXor & ~0xcf00u) == 0, "multisampled surface pipe/bank XOR uses bits outside its 16-pipe swizzle");
|
||||
Require(tileMode != TextureTileMode::kZ64KBX || elementBytes <= 4u, "multisampled Z surface element size exceeds 32 bits");
|
||||
const auto* equation = FindMultisampleSwizzleEquation(tileMode, elementBytes, fragments);
|
||||
Require(equation != nullptr, "unsupported multisampled surface tile mode");
|
||||
|
||||
const auto fragmentBits = static_cast<std::uint32_t>(std::countr_zero(fragments));
|
||||
const auto elementBits = 16u - static_cast<std::uint32_t>(std::countr_zero(elementBytes)) - fragmentBits;
|
||||
const auto widthBits = (elementBits + ((fragmentBits & 1u) == 0 ? 1u : 0u)) / 2u;
|
||||
blockWidth = 1u << widthBits;
|
||||
blockHeight = 1u << (elementBits - widthBits);
|
||||
inverseBlockEquation = inverseBlock(*equation, static_cast<std::uint32_t>(std::countr_zero(elementBytes)), fragmentBits, widthBits);
|
||||
pitch = (width + blockWidth - 1u) / blockWidth * blockWidth;
|
||||
paddedHeight = (height + blockHeight - 1u) / blockHeight * blockHeight;
|
||||
sliceBytes = static_cast<std::uint64_t>(pitch) * paddedHeight * fragments * elementBytes;
|
||||
Require(sliceBytes <= std::numeric_limits<std::uint64_t>::max() / layers, "multisampled surface size overflows");
|
||||
xOffsets = axisOffsets(*equation, width, 0u);
|
||||
yOffsets = axisOffsets(*equation, height, 16u);
|
||||
layerOffsets = axisOffsets(*equation, layers, 32u);
|
||||
fragmentOffsets = axisOffsets(*equation, fragments, 48u);
|
||||
}
|
||||
|
||||
std::uint64_t MultisampleTextureLayout::offset(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t fragment) const {
|
||||
const auto block = static_cast<std::uint64_t>(y / blockHeight) * (pitch / blockWidth) + x / blockWidth;
|
||||
const auto withinBlock = xOffsets[x] ^ yOffsets[y] ^ layerOffsets[layer] ^ fragmentOffsets[fragment] ^ pipeBankXor;
|
||||
return static_cast<std::uint64_t>(layer) * sliceBytes + block * Alignment + withinBlock;
|
||||
}
|
||||
|
||||
std::uint64_t MultisampleTextureLayout::Offset(std::uint32_t x, std::uint32_t y, std::uint32_t layer, std::uint32_t fragment) const {
|
||||
Require(x < width && y < height && layer < layers && fragment < fragments, "multisampled surface coordinate is outside its extent");
|
||||
return offset(x, y, layer, fragment);
|
||||
}
|
||||
|
||||
void MultisampleTextureLayout::transfer(std::span<const std::byte> source, std::span<std::byte> destination, bool retile) const {
|
||||
const auto sourceBytes = retile ? LinearBytes() : Bytes();
|
||||
const auto destinationBytes = retile ? Bytes() : LinearBytes();
|
||||
Require(source.size() >= sourceBytes && destination.size() >= destinationBytes, "multisampled surface transfer buffer is too small");
|
||||
const auto sourceAddress = reinterpret_cast<std::uintptr_t>(source.data());
|
||||
const auto destinationAddress = reinterpret_cast<std::uintptr_t>(destination.data());
|
||||
Require(sourceAddress <= std::numeric_limits<std::uintptr_t>::max() - sourceBytes && destinationAddress <= std::numeric_limits<std::uintptr_t>::max() - destinationBytes, "multisampled surface transfer range overflows");
|
||||
Require(sourceAddress + sourceBytes <= destinationAddress || destinationAddress + destinationBytes <= sourceAddress, "multisampled surface transfer buffers overlap");
|
||||
|
||||
std::uint64_t linear = 0;
|
||||
for (std::uint32_t layer = 0; layer < layers; ++layer) {
|
||||
for (std::uint32_t y = 0; y < height; ++y) {
|
||||
for (std::uint32_t x = 0; x < width; ++x) {
|
||||
for (std::uint32_t fragment = 0; fragment < fragments; ++fragment) {
|
||||
const auto tiled = offset(x, y, layer, fragment);
|
||||
std::memcpy(destination.data() + (retile ? tiled : linear), source.data() + (retile ? linear : tiled), elementBytes);
|
||||
linear += elementBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultisampleTextureLayout::Detile(std::span<const std::byte> source, std::span<std::byte> destination) const {
|
||||
transfer(source, destination, false);
|
||||
}
|
||||
|
||||
void MultisampleTextureLayout::Tile(std::span<const std::byte> source, std::span<std::byte> destination) const {
|
||||
transfer(source, destination, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/shaders/MultisampleUpload_spv.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
VkPipeline TextureDetiler::multisampleUploadPipeline(std::uint32_t elementBytes) {
|
||||
const auto key = (1u << 19u) | elementBytes;
|
||||
for (const auto& entry : pipelines) if (entry.first == key) return entry.second;
|
||||
if (multisampleUploadModule == VK_NULL_HANDLE) {
|
||||
VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
info.codeSize = sizeof(MULTISAMPLE_UPLOAD_SPV);
|
||||
info.pCode = MULTISAMPLE_UPLOAD_SPV;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &info, nullptr, &multisampleUploadModule), "vkCreateShaderModule multisample upload");
|
||||
}
|
||||
const VkSpecializationMapEntry entry{0u, 0u, sizeof(elementBytes)};
|
||||
const VkSpecializationInfo specialization{1u, &entry, sizeof(elementBytes), &elementBytes};
|
||||
VkComputePipelineCreateInfo info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
info.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
info.stage.module = multisampleUploadModule;
|
||||
info.stage.pName = "main";
|
||||
info.stage.pSpecializationInfo = &specialization;
|
||||
info.layout = imagePipelineLayout;
|
||||
pipelines.reserve(pipelines.size() + 1u);
|
||||
VkPipeline result = VK_NULL_HANDLE;
|
||||
{
|
||||
auto pipelineCacheLock = context.LockPipelineCacheForUse();
|
||||
Check(context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1u, &info, nullptr, &result), "vkCreateComputePipelines multisample upload");
|
||||
}
|
||||
pipelines.emplace_back(key, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void TextureDetiler::DispatchMultisampleImage(VkCommandBuffer commands, std::uint32_t elementBytes, VkBuffer source, std::uint64_t offset, std::uint64_t bytes, VkImageView arrayView, std::uint32_t width, std::uint32_t height, std::uint32_t layers, std::uint32_t fragments, std::uint32_t firstElement, std::uint32_t elements) {
|
||||
Require(context.storageImageMultisample && context.storageImageWriteWithoutFormat, "device does not enable multisampled storage image writes without format");
|
||||
Require(commands != VK_NULL_HANDLE && source != VK_NULL_HANDLE && arrayView != VK_NULL_HANDLE, "multisample upload requires commands, buffer and array view");
|
||||
Require(std::has_single_bit(elementBytes) && elementBytes <= 16u, "multisample upload element size is unsupported");
|
||||
Require(width != 0u && width <= 16384u && height != 0u && height <= 16384u && layers != 0u && layers <= 8192u, "multisample upload extent is unsupported");
|
||||
Require(fragments == 2u || fragments == 4u || fragments == 8u, "multisample upload fragment count is unsupported");
|
||||
const auto total = static_cast<std::uint64_t>(width) * height * layers * fragments;
|
||||
Require(total <= std::numeric_limits<std::uint32_t>::max() && elements != 0u && static_cast<std::uint64_t>(firstElement) + elements <= total, "multisample upload element range is invalid");
|
||||
const auto required = (static_cast<std::uint64_t>(elements) * elementBytes + 3u) & ~3ull;
|
||||
const auto alignment = std::max<std::uint64_t>(4u, context.limits.minStorageBufferOffsetAlignment);
|
||||
Require(offset % alignment == 0u && bytes >= required && bytes <= context.limits.maxStorageBufferRange && bytes % 4u == 0u && offset <= std::numeric_limits<std::uint64_t>::max() - bytes, "multisample upload buffer range is invalid");
|
||||
Require(context.limits.maxComputeWorkGroupInvocations >= 64u && context.limits.maxComputeWorkGroupSize[0] >= 64u && context.limits.maxComputeWorkGroupSize[1] >= 1u && context.limits.maxComputeWorkGroupSize[2] >= 1u && context.limits.maxComputeWorkGroupCount[2] >= 1u, "device does not support the multisample upload workgroup");
|
||||
const auto groups = (static_cast<std::uint64_t>(elements) + 63u) / 64u;
|
||||
const auto groupsX = std::min<std::uint64_t>(groups, context.limits.maxComputeWorkGroupCount[0]);
|
||||
Require(groupsX != 0u, "device has no multisample upload workgroups");
|
||||
const auto groupsY = (groups + groupsX - 1u) / groupsX;
|
||||
Require(groupsY <= context.limits.maxComputeWorkGroupCount[1] && groupsX * groupsY * 64u <= std::numeric_limits<std::uint32_t>::max(), "multisample upload dispatch exceeds the device limits");
|
||||
const auto target = multisampleUploadPipeline(elementBytes);
|
||||
const auto set = allocateSet(true);
|
||||
const VkDescriptorBufferInfo buffer{source, offset, bytes};
|
||||
const VkDescriptorImageInfo image{VK_NULL_HANDLE, arrayView, VK_IMAGE_LAYOUT_GENERAL};
|
||||
const std::array<VkWriteDescriptorSet, 2> writes{{{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, 0u, 0u, 1u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nullptr, &buffer, nullptr}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, 1u, 0u, 1u, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, &image, nullptr, nullptr}}};
|
||||
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, 2u, writes.data(), 0u, nullptr);
|
||||
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target);
|
||||
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, imagePipelineLayout, 0u, 1u, &set, 0u, nullptr);
|
||||
const std::array<std::uint32_t, 7> push{width, height, layers, fragments, firstElement, elements, static_cast<std::uint32_t>(groupsX * 64u)};
|
||||
context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants")(commands, imagePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0u, sizeof(push), push.data());
|
||||
context.Function<PFN_vkCmdDispatch>("vkCmdDispatch")(commands, static_cast<std::uint32_t>(groupsX), static_cast<std::uint32_t>(groupsY), 1u);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -122,7 +122,7 @@ void ValidateProvokingVertex(const Context& context, const State& state, std::sp
|
||||
Require(context.provokingVertexLast, "last provoking vertex requires VK_EXT_provoking_vertex with provokingVertexLast enabled");
|
||||
const bool meshPrimitives = state.stages.path == ShaderPath::Geometry && state.stages.mesh;
|
||||
Require(!state.stages.tessellation && (meshPrimitives || (state.stages.path == ShaderPath::Vertex && !state.stages.mesh)), "last provoking vertex is unsupported for generated primitive pipelines");
|
||||
Require(!meshPrimitives || (state.topology != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP && state.topology != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN), "last provoking vertex is unsupported for triangle strips or fans assembled for a geometry program");
|
||||
Require(!meshPrimitives || state.topology != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, "last provoking vertex is unsupported for triangle fans assembled for a geometry program");
|
||||
Require(state.rectList || state.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP || state.topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, "last provoking vertex is unsupported for this primitive topology");
|
||||
for (const auto& shader : shaders) {
|
||||
if (shader.stage != ShaderRecompiler::ShaderStage::Fragment) continue;
|
||||
|
||||
@@ -461,7 +461,12 @@ private:
|
||||
bool outermost;
|
||||
};
|
||||
|
||||
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes = 0, bool written = false);
|
||||
void refreshForView(StorageTexture& source, const GuestTextureResource& resource) {
|
||||
if (resource.dimension == TextureDimension::k2D && source.ImageLayers() > 1) source.RefreshLayers(resource.baseArray, 1);
|
||||
else source.Refresh();
|
||||
}
|
||||
|
||||
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& resource, std::uint32_t mip, std::uint64_t guestBytes = 0, bool written = false, std::optional<std::pair<std::uint32_t, std::uint32_t>> layers = std::nullopt);
|
||||
|
||||
std::uint64_t heldBytes(const StorageTexture& image) {
|
||||
return image.BudgetBytes();
|
||||
@@ -787,9 +792,9 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
// so a write the game made since the last walk is invisible until a collect stamps its page. The
|
||||
// walk is memoized per packet, so a texture viewed from a storage image repeats it for free.
|
||||
auto generation = GuestMemory::CollectWrites(address, bytes);
|
||||
if (source != nullptr && !GuestMemory::UnchangedSince(address, bytes, source->Generation())) {
|
||||
if (source != nullptr && !GuestMemory::UnchangedSince(address, bytes, source->GenerationFor(address, bytes))) {
|
||||
// The CPU wrote the memory while results were pending: Refresh merges them the usual way.
|
||||
source->Refresh();
|
||||
refreshForView(*source, resource);
|
||||
}
|
||||
// Results stored to guest memory just now are stamped newer than `generation`; a snapshot read
|
||||
// after them is current at the generation of a second (memoized) collect. The store marks the
|
||||
@@ -857,7 +862,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
// written meanwhile is taken in by refreshing the image. A fast clear the image cannot
|
||||
// see (keys, and no pending results to prefer) ends the view: a snapshot holds the clear.
|
||||
if (!stagedFlushed && (source == nullptr || source == it->source) && (source != nullptr || *keys == DccKeys::Uncompressed) && StorageImageServesKeys(*it->source, resource.dccAddress)) {
|
||||
if (!GuestMemory::UnchangedSince(address, bytes, it->source->Generation())) it->source->Refresh();
|
||||
if (!GuestMemory::UnchangedSince(address, bytes, it->source->GenerationFor(address, bytes))) refreshForView(*it->source, resource);
|
||||
if (it->texture->IsDepthCopy()) it->texture->RefreshDepthCopy();
|
||||
it->keys = *keys;
|
||||
touchTexture(cache, it);
|
||||
@@ -1108,7 +1113,7 @@ static WrittenStorage WrittenStorageBytes(const GuestTextureResource& viewed, st
|
||||
}
|
||||
|
||||
// `guestBytes` is the surface size when the caller described the surface already (0: described here).
|
||||
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& requested, std::uint32_t mip, std::uint64_t guestBytes, bool written) {
|
||||
std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& requested, std::uint32_t mip, std::uint64_t guestBytes, bool written, std::optional<std::pair<std::uint32_t, std::uint32_t>> layers) {
|
||||
const auto viewed = ArrayLayerWindow(requested, context.limits.maxImageArrayLayers);
|
||||
if (viewed.baseAddress != requested.baseAddress) guestBytes = 0;
|
||||
Require(viewed.dimension != TextureDimension::k2DMsaa, "storage image access to a multisampled texture is not implemented");
|
||||
@@ -1123,7 +1128,7 @@ std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std
|
||||
reportGuestAllocation(viewed, viewedBytes);
|
||||
}
|
||||
}
|
||||
if (DepthPlanesStale() || written) static_cast<void>(FlushDepthPlanes(viewed.baseAddress, static_cast<std::size_t>(viewedBytes)));
|
||||
if (DepthPlanesStale() || written) static_cast<void>(FlushDepthPlanes(viewed.baseAddress, static_cast<std::size_t>(viewedBytes), written ? DepthPlaneScope::Whole : DepthPlaneScope::Overlapped));
|
||||
if (written && DepthSurfaceOverlaps(context.device, viewed.baseAddress, viewedBytes)) {
|
||||
const auto storage = WrittenStorageBytes(viewed, mip);
|
||||
RetireCoveredDepthSurfaces(context.device, storage.bytes, storage.extents);
|
||||
@@ -1154,7 +1159,8 @@ std::shared_ptr<StorageTexture> cachedStorageTexture(const Context& context, std
|
||||
it = cache.entries.end();
|
||||
}
|
||||
if (it != cache.entries.end()) {
|
||||
it->texture->Refresh();
|
||||
if (layers) it->texture->RefreshLayers(layers->first, layers->second);
|
||||
else it->texture->Refresh();
|
||||
cache.entries.splice(cache.entries.begin(), cache.entries, it);
|
||||
texture = it->texture;
|
||||
counters.storageHits.fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -1436,6 +1442,10 @@ std::shared_ptr<StorageTexture> CachedStorageSurface(const Context& context, con
|
||||
return cachedStorageTexture(context, {}, resource, 0);
|
||||
}
|
||||
|
||||
std::shared_ptr<StorageTexture> CachedStorageLayers(const Context& context, const GuestTextureResource& resource, std::uint32_t firstLayer, std::uint32_t layerCount) {
|
||||
return cachedStorageTexture(context, {}, resource, 0, 0, false, std::pair{firstLayer, layerCount});
|
||||
}
|
||||
|
||||
bool StorageImageServesKeys(const StorageTexture& image, std::uint64_t dccAddress) {
|
||||
GuestTextureResource resource{};
|
||||
resource.dccAddress = dccAddress;
|
||||
@@ -3005,7 +3015,7 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span<const
|
||||
}
|
||||
if (source != nullptr) {
|
||||
if (!source->Cached()) return fail(FastFail::Evicted);
|
||||
queries.push_back({address, bytes, source->Generation()});
|
||||
queries.push_back({address, bytes, source->GenerationFor(address, bytes)});
|
||||
images.push_back(source);
|
||||
} else {
|
||||
queries.push_back({address, bytes, surface.generation});
|
||||
@@ -3033,7 +3043,7 @@ bool ShaderResources::fastRevalidate(std::uint64_t serialBefore, std::span<const
|
||||
}
|
||||
if (!unchanged) query(address, address + bytes, image, nullptr, {i, true, false});
|
||||
if (!image->Cached()) return fail(FastFail::Evicted);
|
||||
queries.push_back({address, bytes, image->Generation()});
|
||||
queries.push_back({address, bytes, image->GenerationFor(address, bytes)});
|
||||
images.push_back(image);
|
||||
}
|
||||
if (!pending.empty()) {
|
||||
@@ -3101,7 +3111,7 @@ bool ShaderResources::fastRevalidateEach() {
|
||||
if (surface.source != nullptr) {
|
||||
// The view follows the image: it needs the image current with guest memory, as the
|
||||
// lookup's Refresh would make it.
|
||||
if (!StorageImageCached(context, surface.source) || !GuestMemory::UnchangedSince(address, bytes, surface.source->Generation())) return false;
|
||||
if (!StorageImageCached(context, surface.source) || !GuestMemory::UnchangedSince(address, bytes, surface.source->GenerationFor(address, bytes))) return false;
|
||||
} else if (!GuestMemory::UnchangedSince(address, bytes, surface.generation)) {
|
||||
return false;
|
||||
}
|
||||
@@ -3135,7 +3145,7 @@ bool ShaderResources::fastRevalidateEach() {
|
||||
if (GuestMemory::CollectWrites(address, bytes) == 0) return false;
|
||||
if (PendingStorageOverlaps(address, bytes, image)) return false;
|
||||
if (!StorageImageCached(context, image)) return false;
|
||||
if (!GuestMemory::UnchangedSince(address, bytes, image->Generation())) return false;
|
||||
if (!GuestMemory::UnchangedSince(address, bytes, image->GenerationFor(address, bytes))) return false;
|
||||
}
|
||||
// Every check passed: snapshot textures are current at the collects issued before them. Storage
|
||||
// images keep their own generation: nothing was stamped above it, so the blocks a later write
|
||||
@@ -3169,9 +3179,9 @@ ShaderResources::OwnRefreshFallback ShaderResources::refreshOwnObjects(std::span
|
||||
if (found != nullptr && !Texture::CanCopyFrom(*found, surface.resource)) found.reset();
|
||||
if (found != nullptr) {
|
||||
if (found != source) return OwnRefreshFallback::ForeignView;
|
||||
if (!GuestMemory::UnchangedSince(address, bytes, source->Generation())) {
|
||||
if (!GuestMemory::UnchangedSince(address, bytes, source->GenerationFor(address, bytes))) {
|
||||
countOwnRefresh(false);
|
||||
source->Refresh();
|
||||
refreshForView(*source, surface.resource);
|
||||
}
|
||||
return OwnRefreshFallback::Count;
|
||||
}
|
||||
@@ -4579,7 +4589,7 @@ std::shared_ptr<Texture> ShaderResources::fastTexture(const ImageRecord& record)
|
||||
// keys and stays valid while they are unchanged); a change is the full lookup's to judge.
|
||||
if (keys != record.entryKeys) return nullptr;
|
||||
if (record.source != nullptr) {
|
||||
if (!StorageImageCached(context, record.source.get()) || !GuestMemory::UnchangedSince(address, bytes, record.source->Generation())) return nullptr;
|
||||
if (!StorageImageCached(context, record.source.get()) || !GuestMemory::UnchangedSince(address, bytes, record.source->GenerationFor(address, bytes))) return nullptr;
|
||||
if (record.resource.dccAddress != 0 && IsDccClear(record.source->FilledKeys())) return nullptr;
|
||||
// Under fast-clear keys the view holds only while its image's results are still pending over
|
||||
// the surface; flushed, the clear the image cannot see makes the lookup take a snapshot.
|
||||
|
||||
@@ -138,9 +138,10 @@ struct Module {
|
||||
Require(input && (clipInput || (value == spv::BuiltInPosition && signature == "f32x4") || (value == spv::BuiltInTessCoord && stage == Stage::TessellationEvaluation && signature == "f32x3") || ((value == spv::BuiltInInvocationId || value == spv::BuiltInPrimitiveId || value == spv::BuiltInPatchVertices) && (signature == "i32" || signature == "u32"))), "unsupported tessellation built-in");
|
||||
return;
|
||||
}
|
||||
if (!input && value == spv::BuiltInPrimitivePointIndicesEXT) primitiveIndices = 1u;
|
||||
if (!input && value == spv::BuiltInPrimitiveLineIndicesEXT) primitiveIndices = 2u;
|
||||
if (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT) primitiveIndices = 3u;
|
||||
Require((input && ((value == spv::BuiltInWorkgroupId || value == spv::BuiltInLocalInvocationId || value == spv::BuiltInGlobalInvocationId || value == spv::BuiltInNumWorkgroups) && signature == "u32x3")) || (input && value == spv::BuiltInLocalInvocationIndex && signature == "u32") || (!input && value == spv::BuiltInPrimitiveLineIndicesEXT && signature == "u32x2") || (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT && signature == "u32x3") || (!input && value == spv::BuiltInCullPrimitiveEXT && signature == "bool") || (!input && value == spv::BuiltInLayer && signature == "u32"), "unsupported mesh built-in");
|
||||
Require((input && ((value == spv::BuiltInWorkgroupId || value == spv::BuiltInLocalInvocationId || value == spv::BuiltInGlobalInvocationId || value == spv::BuiltInNumWorkgroups) && signature == "u32x3")) || (input && value == spv::BuiltInLocalInvocationIndex && signature == "u32") || (!input && value == spv::BuiltInPrimitivePointIndicesEXT && signature == "u32") || (!input && value == spv::BuiltInPrimitiveLineIndicesEXT && signature == "u32x2") || (!input && value == spv::BuiltInPrimitiveTriangleIndicesEXT && signature == "u32x3") || (!input && value == spv::BuiltInCullPrimitiveEXT && signature == "bool") || (!input && value == spv::BuiltInLayer && signature == "u32"), "unsupported mesh built-in");
|
||||
return;
|
||||
}
|
||||
const auto signature = Signature(type);
|
||||
@@ -458,7 +459,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
||||
mode(spv::ExecutionModeLocalSize, {paired ? config.threadsPerGroup / 2u : config.threadsPerGroup, 1, 1});
|
||||
mode(spv::ExecutionModeOutputVertices, {config.maxVertices});
|
||||
mode(spv::ExecutionModeOutputPrimitivesEXT, {config.maxPrimitives});
|
||||
mode(config.outputPrimitiveSize == 2u ? spv::ExecutionModeOutputLinesEXT : spv::ExecutionModeOutputTrianglesEXT, {});
|
||||
mode(config.outputPrimitiveSize == 1u ? spv::ExecutionModeOutputPoints : config.outputPrimitiveSize == 2u ? spv::ExecutionModeOutputLinesEXT : spv::ExecutionModeOutputTrianglesEXT, {});
|
||||
Require(module.modes.size() == 4, "unsupported mesh execution mode");
|
||||
} else if (barycentricPatches) {
|
||||
if (control) mode(spv::ExecutionModeOutputVertices, {3u});
|
||||
@@ -513,7 +514,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
|
||||
const auto length = module.constants.find(outer[3]);
|
||||
Require(length != module.constants.end() && length->second != 0, "invalid per-vertex interface array length");
|
||||
if (mesh) {
|
||||
const bool primitive = decoration.perPrimitive || module.perPrimitiveMembers.contains({outer[2], 0u}) || (decoration.builtin && (*decoration.builtin == spv::BuiltInPrimitiveLineIndicesEXT || *decoration.builtin == spv::BuiltInPrimitiveTriangleIndicesEXT || *decoration.builtin == spv::BuiltInCullPrimitiveEXT));
|
||||
const bool primitive = decoration.perPrimitive || module.perPrimitiveMembers.contains({outer[2], 0u}) || (decoration.builtin && (*decoration.builtin == spv::BuiltInPrimitivePointIndicesEXT || *decoration.builtin == spv::BuiltInPrimitiveLineIndicesEXT || *decoration.builtin == spv::BuiltInPrimitiveTriangleIndicesEXT || *decoration.builtin == spv::BuiltInCullPrimitiveEXT));
|
||||
Require(length->second == (primitive ? state.stages.mesh->maxPrimitives : state.stages.mesh->maxVertices), "mesh interface array disagrees with output limits");
|
||||
} else if (geometry) {
|
||||
Require(length->second == 3u, "geometry interface array must hold the three vertices of a triangle");
|
||||
|
||||
@@ -281,7 +281,9 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint
|
||||
const auto opValue = refMask >> 24u;
|
||||
auto reference = test;
|
||||
auto fixed = compare == 0 || compare == 7 ? 0u : face.compareMask;
|
||||
const auto convert = [&](std::uint32_t op) {
|
||||
std::uint32_t invertMask = 0;
|
||||
std::uint32_t inverted = 0;
|
||||
const auto convert = [&](std::uint32_t op, bool testFailed) {
|
||||
if (face.writeMask == 0) return VK_STENCIL_OP_KEEP;
|
||||
char detail[112];
|
||||
if (opValExport && ((op == 4 && !opValReference) || op == 5 || op == 6 || op == 8 || op == 9)) {
|
||||
@@ -304,6 +306,16 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint
|
||||
}
|
||||
const auto value = op == 2 ? 0xffu : op == 3 ? test : opValue;
|
||||
if (((reference ^ value) & fixed & face.writeMask) != 0) {
|
||||
const auto compared = test & face.compareMask;
|
||||
const bool passKnown = compare == 2 || (compare == 3 && compared == face.compareMask) || (compare == 6 && compared == 0u);
|
||||
const bool failKnown = compare == 5 || (compare == 1 && compared == 0u) || (compare == 4 && compared == face.compareMask);
|
||||
const bool testValueKnown = testFailed ? failKnown : passKnown;
|
||||
const auto flipped = (test ^ value) & face.writeMask;
|
||||
if (testValueKnown && (face.writeMask & ~face.compareMask) == 0 && (invertMask == 0 || invertMask == flipped)) {
|
||||
invertMask = flipped;
|
||||
++inverted;
|
||||
return VK_STENCIL_OP_INVERT;
|
||||
}
|
||||
std::snprintf(detail, sizeof(detail), "AGC graphics: stencil replacement 0x%02x against test value 0x%02x (masks 0x%02x/0x%02x) is unsupported", value, test, face.compareMask, face.writeMask);
|
||||
throw std::runtime_error(detail);
|
||||
}
|
||||
@@ -326,9 +338,14 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint
|
||||
throw std::runtime_error(detail);
|
||||
}
|
||||
};
|
||||
face.failOp = convert(ops & 0xfu);
|
||||
face.passOp = convert((ops >> 4u) & 0xfu);
|
||||
face.depthFailOp = convert((ops >> 8u) & 0xfu);
|
||||
face.failOp = convert(ops & 0xfu, true);
|
||||
face.passOp = convert((ops >> 4u) & 0xfu, false);
|
||||
face.depthFailOp = convert((ops >> 8u) & 0xfu, false);
|
||||
if (invertMask != 0) {
|
||||
const auto active = ((ops & 0xfu) != 0u) + (((ops >> 4u) & 0xfu) != 0u) + (((ops >> 8u) & 0xfu) != 0u);
|
||||
if (inverted != active) throw std::runtime_error("AGC graphics: a stencil replacement drawn as an inversion beside another stencil operation is unsupported");
|
||||
face.writeMask = invertMask;
|
||||
}
|
||||
face.reference = reference;
|
||||
Require(!opValReference || compare == 0 || compare == 7 || face.compareMask == 0, "a stencil compare against the test value is unsupported with a pixel shader that exports the stencil operation value");
|
||||
return face;
|
||||
@@ -1091,8 +1108,8 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
||||
const auto maxVertices = read(queue.context, 0x1ff);
|
||||
const auto verticesPerPrimitive = vertexWorkgroup ? 3u : read(queue.context, 0x2ce);
|
||||
const auto outputType = read(queue.context, 0x29b);
|
||||
validate(outputType == 2 || (outputType == 1 && !vertexWorkgroup), "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a line strip from a geometry program, is unsupported");
|
||||
const auto outputSize = outputType == 1 ? 2u : 3u;
|
||||
validate(outputType == 2 || ((outputType == 0 || outputType == 1) && !vertexWorkgroup), "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a point list or line strip from a geometry program, is unsupported");
|
||||
const auto outputSize = outputType == 0 ? 1u : outputType == 1 ? 2u : 3u;
|
||||
validate((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 5 || primitive == 6) && verticesPerPrimitive >= outputSize, "unsupported geometry input or output assembly");
|
||||
const auto inputSize = primitive == 1 ? 1u : primitive == 2 ? 2u : 3u;
|
||||
validate(vertices >= inputSize && maxVertices != 0 && (!vertexWorkgroup || maxVertices >= inputSize) && maxVertices <= 256 && verticesPerPrimitive <= 256, "invalid geometry subgroup output");
|
||||
@@ -1105,7 +1122,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) {
|
||||
validate(esgsItemSize != 0 && esgsItemSize * vertices <= 0xffffu, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
const auto amplification = fastLaunch ? read(queue.context, 0x2d3) & 0x1ffu : verticesPerPrimitive - outputSize + 1u;
|
||||
validate(amplification != 0, "fast launch subgroup amplifies to no primitives");
|
||||
const auto maxPrimitives = outputSize == 2u && !fastLaunch ? std::max(primitives * amplification, maxVertices) : primitives * amplification;
|
||||
const auto maxPrimitives = outputSize <= 2u && !fastLaunch ? std::max(primitives * amplification, maxVertices) : primitives * amplification;
|
||||
const auto threads = std::max({(groupPrimitives - 1u) * inputStep + inputSize, primitives, maxVertices, maxPrimitives});
|
||||
const auto raster = find(queue.context, 0x205);
|
||||
const auto provokingVertex = raster != queue.context.end() && (raster->second & (1u << 19u)) != 0 ? outputSize - 1u : 0u;
|
||||
@@ -1289,7 +1306,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
if (depthKeepsSampleZero && (read(cx, 0x292) & 1u) == 0) {
|
||||
const bool filled = result.topology != VK_PRIMITIVE_TOPOLOGY_POINT_LIST && result.topology != VK_PRIMITIVE_TOPOLOGY_LINE_LIST && result.topology != VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
const auto sampleInputs = read(cx, 0x1b3) & read(cx, 0x1b4) & (ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage));
|
||||
Require(filled && !result.alphaToCoverage && (PixelProgramSkipped(queue) || ((read(cx, 0x293) & PixelShaderIteratesSamples) == 0 && sampleInputs == 0)) && (read(cx, 0x203) & 0x100u) == 0 && (read(cx, 0x201) & 0x0f0c0000u) == 0, "a one-sample depth target under a multisampled rasterizer without MSAA_ENABLE is unsupported for points or lines and with alpha-to-coverage, PS_ITER_SAMPLE, a sample index or coverage input, a sample-mask export, or DB_EQAA Z interpolation or overrasterization");
|
||||
Require(filled && !result.alphaToCoverage && (PixelProgramSkipped(queue) || sampleInputs == 0) && (read(cx, 0x203) & 0x100u) == 0 && (read(cx, 0x201) & 0x0f0c0000u) == 0, "a one-sample depth target under a multisampled rasterizer without MSAA_ENABLE is unsupported for points or lines and with alpha-to-coverage, a sample index or coverage input, a sample-mask export, or DB_EQAA Z interpolation or overrasterization");
|
||||
result.samples = 1;
|
||||
result.customSampleLocations = false;
|
||||
result.sampleLocations.fill(0);
|
||||
@@ -1337,7 +1354,7 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) {
|
||||
const auto raster = read(cx, 0x205);
|
||||
// Bits 5-10 give the front/back polygon type (2 = filled triangles), which POLY_MODE (bit 3) turns on
|
||||
// explicitly; KEEP_TOGETHER_ENABLE (bit 24) only affects primitive distribution across the chip.
|
||||
const bool pointsOrLines = result.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || (result.stages.mesh.has_value() && result.stages.mesh->outputPrimitiveSize == 2u);
|
||||
const bool pointsOrLines = result.topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_LIST || result.topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || (result.stages.mesh.has_value() && result.stages.mesh->outputPrimitiveSize <= 2u);
|
||||
const auto lineOffset = (raster & 0x1800u) != 0 && !pointsOrLines ? 0x2000u : 0u;
|
||||
const auto rasterMode = raster & ~0x7u & ~0x7f8u & ~(1u << 24u) & ~0x1800u & ~lineOffset & ~(1u << 19u) & ~(1u << 16u);
|
||||
if (rasterMode != 0) throw std::runtime_error("AGC graphics: " + zeroMessage(0x205, raster, "depth bias or nonstandard rasterization"));
|
||||
@@ -1623,6 +1640,7 @@ std::array<std::uint16_t, 4> QuadSampleMasks(const State& state) {
|
||||
|
||||
ShaderRecompiler::RasterPrimitive RasterPrimitiveOf(const State& state) {
|
||||
if (state.rectList) return ShaderRecompiler::RasterPrimitive::Rectangle;
|
||||
if (state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 1u) return ShaderRecompiler::RasterPrimitive::Point;
|
||||
if (state.stages.mesh.has_value() && state.stages.mesh->outputPrimitiveSize == 2u) return ShaderRecompiler::RasterPrimitive::Line;
|
||||
if (state.stages.mesh.has_value() || state.stages.tessellation.has_value()) return ShaderRecompiler::RasterPrimitive::Triangle;
|
||||
switch (state.topology) {
|
||||
@@ -1865,6 +1883,61 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
|
||||
return color;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool drawCovers(const Registers& cx, VkExtent2D extent) {
|
||||
const auto viewportControl = read(cx, 0x206);
|
||||
if (viewportControl != 0x43fu) throw std::runtime_error(vteMessage(viewportControl));
|
||||
const auto xs = std::fabs(readFloat(cx, 0x10f));
|
||||
const auto xo = readFloat(cx, 0x110);
|
||||
const auto ys = std::fabs(readFloat(cx, 0x111));
|
||||
const auto yo = readFloat(cx, 0x112);
|
||||
VkRect2D covered{{0, 0}, {0x7fffu, 0x7fffu}};
|
||||
intersect(covered, cx, 0xc, true);
|
||||
intersect(covered, cx, 0x81, false);
|
||||
intersect(covered, cx, 0x90, false);
|
||||
if ((read(cx, 0x292) & 2u) != 0) intersect(covered, cx, 0x94, false);
|
||||
const auto width = static_cast<float>(extent.width);
|
||||
const auto height = static_cast<float>(extent.height);
|
||||
const bool viewportCovers = xo - xs <= 0.0f && xo + xs >= width && yo - ys <= 0.0f && yo + ys >= height;
|
||||
const bool scissorCovers = covered.offset.x == 0 && covered.offset.y == 0 && covered.extent.width >= extent.width && covered.extent.height >= extent.height;
|
||||
return viewportCovers && scissorCovers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::optional<DepthCopyPass> DecodeDepthCopyPass(const QueueState& queue) {
|
||||
const auto& cx = queue.context;
|
||||
const auto control = find(cx, 0x000);
|
||||
if (control == cx.end() || (control->second & 4u) == 0) return std::nullopt;
|
||||
Require((control->second & ~0xf84u) == 0, "DB depth copy combined with a stencil copy, clear, resummarize or decompress");
|
||||
Require((read(cx, 0x200) & 0xfu) == 0, "DB depth copy with depth or stencil tests");
|
||||
Require((read(cx, 0x010) & 3u) == 3u, "DB depth copy from a depth plane that is not Z_32_FLOAT");
|
||||
Require(ColorWriteMask(cx) == 1u, "DB depth copy into anything but the red channel of color target 0");
|
||||
const auto viewRegister = find(cx, 0x002);
|
||||
const auto view = viewRegister == cx.end() ? 0u : viewRegister->second;
|
||||
Require((read(cx, 0x010) & 0x000f1000u) == 0 && (view & 0x3c000000u) == 0, "DB depth copy from a mipmapped or partially resident depth plane");
|
||||
DepthCopyPass pass{};
|
||||
const auto high = find(cx, 0x01a);
|
||||
pass.depthAddress = (high == cx.end() ? 0ull : static_cast<std::uint64_t>(high->second & 0xffu) << 40u) | (static_cast<std::uint64_t>(read(cx, 0x012)) << 8u);
|
||||
const auto size = read(cx, 0x007);
|
||||
pass.extent = {(size & 0x3fffu) + 1u, ((size >> 16u) & 0x3fffu) + 1u};
|
||||
pass.samples = 1u << ((read(cx, 0x010) >> 2u) & 3u);
|
||||
if (pass.samples != 1u) pass.depthAddress &= ~std::uint64_t{65535u};
|
||||
if (const auto slice = view & 0x1fffu; slice != 0) pass.depthAddress += static_cast<std::uint64_t>(slice) * DepthSliceBytes(pass.extent, 4u);
|
||||
const auto maskLow = find(cx, 0x30e);
|
||||
const auto maskHigh = find(cx, 0x30f);
|
||||
Require((maskLow == cx.end() || maskLow->second == 0xffffffffu) && (maskHigh == cx.end() || maskHigh->second == 0xffffffffu), "DB depth copy with a sample mask is unsupported");
|
||||
pass.sample = (control->second & 0x80u) != 0 ? 0u : (control->second >> 8u) & 0xfu;
|
||||
Require(pass.sample < pass.samples, "DB depth copy of a sample the depth plane does not have");
|
||||
pass.color = DecodeColorBuffer(cx, 0);
|
||||
Require(pass.color.format == VK_FORMAT_R32_SFLOAT && pass.color.samples == 1 && pass.color.mip == 0 && pass.color.layers == 1, "DB depth copy into a color target that is not a single-sample R32 float");
|
||||
Require(pass.color.extent.width == pass.extent.width && pass.color.extent.height == pass.extent.height, "DB depth copy into a color target of another size");
|
||||
Require(pass.color.cmaskAddress == 0, "DB depth copy into a color target with CMASK fast clears is unsupported");
|
||||
Require(drawCovers(cx, pass.extent), "DB depth copy over part of the depth surface");
|
||||
return pass;
|
||||
}
|
||||
|
||||
std::optional<ColorMetadataPass> DecodeColorMetadataPass(const QueueState& queue) {
|
||||
const auto& cx = queue.context;
|
||||
const auto control = find(cx, 0x202);
|
||||
@@ -1896,24 +1969,7 @@ std::optional<ColorMetadataPass> DecodeColorMetadataPass(const QueueState& queue
|
||||
const auto samples = resolve && rasterizerSamples && multisampleRaster ? decodeSamples(cx) : 1u;
|
||||
if (!resolve) zero(cx, 0x2f8, ~0u, "multisampling or coverage conversion");
|
||||
zero(cx, 0x80, ~0u, "window offset");
|
||||
const auto viewportControl = read(cx, 0x206);
|
||||
if (viewportControl != 0x43fu) throw std::runtime_error(vteMessage(viewportControl));
|
||||
const auto xs = std::fabs(readFloat(cx, 0x10f));
|
||||
const auto xo = readFloat(cx, 0x110);
|
||||
const auto ys = std::fabs(readFloat(cx, 0x111));
|
||||
const auto yo = readFloat(cx, 0x112);
|
||||
VkRect2D covered{{0, 0}, {0x7fffu, 0x7fffu}};
|
||||
intersect(covered, cx, 0xc, true);
|
||||
intersect(covered, cx, 0x81, false);
|
||||
intersect(covered, cx, 0x90, false);
|
||||
if ((read(cx, 0x292) & 2u) != 0) intersect(covered, cx, 0x94, false);
|
||||
const auto requireCovered = [&](const ColorTarget& target) {
|
||||
const auto width = static_cast<float>(target.extent.width);
|
||||
const auto height = static_cast<float>(target.extent.height);
|
||||
const bool viewportCovers = xo - xs <= 0.0f && xo + xs >= width && yo - ys <= 0.0f && yo + ys >= height;
|
||||
const bool scissorCovers = covered.offset.x == 0 && covered.offset.y == 0 && covered.extent.width >= target.extent.width && covered.extent.height >= target.extent.height;
|
||||
Require(viewportCovers && scissorCovers, "CB metadata pass over part of a color target");
|
||||
};
|
||||
const auto requireCovered = [&](const ColorTarget& target) { Require(drawCovers(cx, target.extent), "CB metadata pass over part of a color target"); };
|
||||
if (resolve) {
|
||||
const auto source = DecodeColorBuffer(cx, 0);
|
||||
const auto destination = DecodeColorBuffer(cx, 1);
|
||||
|
||||
@@ -2036,11 +2036,31 @@ LookupOutcomes& ThreadLookupOutcomes() {
|
||||
}
|
||||
|
||||
bool StorageTexture::Refresh() {
|
||||
return refresh(0, trackedLayers);
|
||||
}
|
||||
|
||||
bool StorageTexture::RefreshLayers(std::uint32_t first, std::uint32_t count) {
|
||||
if (count == 0 || geometry.thick || geometry.imageLayers <= 1 || first >= geometry.imageLayers || count > geometry.imageLayers - first || geometry.layerBytes == 0 || trackedLayerBytes == 0 || descriptor.dccAddress != 0) return Refresh();
|
||||
const auto begin = geometry.GuestLayerOffset(first);
|
||||
const auto end = geometry.GuestLayerOffset(first + count - 1) + geometry.layerBytes;
|
||||
const auto firstLayer = static_cast<std::uint32_t>(begin / trackedLayerBytes);
|
||||
const auto lastLayer = static_cast<std::uint32_t>(std::min<std::uint64_t>((end + trackedLayerBytes - 1) / trackedLayerBytes, trackedLayers));
|
||||
if (firstLayer >= lastLayer || (firstLayer == 0 && lastLayer == trackedLayers)) return Refresh();
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
if (layerPending[layer] && (layer < firstLayer || layer >= lastLayer)) return Refresh();
|
||||
}
|
||||
if (pendingAlias() != nullptr) return Refresh();
|
||||
return refresh(firstLayer, lastLayer);
|
||||
}
|
||||
|
||||
bool StorageTexture::refresh(std::uint32_t firstLayer, std::uint32_t lastLayer) {
|
||||
const bool profile = LookupOutcomes::Profiled();
|
||||
auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
const bool scoped = firstLayer != 0 || lastLayer != trackedLayers;
|
||||
CompletePendingCpuWrites(context, descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
if (descriptor.dccAddress != 0) CompletePendingCpuWrites(context, descriptor.dccAddress, DccKeyBytes(guestBytes));
|
||||
if (refreshProved()) {
|
||||
layerBehind.clear();
|
||||
NoteProved();
|
||||
refreshesProved.fetch_add(1, std::memory_order_relaxed);
|
||||
if (profile) LookupOutcomes::Add(LookupOutcomes::RefreshProved, start);
|
||||
@@ -2062,7 +2082,7 @@ bool StorageTexture::Refresh() {
|
||||
// generation, no results of this image's own there) or dead to its own write-back (tracked and
|
||||
// stamped); an untracked unit is stored whole by that write-back, and a unit pending in both
|
||||
// images takes the old order (store, then this image re-uploads it).
|
||||
auto alias = pendingAlias();
|
||||
auto alias = scoped ? std::shared_ptr<StorageTexture>{} : pendingAlias();
|
||||
if (alias != nullptr && !(ProvedClearKeys(descriptor, guestBytes, keyProof) == DccKeys::Uncompressed && HostImportFor(context, descriptor.baseAddress, static_cast<std::size_t>(guestBytes)) != nullptr)) alias = nullptr;
|
||||
if (alias != nullptr) {
|
||||
std::vector<std::uint8_t> aliasStamped(trackedLayers);
|
||||
@@ -2075,22 +2095,35 @@ bool StorageTexture::Refresh() {
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto scopeBegin = scoped ? layerBegin(firstLayer) : descriptor.baseAddress;
|
||||
const auto scopeBytes = scoped ? static_cast<std::size_t>(layerBegin(lastLayer - 1) + layerBytes(lastLayer - 1) - scopeBegin) : static_cast<std::size_t>(guestBytes);
|
||||
// No publish: the upload below reads the stored units from the unit shadow itself.
|
||||
handedOver.clear();
|
||||
handedUnits.clear();
|
||||
const auto handOverKeys = alias == nullptr ? clearHandOver() : std::nullopt;
|
||||
const bool flushed = !handOverKeys.has_value() && FlushPending(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), alias.get(), "storage refresh", PublishScope::None);
|
||||
const auto handOverKeys = alias == nullptr && !scoped ? clearHandOver() : std::nullopt;
|
||||
const bool flushed = !handOverKeys.has_value() && FlushPending(scopeBegin, scopeBytes, alias.get(), "storage refresh", PublishScope::None);
|
||||
if (profile && flushed) start = LookupOutcomes::Add(LookupOutcomes::PendingFlush, start);
|
||||
// `original` holds the guest bytes the image was last uploaded from or written back as; while the
|
||||
// guest memory and the DCC keys still match, the image content is current. Pages nobody wrote
|
||||
// since `generation` need no comparison.
|
||||
const auto current = GuestMemory::CollectWrites(descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
const auto scopedCurrent = current != 0 || !scoped ? current : GuestMemory::CollectWrites(scopeBegin, scopeBytes);
|
||||
std::vector<std::uint64_t> collected;
|
||||
if (scopedCurrent == 0) {
|
||||
collected.resize(trackedLayers);
|
||||
for (auto layer = firstLayer; layer < lastLayer; ++layer) collected[layer] = collectedLayerGeneration(layer, 0);
|
||||
}
|
||||
const auto currentFor = [&](std::uint32_t layer) {
|
||||
if (scoped && (layer < firstLayer || layer >= lastLayer)) return current;
|
||||
return scopedCurrent != 0 ? scopedCurrent : collected[layer];
|
||||
};
|
||||
// Per tracked layer: changed since its generation (a keys change makes every layer stale).
|
||||
std::vector<bool> changed(trackedLayers, false);
|
||||
// Per 64 KiB block of the surface (blockGenerations): stamped since its layer's generation, and
|
||||
// by a CPU store; scanned once when anything changed.
|
||||
std::vector<std::uint64_t> generations;
|
||||
std::vector<std::uint8_t> stampedBlocks, cpuBlocks;
|
||||
std::vector<bool> outside;
|
||||
// Units taken from the alias's image instead of guest memory.
|
||||
std::vector<bool> borrowed;
|
||||
bool anyBorrowed = false;
|
||||
@@ -2106,6 +2139,7 @@ bool StorageTexture::Refresh() {
|
||||
const auto equalsOriginal = [&] {
|
||||
// Named for the [hooksync] attribution: the compare goes through the flush hook.
|
||||
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::TextureCompare);
|
||||
if (scoped) return GuestMemory::EqualsCommitted(scopeBegin, std::span<const std::byte>(original).subspan(static_cast<std::size_t>(scopeBegin - descriptor.baseAddress), scopeBytes));
|
||||
return GuestMemory::EqualsCommitted(descriptor.baseAddress, original);
|
||||
};
|
||||
const auto scanBlocks = [&] {
|
||||
@@ -2114,13 +2148,19 @@ bool StorageTexture::Refresh() {
|
||||
cpuBlocks.assign(generations.size(), 0);
|
||||
tracked = GuestMemory::ChangedBlocks(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), generations, stampedBlocks, cpuBlocks);
|
||||
if (!tracked) awaitStoredSave(nullptr);
|
||||
compared = !tracked && compareUntracked(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), stampedBlocks, true);
|
||||
const auto firstBlock = static_cast<std::size_t>(scopeBegin / 65536 - descriptor.baseAddress / 65536);
|
||||
const auto blocks = static_cast<std::size_t>((scopeBegin + scopeBytes - 1) / 65536 - scopeBegin / 65536 + 1);
|
||||
compared = !tracked && (scoped ? compareUntracked(scopeBegin, scopeBytes, std::span(stampedBlocks).subspan(firstBlock, blocks), true) : compareUntracked(descriptor.baseAddress, static_cast<std::size_t>(guestBytes), stampedBlocks, true));
|
||||
if (compared) {
|
||||
for (const auto& [from, unitGeneration] : keptAtUnwatch) {
|
||||
const auto unit = static_cast<std::uint32_t>((from - descriptor.baseAddress) / trackedLayerBytes);
|
||||
if (keptAtUnwatchHolds(from, unit)) stampedBlocks[static_cast<std::size_t>(from / 65536 - descriptor.baseAddress / 65536)] = GuestMemory::BlockWritten;
|
||||
}
|
||||
cpuBlocks = stampedBlocks;
|
||||
if (scoped) {
|
||||
std::fill(stampedBlocks.begin(), stampedBlocks.begin() + static_cast<std::ptrdiff_t>(firstBlock), GuestMemory::BlockWritten);
|
||||
std::fill(stampedBlocks.begin() + static_cast<std::ptrdiff_t>(firstBlock + blocks), stampedBlocks.end(), GuestMemory::BlockWritten);
|
||||
}
|
||||
}
|
||||
};
|
||||
const auto keysStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
@@ -2139,7 +2179,7 @@ bool StorageTexture::Refresh() {
|
||||
scanBlocks();
|
||||
for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) {
|
||||
if (stampedBlocks[unit] == 0) {
|
||||
layerGeneration[unit] = current;
|
||||
layerGeneration[unit] = currentFor(unit);
|
||||
continue;
|
||||
}
|
||||
changed[unit] = true;
|
||||
@@ -2165,6 +2205,15 @@ bool StorageTexture::Refresh() {
|
||||
}
|
||||
unchanged = std::none_of(changed.begin(), changed.end(), [](bool value) { return value; });
|
||||
}
|
||||
if (scoped && !keysChanged) {
|
||||
outside.assign(trackedLayers, false);
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
if ((layer >= firstLayer && layer < lastLayer) || (!changed[layer] && !compared)) continue;
|
||||
outside[layer] = true;
|
||||
changed[layer] = false;
|
||||
}
|
||||
unchanged = std::none_of(changed.begin(), changed.end(), [](bool value) { return value; });
|
||||
}
|
||||
for (std::size_t k = 0; k < cpuBlocks.size(); ++k) {
|
||||
if (cpuBlocks[k] != 0 && (compared || (tracked && generations[k] != 0))) cpuWrote = true;
|
||||
}
|
||||
@@ -2175,12 +2224,11 @@ bool StorageTexture::Refresh() {
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
if (changed[layer] && layerPending[layer]) pendingChanged = true;
|
||||
}
|
||||
if (!unchanged && !keysChanged && !pendingChanged && originalHolds(descriptor.baseAddress, static_cast<std::size_t>(guestBytes)) && equalsOriginal()) {
|
||||
if (!unchanged && !keysChanged && !pendingChanged && (scoped ? originalHolds(scopeBegin, scopeBytes) : originalHolds(descriptor.baseAddress, static_cast<std::size_t>(guestBytes))) && equalsOriginal()) {
|
||||
unchanged = true;
|
||||
stamped = false;
|
||||
if (!scoped) outside.clear();
|
||||
}
|
||||
// Only the direct path uploads the selected layers alone (see upload); the others replace
|
||||
// the whole image, so every pending layer's results are stored first.
|
||||
direct = keys == DccKeys::Uncompressed && HostImportFor(context, descriptor.baseAddress, static_cast<std::size_t>(guestBytes)) != nullptr;
|
||||
if (alias != nullptr && direct) {
|
||||
// The alias's pending units that nothing stamped since its generation (a CPU store or a
|
||||
@@ -2205,11 +2253,16 @@ bool StorageTexture::Refresh() {
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool outsideChanged = std::any_of(outside.begin(), outside.end(), [](bool value) { return value; });
|
||||
if (scoped) noteLayersBehind(firstLayer, lastLayer, outside);
|
||||
else layerBehind.clear();
|
||||
if (unchanged) {
|
||||
++Profile().storageReused;
|
||||
layerGeneration.assign(trackedLayers, current);
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
if (!outsideChanged || !outside[layer]) layerGeneration[layer] = currentFor(layer);
|
||||
}
|
||||
refreshGeneration();
|
||||
takeRefreshProof(alias != nullptr);
|
||||
if (layerBehind.empty()) takeRefreshProof(alias != nullptr);
|
||||
if (profile) LookupOutcomes::Add(stamped ? LookupOutcomes::RefreshUnchanged : LookupOutcomes::RefreshCompared, start);
|
||||
return true;
|
||||
}
|
||||
@@ -2259,6 +2312,7 @@ bool StorageTexture::Refresh() {
|
||||
}
|
||||
std::fprintf(stderr, "[dcc-keys] refresh 0x%llx+0x%llx keys %s -> %s (dirty %d, %zu pending units, %zu cpu-stamped units%s)\n", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), DccKeysName(uploadedKeys), DccKeysName(keys), dirty ? 1 : 0, pendingUnits, stampedUnits, keyFlip ? ", flip keeps unstamped results" : "");
|
||||
}
|
||||
const bool selectedCpu = !direct && keys == DccKeys::Uncompressed && !cpuUploadLayers(changed).empty();
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
if (!layerPending[layer]) continue;
|
||||
if (keyFlip && layerGeneration[layer] != 0 && stampedBlocks[layer] != GuestMemory::BlockWritten) {
|
||||
@@ -2277,7 +2331,7 @@ bool StorageTexture::Refresh() {
|
||||
dropped = true;
|
||||
continue;
|
||||
}
|
||||
} else if (direct) {
|
||||
} else if (direct || selectedCpu) {
|
||||
continue;
|
||||
}
|
||||
stored[layer] = true;
|
||||
@@ -2299,7 +2353,7 @@ bool StorageTexture::Refresh() {
|
||||
uploadReason = "alias";
|
||||
borrowUnits(*alias, borrowed);
|
||||
for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) {
|
||||
if (borrowed[unit]) layerGeneration[unit] = current;
|
||||
if (borrowed[unit]) layerGeneration[unit] = currentFor(unit);
|
||||
}
|
||||
refreshGeneration();
|
||||
}
|
||||
@@ -2412,7 +2466,7 @@ void traceKeyStore(const char* path, const GuestTextureResource& descriptor, std
|
||||
|
||||
void StorageTexture::captureGuestBytes(const std::vector<bool>* layers) {
|
||||
original.resize(static_cast<std::size_t>(guestBytes));
|
||||
comparedGuestBytes = {};
|
||||
comparedRanges = {};
|
||||
std::vector<bool> selected(trackedLayers, true);
|
||||
if (layers != nullptr) selected = *layers;
|
||||
for (const auto& [begin, end] : unitRuns(selected)) GuestMemory::FlushGpuWrites(descriptor.baseAddress + begin, static_cast<std::size_t>(end - begin));
|
||||
@@ -2449,11 +2503,10 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes,
|
||||
Require(changed.size() == (end - 1) / blockBytes - first + 1, "texture comparison block count differs");
|
||||
const auto* recorder = Recorder::Active();
|
||||
const auto epoch = GuestMemory::CollectEpoch();
|
||||
const bool whole = address == descriptor.baseAddress && bytes == guestBytes;
|
||||
const auto hint = memoize && epoch != 0 && whole ? GuestMemory::UntrackedHint(address, bytes) : 0;
|
||||
const auto hint = memoize && epoch != 0 ? GuestMemory::UntrackedHint(address, bytes) : 0;
|
||||
const std::array<std::uint64_t, 5> stamp{hint != 0, hint != 0 ? hint : epoch, GuestMemory::NewestStamp(address, bytes), GuestMemory::ForgetSerial(), recorder != nullptr ? recorder->NewestWriteNote(address, bytes) : 0};
|
||||
const bool cacheable = memoize && epoch != 0 && (stamp[3] & 1u) == 0 && whole;
|
||||
if (cacheable && comparedGuestBytes == stamp) {
|
||||
const bool cacheable = memoize && epoch != 0 && (stamp[3] & 1u) == 0;
|
||||
if (cacheable && std::any_of(comparedRanges.begin(), comparedRanges.end(), [&](const ComparedRange& entry) { return entry.address == address && entry.bytes == bytes && entry.stamp == stamp; })) {
|
||||
std::fill(changed.begin(), changed.end(), GuestMemory::BlockUnchanged);
|
||||
return true;
|
||||
}
|
||||
@@ -2461,10 +2514,14 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes,
|
||||
const auto saved = std::span(original).subspan(static_cast<std::size_t>(address - descriptor.baseAddress), bytes);
|
||||
if (GuestMemory::EqualsCommittedUnsynced(address, saved)) {
|
||||
std::fill(changed.begin(), changed.end(), GuestMemory::BlockUnchanged);
|
||||
if (cacheable) comparedGuestBytes = stamp;
|
||||
if (cacheable) {
|
||||
auto found = std::find_if(comparedRanges.begin(), comparedRanges.end(), [&](const ComparedRange& entry) { return entry.address == address && entry.bytes == bytes; });
|
||||
if (found == comparedRanges.end()) found = comparedRanges.begin() + nextComparedRange++ % comparedRanges.size();
|
||||
*found = {address, bytes, stamp};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
comparedGuestBytes = {};
|
||||
comparedRanges = {};
|
||||
for (std::size_t index = 0; index < changed.size(); ++index) {
|
||||
const auto begin = std::max(address, (first + index) * blockBytes);
|
||||
const auto stop = std::min(end, (first + index + 1) * blockBytes);
|
||||
@@ -2476,6 +2533,25 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes,
|
||||
|
||||
constexpr std::uint64_t GenerationBaselineBytes = 1ull << 20;
|
||||
|
||||
std::vector<bool> StorageTexture::cpuUploadLayers(const std::vector<bool>& units) const {
|
||||
if (descriptor.dimension != TextureDimension::k2DArray || descriptor.mipCount != 1 || descriptor.dccAddress != 0 || IsBlockCompressed(descriptor.format) || geometry.thick || arrayLayers <= 1 || geometry.imageLayers != arrayLayers || trackedLayerBytes == 0 || units.size() != trackedLayers) return {};
|
||||
std::vector<bool> selected(arrayLayers, false);
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
const auto begin = geometry.GuestLayerOffset(layer);
|
||||
const auto end = begin + geometry.layerBytes;
|
||||
if (end <= begin || end > guestBytes || begin % trackedLayerBytes != 0 || (end % trackedLayerBytes != 0 && end != guestBytes)) return {};
|
||||
const auto first = static_cast<std::uint32_t>(begin / trackedLayerBytes);
|
||||
const auto last = static_cast<std::uint32_t>((end + trackedLayerBytes - 1) / trackedLayerBytes);
|
||||
if (first >= last || last > trackedLayers) return {};
|
||||
selected[layer] = units[first];
|
||||
for (auto unit = first + 1; unit < last; ++unit) {
|
||||
if (units[unit] != selected[layer]) return {};
|
||||
}
|
||||
}
|
||||
if (std::none_of(selected.begin(), selected.end(), [](bool value) { return value; }) || std::all_of(selected.begin(), selected.end(), [](bool value) { return value; })) return {};
|
||||
return selected;
|
||||
}
|
||||
|
||||
void StorageTexture::upload(const std::vector<bool>* layers) {
|
||||
const bool profile = LookupOutcomes::Profiled();
|
||||
const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
|
||||
@@ -2484,8 +2560,6 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
||||
CaptureTrace::Log("upload image=%llx bytes=%llu generation=%llu reason=%s partial=%d", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(generation), uploadReason, layers != nullptr);
|
||||
const auto elementBytes = BytesPerElement(descriptor.format);
|
||||
const auto linearBytes = sliceLinearBytes * arrayLayers;
|
||||
// A layer selection applies to the direct path alone (the others upload everything); it names
|
||||
// array layers, which the tracked layers are when there are several.
|
||||
if (layers != nullptr && ((!blockUnits && trackedLayers != arrayLayers) || std::all_of(layers->begin(), layers->end(), [](bool selected) { return selected; }))) layers = nullptr;
|
||||
const auto now = GuestMemory::CollectWrites(descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
const auto stampLayers = [&](bool selectedOnly) {
|
||||
@@ -2497,7 +2571,7 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
||||
}
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
if (!whole && !(*layers)[layer]) continue;
|
||||
layerGeneration[layer] = now;
|
||||
layerGeneration[layer] = collectedLayerGeneration(layer, now);
|
||||
if (whole || generationBaseline.size() != guestBytes) continue;
|
||||
const auto offset = static_cast<std::size_t>(layerBegin(layer) - descriptor.baseAddress);
|
||||
GuestMemory::ReadCommitted(layerBegin(layer), std::span<std::byte>(generationBaseline).subspan(offset, static_cast<std::size_t>(layerBytes(layer))));
|
||||
@@ -2692,15 +2766,36 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
||||
if (profile) LookupOutcomes::Add(LookupOutcomes::UploadDirect, start);
|
||||
return;
|
||||
}
|
||||
layerOriginal.assign(trackedLayers, true);
|
||||
cancelStoredSaves();
|
||||
forgetBorrowed(0, trackedLayers);
|
||||
stampLayers(false);
|
||||
original.resize(static_cast<std::size_t>(guestBytes));
|
||||
GuestMemory::ReadCommitted(descriptor.baseAddress, original);
|
||||
const auto cpuLayers = uploadedKeys == DccKeys::Uncompressed && layers != nullptr ? cpuUploadLayers(*layers) : std::vector<bool>{};
|
||||
const bool selectedCpu = !cpuLayers.empty();
|
||||
std::uint64_t uploadedBytes = guestBytes;
|
||||
if (selectedCpu) {
|
||||
captureGuestBytes(layers);
|
||||
for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) {
|
||||
if ((*layers)[unit]) forgetBorrowed(unit, 1);
|
||||
}
|
||||
stampLayers(true);
|
||||
uploadedBytes = 0;
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
if (cpuLayers[layer]) uploadedBytes += geometry.layerBytes;
|
||||
}
|
||||
} else {
|
||||
layerOriginal.assign(trackedLayers, true);
|
||||
cancelStoredSaves();
|
||||
forgetBorrowed(0, trackedLayers);
|
||||
stampLayers(false);
|
||||
original.resize(static_cast<std::size_t>(guestBytes));
|
||||
GuestMemory::ReadCommitted(descriptor.baseAddress, original);
|
||||
}
|
||||
{
|
||||
Buffer staging(context, original.size(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
if (uploadedKeys == DccKeys::Uncompressed) std::memcpy(staging.Bytes().data(), original.data(), original.size());
|
||||
if (selectedCpu) {
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
if (!cpuLayers[layer]) continue;
|
||||
const auto offset = geometry.GuestLayerOffset(layer);
|
||||
std::memcpy(staging.Bytes().data() + offset, original.data() + offset, static_cast<std::size_t>(geometry.layerBytes));
|
||||
}
|
||||
} else if (uploadedKeys == DccKeys::Uncompressed) std::memcpy(staging.Bytes().data(), original.data(), original.size());
|
||||
else ReadTextureSurface(descriptor, uploadedKeys, staging.Bytes().first(original.size()));
|
||||
DeviceBuffer tiled(context, original.size(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
||||
DeviceBuffer linear(context, static_cast<std::size_t>(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
@@ -2708,9 +2803,16 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
||||
CommandBatch batch(context);
|
||||
const auto commands = batch.Handle();
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
CopyBuffer(context, commands, staging.Handle(), 0, tiled.Handle(), 0, original.size());
|
||||
if (selectedCpu) {
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
if (!cpuLayers[layer]) continue;
|
||||
const auto offset = geometry.GuestLayerOffset(layer);
|
||||
CopyBuffer(context, commands, staging.Handle(), offset, tiled.Handle(), offset, geometry.layerBytes);
|
||||
}
|
||||
} else CopyBuffer(context, commands, staging.Handle(), 0, tiled.Handle(), 0, original.size());
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) {
|
||||
if (selectedCpu && !cpuLayers[layer]) continue;
|
||||
for (std::uint32_t level = 0; level < mips.size(); ++level) {
|
||||
if (!geometry.HasLayer(level, layer)) continue;
|
||||
const auto& mip = mips[level];
|
||||
@@ -2719,15 +2821,16 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
||||
}
|
||||
const auto linearRead = WholeBufferBarrier(linear.Handle(), VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
|
||||
VkImageMemoryBarrier toTransfer{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
toTransfer.srcAccessMask = selectedCpu ? VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : 0u;
|
||||
toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
toTransfer.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
toTransfer.oldLayout = selectedCpu ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
toTransfer.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransfer.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
toTransfer.image = image;
|
||||
toTransfer.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, descriptor.mipCount, 0, geometry.imageLayers};
|
||||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &linearRead, 1, &toTransfer);
|
||||
const auto regions = CopyRegions();
|
||||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | (selectedCpu ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT : 0u), VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &linearRead, 1, &toTransfer);
|
||||
const auto regions = CopyRegions(selectedCpu ? &cpuLayers : nullptr);
|
||||
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, linear.Handle(), image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<std::uint32_t>(regions.size()), regions.data());
|
||||
VkImageMemoryBarrier toGeneral = toTransfer;
|
||||
toGeneral.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
@@ -2739,7 +2842,7 @@ void StorageTexture::upload(const std::vector<bool>* layers) {
|
||||
batch.SubmitAndWait();
|
||||
APS5_LOG_CHARS_OUT("StorageTexture upload done");
|
||||
}
|
||||
countStorageUpload(2, guestBytes);
|
||||
countStorageUpload(2, uploadedBytes);
|
||||
++version;
|
||||
if (profile) LookupOutcomes::Add(LookupOutcomes::UploadCpu, start);
|
||||
}
|
||||
@@ -2837,6 +2940,16 @@ std::vector<StorageTexture::SliceWindow> StorageTexture::sliceWindows(std::span<
|
||||
return windows;
|
||||
}
|
||||
|
||||
std::vector<VkRect2D> StorageTexture::UnitRects(const std::vector<bool>& units) const {
|
||||
std::vector<VkRect2D> rects;
|
||||
if (units.size() != trackedLayers) return rects;
|
||||
const auto runs = unitRuns(units);
|
||||
for (const auto& window : sliceWindows(runs)) {
|
||||
for (const auto& region : window.regions) rects.push_back({{region.imageOffset.x, region.imageOffset.y}, {region.imageExtent.width, region.imageExtent.height}});
|
||||
}
|
||||
return rects;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> StorageTexture::uncoveredBytes(std::span<const std::pair<std::uint64_t, std::uint64_t>> runs) const {
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> covered;
|
||||
if (!geometry.thick) {
|
||||
@@ -3568,6 +3681,51 @@ void StorageTexture::refreshGeneration() {
|
||||
generation = *std::min_element(layerGeneration.begin(), layerGeneration.end());
|
||||
}
|
||||
|
||||
std::uint64_t StorageTexture::GenerationFor(std::uint64_t address, std::uint64_t bytes) const {
|
||||
if (layerBehind.empty() && generation != 0) return generation;
|
||||
std::optional<std::uint64_t> oldest;
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
const auto begin = layerBegin(layer);
|
||||
if (address >= begin + layerBytes(layer) || begin >= address + bytes) continue;
|
||||
if (!layerBehind.empty() && layerBehind[layer]) return 0;
|
||||
oldest = std::min(oldest.value_or(layerGeneration[layer]), layerGeneration[layer]);
|
||||
}
|
||||
return oldest.value_or(generation);
|
||||
}
|
||||
|
||||
std::uint64_t StorageTexture::collectedLayerGeneration(std::uint32_t layer, std::uint64_t whole, bool uncached) const {
|
||||
if (whole != 0 || trackedLayers <= 1) return whole;
|
||||
const auto begin = layerBegin(layer);
|
||||
const auto bytes = static_cast<std::size_t>(layerBytes(layer));
|
||||
return uncached ? GuestMemory::CollectWritesUncached(begin, bytes) : GuestMemory::CollectWrites(begin, bytes);
|
||||
}
|
||||
|
||||
void StorageTexture::noteLayersBehind(std::uint32_t firstLayer, std::uint32_t lastLayer, const std::vector<bool>& outside) {
|
||||
if (layerBehind.size() != trackedLayers) layerBehind.assign(trackedLayers, false);
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
if (layer >= firstLayer && layer < lastLayer) layerBehind[layer] = false;
|
||||
else if (layer < outside.size() && outside[layer]) layerBehind[layer] = true;
|
||||
}
|
||||
const auto layersEnd = layerBegin(lastLayer - 1) + layerBytes(lastLayer - 1);
|
||||
const std::array<std::pair<std::uint64_t, std::uint64_t>, 2> ranges{{{descriptor.baseAddress, layerBegin(firstLayer)}, {layersEnd, descriptor.baseAddress + guestBytes}}};
|
||||
for (const auto& [from, to] : ranges) {
|
||||
if (to <= from) continue;
|
||||
for (const auto& other : overlappingPending(from, static_cast<std::size_t>(to - from))) {
|
||||
const bool whole = other->descriptor.dccAddress != 0 && other->uploadedKeys != DccKeys::Uncompressed;
|
||||
for (std::uint32_t unit = 0; unit < other->trackedLayers; ++unit) {
|
||||
if (!whole && !other->layerPending[unit]) continue;
|
||||
const auto begin = std::max(from, other->layerBegin(unit));
|
||||
const auto end = std::min(to, other->layerBegin(unit) + other->layerBytes(unit));
|
||||
if (end <= begin) continue;
|
||||
const auto first = static_cast<std::uint32_t>((begin - descriptor.baseAddress) / trackedLayerBytes);
|
||||
const auto last = static_cast<std::uint32_t>(std::min<std::uint64_t>((end - descriptor.baseAddress + trackedLayerBytes - 1) / trackedLayerBytes, trackedLayers));
|
||||
for (auto layer = first; layer < last; ++layer) layerBehind[layer] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (std::none_of(layerBehind.begin(), layerBehind.end(), [](bool behind) { return behind; })) layerBehind.clear();
|
||||
}
|
||||
|
||||
void StorageTexture::markLayersPending(std::uint32_t first, std::uint32_t count) {
|
||||
CaptureTrace::Log("image-write image=%llx first=%u count=%u generation=%llu", static_cast<unsigned long long>(descriptor.baseAddress), first, count, static_cast<unsigned long long>(generation));
|
||||
++writes;
|
||||
@@ -3674,6 +3832,18 @@ bool StorageTexture::pendingUnitInside(std::uint64_t address, std::size_t bytes)
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void waitForIdlePublish(const char* reason) {
|
||||
if (reason == nullptr || std::strcmp(reason, "idle") != 0 || Recorder::InCompletion()) return;
|
||||
if (auto* recorder = Recorder::Active()) {
|
||||
Recorder::CountSync(0);
|
||||
recorder->Sync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except, const char* reason, PublishScope scope, bool* published) {
|
||||
static_cast<void>(FlushDepthPlanes(address, bytes));
|
||||
if (published != nullptr) *published = false;
|
||||
@@ -3767,6 +3937,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons
|
||||
// import, so they are published under the same hold, after every store.
|
||||
if (scope != PublishScope::None) {
|
||||
const auto units = PublishShadow(address, bytes, scope, PublishReasonFor(reason));
|
||||
if (units != 0) waitForIdlePublish(reason);
|
||||
if (published != nullptr) *published = units != 0;
|
||||
}
|
||||
return true;
|
||||
@@ -3831,7 +4002,7 @@ void StorageTexture::saveBeforeUnwatch() {
|
||||
const auto runs = unitRuns(units);
|
||||
if (runs.empty()) return;
|
||||
original.resize(static_cast<std::size_t>(guestBytes));
|
||||
comparedGuestBytes = {};
|
||||
comparedRanges = {};
|
||||
for (const auto& [begin, end] : runs) GuestMemory::ReadCommittedUnsynced(descriptor.baseAddress + begin, std::span(original).subspan(static_cast<std::size_t>(begin), static_cast<std::size_t>(end - begin)));
|
||||
for (const auto& [begin, end] : runs) GuestMemory::CollectWritesUncached(descriptor.baseAddress + begin, static_cast<std::size_t>(end - begin));
|
||||
constexpr std::uint64_t block = 65536;
|
||||
@@ -3892,7 +4063,7 @@ void StorageTexture::applyStoredSave(std::uint64_t ticket, const std::vector<std
|
||||
for (const auto unit : units) {
|
||||
if (savedTicket[unit] != ticket) continue;
|
||||
if (original.size() != guestBytes) original.resize(static_cast<std::size_t>(guestBytes));
|
||||
comparedGuestBytes = {};
|
||||
comparedRanges = {};
|
||||
const auto offset = static_cast<std::size_t>(layerBegin(unit) - descriptor.baseAddress);
|
||||
std::memcpy(original.data() + offset, stored.data() + offset, static_cast<std::size_t>(layerBytes(unit)));
|
||||
layerOriginal[unit] = true;
|
||||
@@ -3932,7 +4103,9 @@ void StorageTexture::awaitStoredSave(const std::vector<bool>* layers) {
|
||||
bool StorageTexture::PublishShadowsOnly(std::uint64_t address, std::size_t bytes, PublishScope scope, const char* reason) {
|
||||
if (scope == PublishScope::None || !AnyShadowedOverlaps(address, bytes)) return false;
|
||||
std::lock_guard gpu(GuestMemory::GpuMutex());
|
||||
return PublishShadow(address, bytes, scope, PublishReasonFor(reason)) != 0;
|
||||
if (PublishShadow(address, bytes, scope, PublishReasonFor(reason)) == 0) return false;
|
||||
waitForIdlePublish(reason);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -4529,7 +4702,8 @@ bool StorageTexture::clearByKeysFill(DccKeys keys, std::uint8_t key) {
|
||||
filledKeys = DccKeys::Uncompressed;
|
||||
keyProof = {};
|
||||
forgetBorrowed(0, trackedLayers);
|
||||
layerGeneration.assign(trackedLayers, GuestMemory::CollectWrites(descriptor.baseAddress, static_cast<std::size_t>(guestBytes)));
|
||||
const auto now = GuestMemory::CollectWrites(descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) layerGeneration[layer] = collectedLayerGeneration(layer, now);
|
||||
refreshGeneration();
|
||||
++version;
|
||||
if (traceKeys) std::fprintf(stderr, "[dcc-keys] key fill %s clears 0x%llx+0x%llx at once (keys 0x%llx, %zu pending units dropped, dirty %d)\n", DccKeysName(keys), static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(descriptor.dccAddress), droppedUnits, wasDirty ? 1 : 0);
|
||||
@@ -4690,7 +4864,8 @@ bool StorageTexture::FillClear(std::span<const std::uint32_t, 4> pattern, std::u
|
||||
return true;
|
||||
}
|
||||
if (!watched) captureGuestBytes(nullptr);
|
||||
layerGeneration.assign(trackedLayers, GuestMemory::CollectWritesUncached(descriptor.baseAddress, static_cast<std::size_t>(guestBytes)));
|
||||
const auto now = GuestMemory::CollectWritesUncached(descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) layerGeneration[layer] = collectedLayerGeneration(layer, now, true);
|
||||
refreshGeneration();
|
||||
forgetBorrowed(0, trackedLayers);
|
||||
MarkDirty();
|
||||
@@ -4808,12 +4983,45 @@ void StorageTexture::MarkWholeWritten() {
|
||||
captureGuestBytes(nullptr);
|
||||
}
|
||||
uploadedKeys = DccKeys::Uncompressed;
|
||||
layerGeneration.assign(trackedLayers, GuestMemory::CollectWritesUncached(descriptor.baseAddress, static_cast<std::size_t>(guestBytes)));
|
||||
const auto now = GuestMemory::CollectWritesUncached(descriptor.baseAddress, static_cast<std::size_t>(guestBytes));
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) layerGeneration[layer] = collectedLayerGeneration(layer, now, true);
|
||||
refreshGeneration();
|
||||
forgetBorrowed(0, trackedLayers);
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
void StorageTexture::MarkUnitsWritten(const std::vector<bool>& units) {
|
||||
if (!blockUnits || descriptor.dccAddress != 0 || units.size() != trackedLayers || std::all_of(units.begin(), units.end(), [](bool unit) { return unit; })) {
|
||||
MarkWholeWritten();
|
||||
return;
|
||||
}
|
||||
const auto runs = unitRuns(units);
|
||||
if (runs.empty()) return;
|
||||
if (GuestMemory::Watched(descriptor.baseAddress, static_cast<std::size_t>(guestBytes))) {
|
||||
for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) {
|
||||
if (!units[unit]) continue;
|
||||
layerOriginal[unit] = false;
|
||||
cancelStoredSave(unit);
|
||||
}
|
||||
} else {
|
||||
captureGuestBytes(&units);
|
||||
}
|
||||
uploadedKeys = DccKeys::Uncompressed;
|
||||
for (const auto& [begin, end] : runs) {
|
||||
const auto first = static_cast<std::uint32_t>(begin / trackedLayerBytes);
|
||||
const auto count = static_cast<std::uint32_t>((end - begin + trackedLayerBytes - 1) / trackedLayerBytes);
|
||||
const auto collected = GuestMemory::CollectWritesUncached(descriptor.baseAddress + begin, static_cast<std::size_t>(end - begin));
|
||||
for (auto unit = first; unit < first + count; ++unit) layerGeneration[unit] = collected;
|
||||
}
|
||||
refreshGeneration();
|
||||
for (const auto& [begin, end] : runs) {
|
||||
const auto first = static_cast<std::uint32_t>(begin / trackedLayerBytes);
|
||||
const auto count = static_cast<std::uint32_t>((end - begin + trackedLayerBytes - 1) / trackedLayerBytes);
|
||||
forgetBorrowed(first, count);
|
||||
markLayersPending(first, count);
|
||||
}
|
||||
}
|
||||
|
||||
// See AdjacentGenerationEnabled above.
|
||||
std::vector<StorageTexture::Adjacent> StorageTexture::adjacentPendingUnchanged(std::uint64_t firstBlock, std::uint64_t lastBlock) const {
|
||||
std::vector<Adjacent> adjacent;
|
||||
@@ -5270,7 +5478,7 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
|
||||
if (!advances && adjacent.empty() && shadowed.empty()) return;
|
||||
const auto now = memoizedCollect ? GuestMemory::CollectWrites(firstStored, static_cast<std::size_t>(lastStored - firstStored)) : GuestMemory::CollectWritesUncached(firstStored, static_cast<std::size_t>(lastStored - firstStored));
|
||||
for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) {
|
||||
if (layers[layer] && !skippedLayer[layer]) layerGeneration[layer] = now;
|
||||
if (layers[layer] && !skippedLayer[layer]) layerGeneration[layer] = collectedLayerGeneration(layer, now, !memoizedCollect);
|
||||
}
|
||||
refreshGeneration();
|
||||
advanceAdjacent(adjacent, now, firstBlock, lastBlock);
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/shaders/TextureDetile_spv.h"
|
||||
#include "prx/libSceAgcDriver/Graphics/shaders/CmaskClear_spv.h"
|
||||
#include "prx/libSceAgcDriver/Graphics/shaders/DepthSampleMultisample_spv.h"
|
||||
#include "prx/libSceAgcDriver/Graphics/shaders/DepthSampleSingle_spv.h"
|
||||
#include "prx/libSceAgcDriver/Graphics/shaders/TextureDetileImage_spv.h"
|
||||
#include "prx/libSceAgcDriver/Graphics/shaders/TextureRepack_spv.h"
|
||||
#include <algorithm>
|
||||
@@ -108,6 +110,7 @@ void TextureDetiler::release() noexcept {
|
||||
for (const auto pool : readbackDescriptorSets.pools) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, pool, nullptr);
|
||||
for (const auto& entry : readbackPipelines) context.Function<PFN_vkDestroyPipeline>("vkDestroyPipeline")(context.device, entry.second, nullptr);
|
||||
for (const auto shader : readbackModules) if (shader) context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, shader, nullptr);
|
||||
if (multisampleUploadModule) context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, multisampleUploadModule, nullptr);
|
||||
if (readbackPipelineLayout) context.Function<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout")(context.device, readbackPipelineLayout, nullptr);
|
||||
if (readbackDescriptorLayout) context.Function<PFN_vkDestroyDescriptorSetLayout>("vkDestroyDescriptorSetLayout")(context.device, readbackDescriptorLayout, nullptr);
|
||||
for (const auto pool : cmaskDescriptorSets.pools) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, pool, nullptr);
|
||||
@@ -117,6 +120,11 @@ void TextureDetiler::release() noexcept {
|
||||
cmaskErrors.reset();
|
||||
for (const auto pool : descriptorSets.pools) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, pool, nullptr);
|
||||
for (const auto pool : imageDescriptorSets.pools) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, pool, nullptr);
|
||||
for (const auto pool : depthSampleSets.pools) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, pool, nullptr);
|
||||
for (const auto pipeline : depthSamplePipelines) if (pipeline) context.Function<PFN_vkDestroyPipeline>("vkDestroyPipeline")(context.device, pipeline, nullptr);
|
||||
if (depthSamplePipelineLayout) context.Function<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout")(context.device, depthSamplePipelineLayout, nullptr);
|
||||
if (depthSampleLayout) context.Function<PFN_vkDestroyDescriptorSetLayout>("vkDestroyDescriptorSetLayout")(context.device, depthSampleLayout, nullptr);
|
||||
if (depthSampler) context.Function<PFN_vkDestroySampler>("vkDestroySampler")(context.device, depthSampler, nullptr);
|
||||
if (imageModule) context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, imageModule, nullptr);
|
||||
if (repackPipeline) context.Function<PFN_vkDestroyPipeline>("vkDestroyPipeline")(context.device, repackPipeline, nullptr);
|
||||
if (repackModule) context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, repackModule, nullptr);
|
||||
@@ -394,6 +402,79 @@ void TextureDetiler::DispatchCmaskClear(VkCommandBuffer commands, VkBuffer cmask
|
||||
barrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, anyAccess);
|
||||
}
|
||||
|
||||
void TextureDetiler::DispatchDepthSample(VkCommandBuffer commands, VkImageView depth, bool multisampled, VkImageView target, std::uint32_t width, std::uint32_t height, std::uint32_t sample) {
|
||||
Require(commands != VK_NULL_HANDLE && depth != VK_NULL_HANDLE && target != VK_NULL_HANDLE, "a depth sample copy needs a command buffer, the depth view and the target view");
|
||||
if (depthSamplePipelineLayout == VK_NULL_HANDLE) {
|
||||
VkSamplerCreateInfo samplerInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
||||
samplerInfo.magFilter = VK_FILTER_NEAREST;
|
||||
samplerInfo.minFilter = VK_FILTER_NEAREST;
|
||||
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
||||
Check(context.Function<PFN_vkCreateSampler>("vkCreateSampler")(context.device, &samplerInfo, nullptr, &depthSampler), "vkCreateSampler depth sample copy");
|
||||
std::array<VkDescriptorSetLayoutBinding, 2> bindings{};
|
||||
bindings[0] = {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_COMPUTE_BIT, &depthSampler};
|
||||
bindings[1] = {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr};
|
||||
VkDescriptorSetLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
|
||||
layoutInfo.bindingCount = static_cast<std::uint32_t>(bindings.size());
|
||||
layoutInfo.pBindings = bindings.data();
|
||||
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("vkCreateDescriptorSetLayout")(context.device, &layoutInfo, nullptr, &depthSampleLayout), "vkCreateDescriptorSetLayout depth sample copy");
|
||||
const VkPushConstantRange pushRange{VK_SHADER_STAGE_COMPUTE_BIT, 0, 3 * sizeof(std::uint32_t)};
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
||||
pipelineLayoutInfo.setLayoutCount = 1;
|
||||
pipelineLayoutInfo.pSetLayouts = &depthSampleLayout;
|
||||
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||
pipelineLayoutInfo.pPushConstantRanges = &pushRange;
|
||||
Check(context.Function<PFN_vkCreatePipelineLayout>("vkCreatePipelineLayout")(context.device, &pipelineLayoutInfo, nullptr, &depthSamplePipelineLayout), "vkCreatePipelineLayout depth sample copy");
|
||||
}
|
||||
auto& depthSamplePipeline = depthSamplePipelines[multisampled ? 1u : 0u];
|
||||
if (depthSamplePipeline == VK_NULL_HANDLE) {
|
||||
VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
moduleInfo.codeSize = multisampled ? sizeof(DEPTH_SAMPLE_MULTISAMPLE_SPV) : sizeof(DEPTH_SAMPLE_SINGLE_SPV);
|
||||
moduleInfo.pCode = multisampled ? DEPTH_SAMPLE_MULTISAMPLE_SPV : DEPTH_SAMPLE_SINGLE_SPV;
|
||||
VkShaderModule depthModule = VK_NULL_HANDLE;
|
||||
Check(context.Function<PFN_vkCreateShaderModule>("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &depthModule), "vkCreateShaderModule depth sample copy");
|
||||
VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
|
||||
pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
|
||||
pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
pipelineInfo.stage.module = depthModule;
|
||||
pipelineInfo.stage.pName = "main";
|
||||
pipelineInfo.layout = depthSamplePipelineLayout;
|
||||
const auto created = context.Function<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &depthSamplePipeline);
|
||||
context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, depthModule, nullptr);
|
||||
Check(created, "vkCreateComputePipelines depth sample copy");
|
||||
}
|
||||
const auto set = allocateFrom(depthSampleSets, depthSampleLayout, {{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 64}, {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 64}}, "vkCreateDescriptorPool depth sample copy", "vkAllocateDescriptorSets depth sample copy", "vkResetDescriptorPool depth sample copy");
|
||||
const VkDescriptorImageInfo depthInfo{VK_NULL_HANDLE, depth, VK_IMAGE_LAYOUT_GENERAL};
|
||||
const VkDescriptorImageInfo targetInfo{VK_NULL_HANDLE, target, VK_IMAGE_LAYOUT_GENERAL};
|
||||
std::array<VkWriteDescriptorSet, 2> writes{};
|
||||
for (std::uint32_t index = 0; index < writes.size(); ++index) {
|
||||
writes[index].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
writes[index].dstSet = set;
|
||||
writes[index].dstBinding = index;
|
||||
writes[index].descriptorCount = 1;
|
||||
writes[index].descriptorType = index == 0 ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
}
|
||||
writes[0].pImageInfo = &depthInfo;
|
||||
writes[1].pImageInfo = &targetInfo;
|
||||
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
||||
const auto barrier = [&](VkPipelineStageFlags source, VkPipelineStageFlags destination, VkAccessFlags sourceAccess, VkAccessFlags destinationAccess) {
|
||||
VkMemoryBarrier memory{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
memory.srcAccessMask = sourceAccess;
|
||||
memory.dstAccessMask = destinationAccess;
|
||||
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, source, destination, 0, 1, &memory, 0, nullptr, 0, nullptr);
|
||||
};
|
||||
constexpr VkAccessFlags anyWrite = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT;
|
||||
constexpr VkAccessFlags anyAccess = anyWrite | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_HOST_READ_BIT;
|
||||
barrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, anyWrite, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, depthSamplePipeline);
|
||||
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, depthSamplePipelineLayout, 0, 1, &set, 0, nullptr);
|
||||
const std::array<std::uint32_t, 3> push{width, height, sample};
|
||||
context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants")(commands, depthSamplePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data());
|
||||
context.Function<PFN_vkCmdDispatch>("vkCmdDispatch")(commands, (width + 7u) / 8u, (height + 7u) / 8u, 1);
|
||||
barrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, anyAccess);
|
||||
}
|
||||
|
||||
void TextureDetiler::DispatchImage(VkCommandBuffer commands, TextureTileMode tileMode, std::uint32_t elementBytes, VkBuffer tiled, std::uint64_t tiledOffset, VkImageView view, const TileMipLayout& layout, bool retile, std::uint32_t slice, const DetileWindow& window) {
|
||||
Require(commands != VK_NULL_HANDLE, "texture detiling requires an active command buffer");
|
||||
Require(window.rangeBegin < window.rangeEnd && window.tiledBase <= window.rangeBegin, "texture detiling window is empty");
|
||||
|
||||
@@ -2227,6 +2227,95 @@ void importWatchTests() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void sparseImportedHeapTests(const Context& context, const BdaTestAccess& access) {
|
||||
#ifndef _WIN32
|
||||
if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) {
|
||||
std::cout << "write watch unavailable: sparse imported heaps not tested\n";
|
||||
return;
|
||||
}
|
||||
constexpr std::size_t bytes = 65536;
|
||||
void* raw = mmap(nullptr, 4 * bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Require(raw != MAP_FAILED, "cannot reserve the sparse imported heap fixture");
|
||||
const auto base = (reinterpret_cast<std::uintptr_t>(raw) + bytes - 1) & ~(bytes - 1);
|
||||
auto* guest = reinterpret_cast<std::uint8_t*>(base);
|
||||
Require(mprotect(guest, bytes, PROT_READ | PROT_WRITE) == 0, "cannot commit the sparse imported heap prefix");
|
||||
std::memset(guest, 0x57, bytes);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(guest, bytes);
|
||||
auto importing = context;
|
||||
importing.hostImportAlignment = bytes;
|
||||
auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix();
|
||||
GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest, bytes, true, true, true);
|
||||
GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation);
|
||||
const auto* imported = HostImportFor(importing, base, bytes);
|
||||
Require(imported != nullptr, "the sparse heap prefix was not imported");
|
||||
const auto before = MirrorCounters();
|
||||
for (unsigned index = 0; index < 3; ++index) {
|
||||
{
|
||||
GuestBufferMemory described(importing);
|
||||
described.AddReadable(base, 2 * bytes);
|
||||
described.Upload(false);
|
||||
std::uint32_t adjustment = 0;
|
||||
const auto view = described.Descriptor(base, 2 * bytes, adjustment);
|
||||
Require(view.buffer == imported->buffer && view.range == bytes && adjustment == 0, "the sparse descriptor did not bind its imported prefix");
|
||||
Require(described.CopiedBytes() == 0 && MirrorCounters().rebuilds == before.rebuilds, "the sparse imported descriptor rebuilt or copied its heap mirror");
|
||||
reject([&] { static_cast<void>(described.Descriptor(base + bytes + 16, 4, adjustment)); }, "starts past its copy");
|
||||
described.WriteBack();
|
||||
}
|
||||
{
|
||||
GuestBufferMemory addressed(importing);
|
||||
addressed.AcquireRegistered();
|
||||
addressed.Upload(true);
|
||||
Require(MirrorCounters().rebuilds == before.rebuilds && MirrorCounters().heapMirrors == before.heapMirrors, "alternating descriptor and address builds rebuilt the imported heap mirror");
|
||||
addressed.WriteBack();
|
||||
}
|
||||
}
|
||||
{
|
||||
auto misaligned = importing;
|
||||
misaligned.limits.minStorageBufferOffsetAlignment = 256;
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
Recorder recorder(misaligned);
|
||||
recorder.Activate();
|
||||
GuestBufferMemory memory(misaligned);
|
||||
memory.AddReadable(base + 16, 2 * bytes - 16);
|
||||
memory.Upload(false);
|
||||
std::uint32_t adjustment = 0;
|
||||
const auto view = memory.Descriptor(base + 16, 2 * bytes - 16, adjustment);
|
||||
Require(view.buffer != imported->buffer, "a misaligned sparse descriptor bound its import in place");
|
||||
Require(view.range != 0 && view.range <= bytes && memory.CopiedBytes() <= bytes && access.bytes(view.buffer)[view.offset + adjustment] == std::byte{0x57}, "a misaligned sparse descriptor does not serve its committed prefix alone");
|
||||
memory.WriteBack();
|
||||
}
|
||||
{
|
||||
GuestBufferMemory written(importing);
|
||||
written.AddWritable(base, 2 * bytes);
|
||||
written.Upload(false);
|
||||
std::uint32_t adjustment = 0;
|
||||
const auto view = written.Descriptor(base, 2 * bytes, adjustment);
|
||||
Require(view.buffer != imported->buffer && view.range == bytes && MirrorCounters().rebuilds == before.rebuilds + 1, "a sparse writable descriptor bound a shortened import");
|
||||
access.bytes(view.buffer)[view.offset] = std::byte{0x68};
|
||||
guest[8] = 0x79;
|
||||
written.WriteBack();
|
||||
Require(guest[0] == 0x68 && guest[8] == 0x79, "the sparse writable fallback lost a GPU store or rolled back a CPU store");
|
||||
}
|
||||
mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix();
|
||||
GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest);
|
||||
GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation);
|
||||
Require(HostImportFor(importing, base, bytes) == nullptr, "the sparse heap import outlived its range");
|
||||
{
|
||||
GuestBufferMemory sweep(importing);
|
||||
sweep.AcquireRegistered();
|
||||
sweep.Upload(true);
|
||||
sweep.WriteBack();
|
||||
}
|
||||
Require(MirrorCounters().heapMirrors == before.heapMirrors, "the sparse imported heap fixture leaked its mirror");
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(guest, bytes);
|
||||
munmap(raw, 4 * bytes);
|
||||
std::cout << "Sparse imported heap tests passed\n";
|
||||
#else
|
||||
static_cast<void>(context);
|
||||
static_cast<void>(access);
|
||||
#endif
|
||||
}
|
||||
|
||||
void heapMirrorTests(const Context& context, const BdaTestAccess& access) {
|
||||
if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) {
|
||||
std::cout << "guest arena unavailable or not write-watched: heap mirrors not tested\n";
|
||||
@@ -3439,6 +3528,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) {
|
||||
importReportFloorTests(context, access, true);
|
||||
importReportFloorTests(context, access, false);
|
||||
importReportRefusalMemoryTests(context, access);
|
||||
sparseImportedHeapTests(context, access);
|
||||
heapMirrorTests(context, access);
|
||||
importedHeapMirrorTests(context, access);
|
||||
importWatchTests();
|
||||
|
||||
@@ -830,7 +830,7 @@ void msaaEnableClearOneSampleTargetTests() {
|
||||
queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::SampleCoverage);
|
||||
Require(AgcDriver::Graphics::PixelProgramSkipped(queue), "the depth-only draw did not skip its pixel program");
|
||||
requireOneSampleDepth(queue, 0, "a one-sample depth target whose skipped pixel program has PS_ITER_SAMPLE and sample index and coverage inputs");
|
||||
constexpr std::string_view depthRefused = "a one-sample depth target under a multisampled rasterizer without MSAA_ENABLE is unsupported for points or lines and with alpha-to-coverage, PS_ITER_SAMPLE, a sample index or coverage input, a sample-mask export, or DB_EQAA Z interpolation or overrasterization";
|
||||
constexpr std::string_view depthRefused = "a one-sample depth target under a multisampled rasterizer without MSAA_ENABLE is unsupported for points or lines and with alpha-to-coverage, a sample index or coverage input, a sample-mask export, or DB_EQAA Z interpolation or overrasterization";
|
||||
for (const bool color : {false, true}) {
|
||||
for (const auto input : {ShaderRecompiler::PixelInput::Ancillary, ShaderRecompiler::PixelInput::SampleCoverage}) {
|
||||
queue = depthTarget(color);
|
||||
@@ -841,6 +841,10 @@ void msaaEnableClearOneSampleTargetTests() {
|
||||
queue = depthTarget(color);
|
||||
queue.context[0x203] |= 0x40u;
|
||||
queue.context[0x293] = 0x10000u;
|
||||
Require(!AgcDriver::Graphics::PixelProgramSkipped(queue), "the PS_ITER_SAMPLE draw skipped its pixel program");
|
||||
requireOneSampleDepth(queue, color ? 1u : 0u, "a one-sample depth target whose running pixel program has PS_ITER_SAMPLE and no sample index or coverage input");
|
||||
Require(!DecodeState(queue).perSampleShading, "a PS_ITER_SAMPLE program drawn as one sample still shades per sample");
|
||||
queue.context[0x1b3] = queue.context[0x1b4] = 2u | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::Ancillary);
|
||||
expectFailure([&] { DecodeState(queue); }, depthRefused);
|
||||
queue = depthTarget(color);
|
||||
queue.context[0x1c4] = 9;
|
||||
@@ -1070,14 +1074,19 @@ void ShaderStageTests() {
|
||||
Require(AgcDriver::Graphics::DecodeState(lines).stages.mesh->provokingVertex == 2 && AgcDriver::Graphics::DecodeShaderStages(lines).mesh->provokingVertex == 2, "a last-vertex geometry draw does not take its flat inputs and layer from the third vertex of each output triangle");
|
||||
lines.context[0x29b] = 1;
|
||||
Require(AgcDriver::Graphics::DecodeState(lines).stages.mesh->provokingVertex == 1, "a last-vertex geometry draw does not take its flat inputs and layer from the second vertex of each output line");
|
||||
lines.context[0x29b] = 0;
|
||||
state = AgcDriver::Graphics::DecodeState(lines);
|
||||
mesh = *state.stages.mesh;
|
||||
Require(mesh.outputPrimitiveSize == 1 && mesh.maxPrimitives == 255 && AgcDriver::Graphics::RasterPrimitiveOf(state) == ShaderRecompiler::RasterPrimitive::Point, "a point-list subgroup did not decode as points");
|
||||
Require(mesh.provokingVertex == 0, "a point draw has provoking vertex 0");
|
||||
lines.context[0x205] = raster;
|
||||
lines.context[0x29b] = 1;
|
||||
lines.context[0x2ce] = 1;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "unsupported geometry input or output assembly");
|
||||
lines.context[0x2ce] = 5;
|
||||
for (const auto output : {0u, 3u, 0x80000001u, 0x101u}) {
|
||||
for (const auto output : {3u, 0x80000001u, 0x101u}) {
|
||||
lines.context[0x29b] = output;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a line strip from a geometry program, is unsupported");
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(lines); }, "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a point list or line strip from a geometry program, is unsupported");
|
||||
}
|
||||
lines.userConfig[0x242] = 4;
|
||||
lines.context[0x205] = 0x2a40;
|
||||
@@ -1231,7 +1240,9 @@ void NggVertexWorkgroupTests() {
|
||||
}
|
||||
queue.context[0x2d5] = 0x12010;
|
||||
queue.context[0x29b] = 1;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue, true); }, "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a line strip from a geometry program, is unsupported");
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue, true); }, "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a point list or line strip from a geometry program, is unsupported");
|
||||
queue.context[0x29b] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue, true); }, "VGT_GS_OUT_PRIM_TYPE other than a triangle strip, or a point list or line strip from a geometry program, is unsupported");
|
||||
}
|
||||
|
||||
constexpr std::array<std::uint8_t, 8> IdentityExports{0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u};
|
||||
@@ -2251,7 +2262,41 @@ void DepthStencilTests() {
|
||||
queue.context[0x10b] = 0x40;
|
||||
queue.context[0x10c] = 0x05ffff02;
|
||||
queue.context[0x200] = 0x00200211;
|
||||
state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.failOp == VK_STENCIL_OP_KEEP && state.stencilFront.depthFailOp == VK_STENCIL_OP_KEEP && state.stencilFront.compareOp == VK_COMPARE_OP_EQUAL, "an equal test replaced by another value is not drawn as an inversion");
|
||||
Require(state.stencilFront.reference == 2 && state.stencilFront.compareMask == 0xff && state.stencilFront.writeMask == 7, "an inverted replacement does not keep the test value and write only the bits that differ from it");
|
||||
queue.context[0x10b] = 0x440;
|
||||
state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.depthFailOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 7, "a replacement on pass and depth fail is not drawn as one inversion");
|
||||
queue.context[0x10b] = 0x4;
|
||||
queue.context[0x200] = 0x00200511;
|
||||
state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.stencilFront.failOp == VK_STENCIL_OP_INVERT && state.stencilFront.passOp == VK_STENCIL_OP_KEEP && state.stencilFront.writeMask == 7, "a not-equal test that fails is not replaced as an inversion");
|
||||
queue.context[0x10c] = 0x40ffffff;
|
||||
queue.context[0x10b] = 0x40;
|
||||
queue.context[0x200] = 0x00200311;
|
||||
state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.stencilFront.passOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 0xbf && state.stencilFront.reference == 0xff, "a less-or-equal test against 0xff, which passes only on 0xff, is not replaced as an inversion");
|
||||
queue.context[0x10b] = 0x4;
|
||||
queue.context[0x200] = 0x00200411;
|
||||
state = AgcDriver::Graphics::DecodeState(queue);
|
||||
Require(state.stencilFront.failOp == VK_STENCIL_OP_INVERT && state.stencilFront.writeMask == 0xbf, "a greater test against 0xff, which fails only on 0xff, is not replaced as an inversion");
|
||||
queue.context[0x10c] = 0x40ffff7f;
|
||||
queue.context[0x10b] = 0x40;
|
||||
queue.context[0x200] = 0x00200311;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement");
|
||||
queue.context[0x10c] = 0x05ffff02;
|
||||
queue.context[0x200] = 0x00200511;
|
||||
queue.context[0x10b] = 0x40;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement");
|
||||
queue.context[0x200] = 0x00200111;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement");
|
||||
queue.context[0x200] = 0x00200211;
|
||||
queue.context[0x10c] = 0x05ff0f02;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil replacement");
|
||||
queue.context[0x10c] = 0x05ffff02;
|
||||
queue.context[0x10b] = 0x41;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "beside another stencil operation");
|
||||
queue.context[0x10b] = 0x50;
|
||||
queue.context[0x200] = 0x00700711;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "stencil add/subtract");
|
||||
@@ -2737,6 +2782,65 @@ void DepthBoundsBiasTests() {
|
||||
|
||||
alignas(256) std::array<std::uint8_t, 4> dccKeys{};
|
||||
|
||||
void depthCopyPassTests() {
|
||||
using AgcDriver::Graphics::DecodeDepthCopyPass;
|
||||
auto queue = makeState();
|
||||
queue.context[0x0] = 0;
|
||||
Require(!DecodeDepthCopyPass(queue).has_value(), "a draw without DEPTH_COPY decoded as a DB depth copy");
|
||||
queue.context[0x000] = 0x84;
|
||||
queue.context[0x010] = 0x80000187;
|
||||
queue.context[0x011] = 0x20000180;
|
||||
queue.context[0x012] = 0x00001000;
|
||||
queue.context[0x007] = 0x0003003f;
|
||||
queue.context[0x31c] = 0x40710;
|
||||
queue.context[0x8e] = 1;
|
||||
auto pass = DecodeDepthCopyPass(queue);
|
||||
Require(pass.has_value() && pass->depthAddress == 0x100000 && pass->samples == 2 && pass->sample == 0 && pass->extent.width == 64 && pass->extent.height == 4, "a centroid DB depth copy of a two-sample depth plane decoded differently");
|
||||
Require(pass->color.format == VK_FORMAT_R32_SFLOAT && pass->color.samples == 1 && pass->color.address == reinterpret_cast<std::uintptr_t>(colorMemory.data()), "the DB depth copy target decoded differently");
|
||||
queue.context[0x000] = 0x104;
|
||||
Require(DecodeDepthCopyPass(queue)->sample == 1, "COPY_SAMPLE without COPY_CENTROID did not select the sample");
|
||||
queue.context[0x000] = 0x204;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "a sample the depth plane does not have");
|
||||
queue.context[0x000] = 0x8c;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "stencil copy");
|
||||
queue.context[0x000] = 0x84;
|
||||
queue.context[0x30e] = 0xfffffffe;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "sample mask");
|
||||
queue.context[0x30e] = 0xffffffff;
|
||||
queue.context[0x200] = 2;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "depth or stencil tests");
|
||||
queue.context[0x200] = 0;
|
||||
queue.context[0x010] = 0x80000185;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "Z_32_FLOAT");
|
||||
queue.context[0x010] = 0x80000187;
|
||||
queue.context[0x8e] = 0xf;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "red channel");
|
||||
queue.context[0x8e] = 1;
|
||||
queue.context[0x31c] = 0x28028;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "single-sample R32 float");
|
||||
queue.context[0x31c] = 0x40710;
|
||||
queue.context[0x10f] = std::bit_cast<std::uint32_t>(16.0f);
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "over part of the depth surface");
|
||||
queue.context[0x10f] = std::bit_cast<std::uint32_t>(32.0f);
|
||||
queue.context[0x010] = 0x80001187;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "mipmapped or partially resident");
|
||||
queue.context[0x010] = 0x80000187;
|
||||
queue.context[0x012] = 0x00001003;
|
||||
pass = DecodeDepthCopyPass(queue);
|
||||
Require(pass.has_value() && pass->depthAddress == 0x100000, "a DB depth copy kept the pipe-bank XOR of a two-sample depth plane in its address");
|
||||
queue.context[0x012] = 0x00001000;
|
||||
queue.context[0x002] = 1u | (1u << 13u);
|
||||
pass = DecodeDepthCopyPass(queue);
|
||||
Require(pass.has_value() && pass->depthAddress == 0x100000 + AgcDriver::Graphics::DepthSliceBytes({64, 4}, 4u), "a DB depth copy of slice 1 did not address the slice the depth surface registers");
|
||||
queue.context[0x002] = 0;
|
||||
Require(DecodeDepthCopyPass(queue).has_value(), "the restored DB depth copy was refused");
|
||||
queue.context[0x010] = 0x80000183;
|
||||
pass = DecodeDepthCopyPass(queue);
|
||||
Require(pass.has_value() && pass->samples == 1 && pass->sample == 0, "a centroid DB depth copy of a one-sample depth plane decoded differently");
|
||||
queue.context[0x000] = 0x104;
|
||||
expectFailure([&] { (void)DecodeDepthCopyPass(queue); }, "a sample the depth plane does not have");
|
||||
}
|
||||
|
||||
void metadataPassTests() {
|
||||
using AgcDriver::Graphics::ColorMetadataPass;
|
||||
using AgcDriver::Graphics::DecodeColorMetadataPass;
|
||||
@@ -9091,10 +9195,16 @@ void ProvokingVertexTests() {
|
||||
meshDraw.topology = topology;
|
||||
for (const auto& drawn : {std::span<const CompiledShader>{}, std::span<const CompiledShader>(flatShaders), std::span<const CompiledShader>(flatP10Shaders), std::span<const CompiledShader>(smoothShaders), std::span<const CompiledShader>(slotShaders)}) ValidateProvokingVertex(supported, meshDraw, drawn);
|
||||
}
|
||||
meshDraw.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
for (const auto& drawn : {std::span<const CompiledShader>{}, std::span<const CompiledShader>(flatShaders), std::span<const CompiledShader>(flatP10Shaders), std::span<const CompiledShader>(smoothShaders)}) ValidateProvokingVertex(supported, meshDraw, drawn);
|
||||
fragment.fragmentParameters = {{0, 0, false, true, false, false, false}, {1, 1, false, true, false, false, false}, {2, 2, false, true, false, false, false}, {3, 3, false, true, false, false, false}};
|
||||
ValidateProvokingVertex(supported, meshDraw, shaders);
|
||||
expectFailure([&] { ValidateProvokingVertex(supported, meshDraw, slotShaders); }, "last provoking vertex with explicit per-vertex triangle-strip interpolation is unsupported");
|
||||
meshDraw.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
|
||||
expectFailure([&] { ValidateProvokingVertex(supported, meshDraw, flatShaders); }, "last provoking vertex is unsupported for triangle fans assembled for a geometry program");
|
||||
for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN}) {
|
||||
meshDraw.topology = topology;
|
||||
expectFailure([&] { ValidateProvokingVertex(supported, meshDraw, flatShaders); }, "last provoking vertex is unsupported for triangle strips or fans assembled for a geometry program");
|
||||
auto firstDraw = meshDraw;
|
||||
firstDraw.topology = topology;
|
||||
firstDraw.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT;
|
||||
ValidateProvokingVertex(supported, firstDraw, flatShaders);
|
||||
}
|
||||
@@ -9780,26 +9890,63 @@ void validationTests() {
|
||||
attribute.resource.fields[3] = 77u << 12u;
|
||||
attribute.fetchIndex = 1;
|
||||
attribute.resource.fields[1] = 0;
|
||||
attribute.resource.fields[2] = 16;
|
||||
const auto zeroStride = [&](std::uint32_t select, std::uint32_t records) {
|
||||
attribute.resource.fields[3] = (77u << 12u) | (select << 28u);
|
||||
attribute.resource.fields[2] = records;
|
||||
};
|
||||
for (const auto records : {0u, 1u, 16u, 0xffffffffu}) {
|
||||
zeroStride(0, records);
|
||||
Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 0 must read every zero-stride fetch as zero");
|
||||
Require(AgcDriver::Graphics::ZeroStrideAvailableBytes(attribute) == 0, "OOB_SELECT 0 zero-stride fetch has bytes to read");
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1); }, "byte range");
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferExtent(attribute); }, "empty or oversized");
|
||||
}
|
||||
zeroStride(1, 16);
|
||||
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2) == 16, "zero stride must repeat one value");
|
||||
Require(AgcDriver::Graphics::VertexBufferReadOffset(attribute, 100, 7) == 0, "zero-stride input skipped its repeated value");
|
||||
attribute.resource.fields[2] = 8;
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1); }, "byte range");
|
||||
Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "a structured zero-stride fetch was treated as out of range");
|
||||
attribute.resource.fields[3] = (77u << 12u) | (2u << 28u);
|
||||
attribute.resource.fields[2] = 1;
|
||||
Require(AgcDriver::Graphics::VertexBufferExtent(attribute) == 16, "OOB_SELECT 1 extent must cover the whole fetch");
|
||||
zeroStride(1, 0);
|
||||
Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 1 without records must read zero");
|
||||
zeroStride(1, 8);
|
||||
Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 1 counted NUM_RECORDS in bytes");
|
||||
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 8) == 16, "OOB_SELECT 1 must read the whole element for every index below NUM_RECORDS");
|
||||
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1, 7) == 16, "OOB_SELECT 1 must read the whole element for the last index below NUM_RECORDS");
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 9); }, "record count");
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1, 8); }, "record count");
|
||||
zeroStride(1, 1);
|
||||
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1) == 16, "OOB_SELECT 1 with one record must read the element at index 0");
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 2); }, "record count");
|
||||
attribute.fetchIndex = 0;
|
||||
zeroStride(1, 3);
|
||||
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 2, 1) == 16, "OOB_SELECT 1 must read the element for index 2 of 3 records");
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 3, 1); }, "record count");
|
||||
attribute.fetchIndex = 1;
|
||||
zeroStride(2, 1);
|
||||
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2) == 16, "OOB_SELECT 2 must read a zero-stride fetch whole while NUM_RECORDS is nonzero");
|
||||
Require(AgcDriver::Graphics::VertexBufferExtent(attribute) == 16, "OOB_SELECT 2 extent must cover the whole fetch");
|
||||
Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 2 with records was treated as out of range");
|
||||
attribute.resource.fields[2] = 0;
|
||||
zeroStride(2, 0);
|
||||
Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 2 without records must read zero");
|
||||
attribute.resource.fields[3] = (77u << 12u) | (3u << 28u);
|
||||
zeroStride(3, 0);
|
||||
Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 without records must read zero");
|
||||
attribute.resource.fields[2] = 8;
|
||||
Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 with records was treated as out of range");
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1); }, "byte range");
|
||||
attribute.resource.fields[2] = 16;
|
||||
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1) == 16, "OOB_SELECT 3 must read a fetch inside the range");
|
||||
for (const auto records : {1u, 8u, 15u}) {
|
||||
zeroStride(3, records);
|
||||
Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 must read a fetch that only partly fits as zero");
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 1); }, "byte range");
|
||||
}
|
||||
for (const auto records : {16u, 17u, 0xffffffffu}) {
|
||||
zeroStride(3, records);
|
||||
Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 with the whole fetch in range was treated as out of range");
|
||||
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2) == 16, "OOB_SELECT 3 must read a fetch inside the range, whatever the index");
|
||||
Require(AgcDriver::Graphics::VertexBufferExtent(attribute) == 16, "OOB_SELECT 3 extent must cover only the fetch");
|
||||
}
|
||||
attribute.resource.fields[3] = (22u << 12u) | (3u << 28u);
|
||||
attribute.resource.fields[2] = 4;
|
||||
Require(!AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 compared NUM_RECORDS with the wrong fetch size");
|
||||
attribute.resource.fields[2] = 3;
|
||||
Require(AgcDriver::Graphics::VertexFetchOutOfRange(attribute), "OOB_SELECT 3 read a 4-byte fetch past 3 bytes");
|
||||
attribute.resource.fields[3] = (113u << 12u) | (3u << 28u);
|
||||
expectFailure([&] { AgcDriver::Graphics::VertexFetchOutOfRange(attribute); }, "unsupported vertex format");
|
||||
attribute.resource.fields[3] = 113u << 12u;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeVertexFormat(attribute); }, "unsupported vertex format");
|
||||
attribute.resource.fields[3] = 50u << 12u;
|
||||
@@ -11016,6 +11163,7 @@ int main(int argc, char** argv) {
|
||||
DualSourceBlendTests();
|
||||
ReversedBlendShaderTests();
|
||||
metadataPassTests();
|
||||
depthCopyPassTests();
|
||||
cmaskTests();
|
||||
uint16ExportTests();
|
||||
uint8x2TargetTests();
|
||||
|
||||
@@ -517,7 +517,7 @@ void partialUnmap(Fixture& fixture) {
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Unmap(middle, part, [&](const void* piece, std::size_t bytes, const void* original, bool last) {
|
||||
Require(!imported->freed.load(), "an unmap inside a host import freed the memory of the side it keeps");
|
||||
requireFreed(imported);
|
||||
Require(!other->freed.load(), "partial unmap retired a disjoint host import");
|
||||
Require(piece == middle && bytes == part && original == allocation.data && !last, "partial unmap changed the wrong guest extent");
|
||||
});
|
||||
@@ -525,9 +525,8 @@ void partialUnmap(Fixture& fixture) {
|
||||
Require(!RegisteredReadableCovers(reinterpret_cast<std::uint64_t>(middle), part), "the middle of a partial unmap remains registered");
|
||||
const auto low = fixture.Import(allocation.Address(), part);
|
||||
const auto high = fixture.Import(allocation.Address() + 2 * part, part);
|
||||
Require(high->memory == imported->memory && !imported->freed.load(), "the side kept from an unmap inside the host import was imported again");
|
||||
Require(imported->freed.load(), "the side kept from an unmap inside the host import reused memory imported across the unmapped range");
|
||||
Require(high->address == allocation.Address() + 2 * part && high->bytes == part, "the kept side of the host import does not cover its remaining range");
|
||||
Require(low->memory != imported->memory, "the smaller side of an unmap inside the host import was not imported again");
|
||||
Require(!low->freed.load() && !high->freed.load() && !other->freed.load(), "remaining partial mappings could not be imported");
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ReferenceCase {
|
||||
TextureTileMode tileMode;
|
||||
std::uint32_t elementBytes;
|
||||
std::uint32_t fragments;
|
||||
std::uint32_t blockWidth;
|
||||
std::uint32_t blockHeight;
|
||||
std::uint32_t pitch;
|
||||
std::uint32_t paddedHeight;
|
||||
std::uint64_t sliceBytes;
|
||||
std::array<std::uint64_t, 7> addresses;
|
||||
};
|
||||
|
||||
constexpr std::array<std::array<std::uint32_t, 3>, 7> kCoordinates{{
|
||||
{0u, 0u, 0u}, {128u, 66u, 16u}, {64u, 32u, 1u}, {32u, 64u, 2u},
|
||||
{63u, 31u, 7u}, {127u, 65u, 15u}, {1u, 1u, 3u}
|
||||
}};
|
||||
|
||||
constexpr ReferenceCase kReferences[] = {
|
||||
{TextureTileMode::kR64KBX, 1u, 2u, 128u, 256u, 256u, 256u, 131072ull, {{0ull, 2201608ull, 141312ull, 300032ull, 919295ull, 2013271ull, 396305ull}}},
|
||||
{TextureTileMode::kR64KBX, 1u, 4u, 128u, 128u, 256u, 128u, 131072ull, {{0ull, 2185224ull, 174080ull, 316416ull, 919295ull, 1996887ull, 429073ull}}},
|
||||
{TextureTileMode::kR64KBX, 1u, 8u, 64u, 128u, 192u, 128u, 196608ull, {{0ull, 3291144ull, 279552ull, 422400ull, 1413119ull, 3061847ull, 640529ull}}},
|
||||
{TextureTileMode::kR64KBX, 2u, 2u, 128u, 128u, 256u, 128u, 131072ull, {{0ull, 2213920ull, 141312ull, 312320ull, 923390ull, 2025630ull, 396306ull}}},
|
||||
{TextureTileMode::kR64KBX, 2u, 4u, 128u, 64u, 256u, 128u, 262144ull, {{0ull, 4392992ull, 305152ull, 689152ull, 1840894ull, 4073630ull, 822290ull}}},
|
||||
{TextureTileMode::kR64KBX, 2u, 8u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6621216ull, 476160ull, 999936ull, 2793470ull, 6195358ull, 1230354ull}}},
|
||||
{TextureTileMode::kR64KBX, 4u, 2u, 64u, 128u, 192u, 128u, 196608ull, {{0ull, 3328032ull, 264192ull, 443392ull, 1390332ull, 3074204ull, 592916ull}}},
|
||||
{TextureTileMode::kR64KBX, 4u, 4u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6621216ull, 493568ull, 1016832ull, 2766588ull, 6170780ull, 1215508ull}}},
|
||||
{TextureTileMode::kR64KBX, 4u, 8u, 32u, 64u, 160u, 128u, 655360ull, {{0ull, 11077664ull, 820224ull, 1737216ull, 4667388ull, 10365084ull, 2002452ull}}},
|
||||
{TextureTileMode::kR64KBX, 8u, 2u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6621312ull, 460800ull, 984064ull, 2782968ull, 6170744ull, 1182744ull}}},
|
||||
{TextureTileMode::kR64KBX, 8u, 4u, 64u, 32u, 192u, 96u, 589824ull, {{0ull, 9963648ull, 855040ull, 1574400ull, 4158456ull, 9317496ull, 1773080ull}}},
|
||||
{TextureTileMode::kR64KBX, 8u, 8u, 32u, 32u, 160u, 96u, 983040ull, {{0ull, 16648320ull, 1444352ull, 2689280ull, 6979576ull, 15605880ull, 2952984ull}}},
|
||||
{TextureTileMode::kR64KBX, 16u, 2u, 32u, 64u, 160u, 128u, 655360ull, {{0ull, 11075712ull, 787456ull, 1706496ull, 4718576ull, 10397808ull, 1967664ull}}},
|
||||
{TextureTileMode::kR64KBX, 16u, 4u, 32u, 32u, 160u, 96u, 983040ull, {{0ull, 16646272ull, 1443328ull, 2690304ull, 7011312ull, 15641712ull, 2950960ull}}},
|
||||
{TextureTileMode::kR64KBX, 16u, 8u, 16u, 32u, 144u, 96u, 1769472ull, {{0ull, 30017664ull, 2624000ull, 4851968ull, 12615664ull, 28201072ull, 5312304ull}}},
|
||||
{TextureTileMode::kZ64KBX, 1u, 2u, 128u, 256u, 256u, 256u, 131072ull, {{0ull, 2172945ull, 149504ull, 271361ull, 923390ull, 1992879ull, 396294ull}}},
|
||||
{TextureTileMode::kZ64KBX, 1u, 4u, 128u, 128u, 256u, 128u, 131072ull, {{0ull, 2181153ull, 165890ull, 279555ull, 931580ull, 2025565ull, 396302ull}}},
|
||||
{TextureTileMode::kZ64KBX, 1u, 8u, 64u, 128u, 192u, 128u, 196608ull, {{0ull, 3295297ull, 263170ull, 410115ull, 1425404ull, 3041469ull, 591390ull}}},
|
||||
{TextureTileMode::kZ64KBX, 2u, 2u, 128u, 128u, 256u, 128u, 131072ull, {{0ull, 2181154ull, 165888ull, 279554ull, 931580ull, 2025566ull, 396300ull}}},
|
||||
{TextureTileMode::kZ64KBX, 2u, 4u, 128u, 64u, 256u, 128u, 262144ull, {{0ull, 4425794ull, 280580ull, 689158ull, 1881848ull, 4118714ull, 789532ull}}},
|
||||
{TextureTileMode::kZ64KBX, 2u, 8u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6637698ull, 459780ull, 999942ull, 2818040ull, 6219898ull, 1181244ull}}},
|
||||
{TextureTileMode::kZ64KBX, 4u, 2u, 64u, 128u, 192u, 128u, 196608ull, {{0ull, 3295300ull, 264192ull, 410628ull, 1423096ull, 3041468ull, 592920ull}}},
|
||||
{TextureTileMode::kZ64KBX, 4u, 4u, 64u, 64u, 192u, 128u, 393216ull, {{0ull, 6637700ull, 460808ull, 1000460ull, 2815728ull, 6219892ull, 1182776ull}}},
|
||||
{TextureTileMode::kZ64KBX, 4u, 8u, 32u, 64u, 160u, 128u, 655360ull, {{0ull, 11126788ull, 787464ull, 1720844ull, 4716528ull, 10359028ull, 1967736ull}}},
|
||||
};
|
||||
|
||||
template<typename TFunction>
|
||||
void requireRefusal(TFunction action) {
|
||||
bool refused = false;
|
||||
try { action(); } catch (const std::exception&) { refused = true; }
|
||||
Require(refused, "multisampled layout accepted an unsupported operation");
|
||||
}
|
||||
|
||||
void checkReference(const ReferenceCase& reference) {
|
||||
MultisampleTextureLayout layout(reference.tileMode, reference.elementBytes, reference.fragments, 129u, 67u, 17u);
|
||||
Require(layout.BlockWidth() == reference.blockWidth && layout.BlockHeight() == reference.blockHeight, "multisampled block extent differs from AMD reference");
|
||||
Require(layout.Pitch() == reference.pitch && layout.PaddedHeight() == reference.paddedHeight, "multisampled padded extent differs from AMD reference");
|
||||
Require(layout.SliceBytes() == reference.sliceBytes && layout.Bytes() == reference.sliceBytes * 17u, "multisampled allocation differs from AMD reference");
|
||||
Require(layout.LinearBytes() == 129ull * 67u * 17u * reference.fragments * reference.elementBytes, "multisampled linear region omits fragments or layers");
|
||||
for (std::uint32_t n = 0; n < kCoordinates.size(); ++n) {
|
||||
const auto& coordinate = kCoordinates[n];
|
||||
const auto address = layout.Offset(coordinate[0], coordinate[1], coordinate[2], n % reference.fragments);
|
||||
Require(address == reference.addresses[n], "multisampled fragment address differs from AMD reference");
|
||||
for (auto xorBytes : {0x100u, 0x800u, 0x4000u, 0x8000u, 0xcf00u}) {
|
||||
MultisampleTextureLayout swizzled(reference.tileMode, reference.elementBytes, reference.fragments, 129u, 67u, 17u, xorBytes);
|
||||
const auto expected = (address & ~65535ull) | ((address & 65535ull) ^ xorBytes);
|
||||
Require(swizzled.Offset(coordinate[0], coordinate[1], coordinate[2], n % reference.fragments) == expected, "multisampled pipe/bank XOR moves a fragment outside its tile");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkTransfer(const ReferenceCase& reference) {
|
||||
MultisampleTextureLayout layout(reference.tileMode, reference.elementBytes, reference.fragments, 129u, 67u, 3u, 0x8100u);
|
||||
std::vector<std::byte> source(layout.LinearBytes() + 32u, std::byte{0xa5});
|
||||
auto linear = std::span(source).subspan(16u, layout.LinearBytes());
|
||||
for (std::size_t n = 0; n < linear.size(); ++n) linear[n] = static_cast<std::byte>((n * 13u + n / 251u) & 255u);
|
||||
const auto savedSource = source;
|
||||
std::vector<std::byte> tiled(layout.Bytes() + 32u, std::byte{0xa5});
|
||||
auto guest = std::span(tiled).subspan(16u, layout.Bytes());
|
||||
layout.Tile(linear, guest);
|
||||
Require(source == savedSource, "multisampled retile changed its source");
|
||||
std::vector<bool> covered(guest.size());
|
||||
std::size_t position = 0;
|
||||
for (std::uint32_t layer = 0; layer < 3u; ++layer) {
|
||||
for (std::uint32_t y = 0; y < 67u; ++y) {
|
||||
for (std::uint32_t x = 0; x < 129u; ++x) {
|
||||
for (std::uint32_t fragment = 0; fragment < reference.fragments; ++fragment) {
|
||||
const auto address = layout.Offset(x, y, layer, fragment);
|
||||
for (std::uint32_t channel = 0; channel < reference.elementBytes; ++channel) {
|
||||
Require(address + channel < guest.size() && !covered[address + channel], "multisampled element addresses overlap or exceed the allocation");
|
||||
covered[address + channel] = true;
|
||||
Require(guest[address + channel] == linear[position++], "multisampled retile changed element bytes");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::size_t n = 0; n < guest.size(); ++n) Require(covered[n] || guest[n] == std::byte{0xa5}, "multisampled retile changed tile padding");
|
||||
Require(std::all_of(tiled.begin(), tiled.begin() + 16u, [](auto value) { return value == std::byte{0xa5}; }) && std::all_of(tiled.end() - 16u, tiled.end(), [](auto value) { return value == std::byte{0xa5}; }), "multisampled retile changed guard bytes");
|
||||
const auto savedTiled = tiled;
|
||||
std::vector<std::byte> restored(source.size(), std::byte{0xa5});
|
||||
layout.Detile(guest, std::span(restored).subspan(16u, layout.LinearBytes()));
|
||||
Require(restored == source && tiled == savedTiled, "multisampled detile changed element, source or guard bytes");
|
||||
}
|
||||
|
||||
void checkInverse(const ReferenceCase& reference) {
|
||||
const auto byteBits = static_cast<std::uint32_t>(std::countr_zero(reference.elementBytes));
|
||||
const auto fragmentBits = static_cast<std::uint32_t>(std::countr_zero(reference.fragments));
|
||||
const auto widthBits = static_cast<std::uint32_t>(std::countr_zero(reference.blockWidth));
|
||||
for (const auto xorBytes : {0u, 0x4000u, 0x8000u, 0xcf00u}) {
|
||||
const MultisampleTextureLayout layout(reference.tileMode, reference.elementBytes, reference.fragments, reference.blockWidth * 4u, reference.blockHeight * 4u, 8u, xorBytes);
|
||||
for (const auto origin : {std::array{0u, 0u, 0u}, std::array{reference.blockWidth * 2u, reference.blockHeight * 3u, 7u}}) {
|
||||
const auto base = static_cast<std::uint32_t>(layout.Offset(origin[0], origin[1], origin[2], 0u) & 65535u);
|
||||
const auto step = xorBytes == 0u && origin[2] == 0u ? 1u : 257u;
|
||||
for (std::uint32_t packed = 0u; packed < 65536u; packed += step) {
|
||||
const auto byte = packed & (reference.elementBytes - 1u);
|
||||
const auto fragment = (packed >> byteBits) & (reference.fragments - 1u);
|
||||
const auto x = (packed >> (byteBits + fragmentBits)) & (reference.blockWidth - 1u);
|
||||
const auto y = packed >> (byteBits + fragmentBits + widthBits);
|
||||
const auto physical = static_cast<std::uint32_t>((layout.Offset(origin[0] + x, origin[1] + y, origin[2], fragment) + byte) & 65535u) ^ base;
|
||||
std::uint32_t restored = 0u;
|
||||
for (std::uint32_t bit = 0; bit < 16u; ++bit) restored |= static_cast<std::uint32_t>(std::popcount(physical & layout.InverseBlockEquation()[bit]) & 1u) << bit;
|
||||
Require(restored == packed, "multisampled inverse equation changed a byte, fragment or pixel coordinate");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkRefusals() {
|
||||
for (const auto& extent : {std::array{0u, 3u, 1u}, std::array{5u, 0u, 1u}, std::array{16385u, 3u, 1u}, std::array{5u, 16385u, 1u}, std::array{5u, 3u, 0u}, std::array{5u, 3u, 8193u}}) {
|
||||
requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kR64KBX, 8u, 8u, extent[0], extent[1], extent[2]); });
|
||||
}
|
||||
for (auto elementBytes : {0u, 3u, 32u}) requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kR64KBX, elementBytes, 8u, 5u, 3u, 1u); });
|
||||
for (auto fragments : {0u, 1u, 3u, 16u}) requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kR64KBX, 8u, fragments, 5u, 3u, 1u); });
|
||||
for (auto tileMode : {TextureTileMode::kLinear, TextureTileMode::kStandard4KB, TextureTileMode::kStandard64KB, TextureTileMode::kS64KBX, TextureTileMode::kD64KBX}) requireRefusal([&] { MultisampleTextureLayout invalid(tileMode, 8u, 8u, 5u, 3u, 1u); });
|
||||
for (auto elementBytes : {8u, 16u}) requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kZ64KBX, elementBytes, 8u, 5u, 3u, 1u); });
|
||||
for (auto xorBytes : {1u, 255u, 0x1000u, 0x2000u, 0x10000u}) requireRefusal([&] { MultisampleTextureLayout invalid(TextureTileMode::kR64KBX, 8u, 8u, 5u, 3u, 1u, xorBytes); });
|
||||
|
||||
MultisampleTextureLayout layout(TextureTileMode::kR64KBX, 8u, 8u, 5u, 3u, 2u);
|
||||
for (const auto& coordinate : {std::array{5u, 0u, 0u, 0u}, std::array{0u, 3u, 0u, 0u}, std::array{0u, 0u, 2u, 0u}, std::array{0u, 0u, 0u, 8u}}) requireRefusal([&] { layout.Offset(coordinate[0], coordinate[1], coordinate[2], coordinate[3]); });
|
||||
std::vector<std::byte> source(layout.Bytes(), std::byte{0xa5}), destination(layout.Bytes(), std::byte{0x5a});
|
||||
const auto savedSource = source, savedDestination = destination;
|
||||
requireRefusal([&] { layout.Detile(std::span(source).first(layout.Bytes() - 1u), destination); });
|
||||
requireRefusal([&] { layout.Detile(source, std::span(destination).first(layout.LinearBytes() - 1u)); });
|
||||
requireRefusal([&] { layout.Tile(std::span(source).first(layout.LinearBytes() - 1u), destination); });
|
||||
requireRefusal([&] { layout.Tile(source, std::span(destination).first(layout.Bytes() - 1u)); });
|
||||
requireRefusal([&] { layout.Detile(source, source); });
|
||||
requireRefusal([&] { layout.Tile(source, source); });
|
||||
requireRefusal([&] { layout.Detile(source, std::span(source).subspan(8u)); });
|
||||
Require(source == savedSource && destination == savedDestination, "refused multisampled transfer changed a buffer");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
for (const auto& reference : kReferences) {
|
||||
checkReference(reference);
|
||||
checkTransfer(reference);
|
||||
checkInverse(reference);
|
||||
}
|
||||
checkRefusals();
|
||||
std::cout << "Multisampled texture layout reference, fragment, layer, padding and refusal tests passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1079,6 +1079,15 @@ void testIndexAttributesReadAtExecution() {
|
||||
check(std::atomic_ref<std::uint32_t>(done).load() == 1, "INDEX_ATTRIBUTES_INDIRECT did not read its record when the packet ran");
|
||||
}
|
||||
|
||||
void testReleaseMemDataSelects() {
|
||||
alignas(8) static std::uint64_t target = 0;
|
||||
const auto release = [](std::uint32_t select) { return makePacket(0x49, {0x0030c514, select << 29u, low(&target), high(&target), 1, 0, 0}); };
|
||||
for (std::uint32_t select = 0; select <= 3; ++select) AgcDriver::Pm4::Validate(release(select), 0);
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(release(4), 0); }, "64-bit system counter (DATA_SEL 4)");
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(release(5), 0); }, "GDS RELEASE_MEM data");
|
||||
for (const auto select : {6u, 7u}) expectFailure([&] { AgcDriver::Pm4::Validate(release(select), 0); }, "reserved RELEASE_MEM DATA_SEL");
|
||||
}
|
||||
|
||||
void testSuspendPointWritesQueuedLabels() {
|
||||
alignas(8) static std::uint32_t gate = 0, marker = 0;
|
||||
gate = 0;
|
||||
@@ -1944,6 +1953,7 @@ int main(int argc, char** argv) {
|
||||
testConditionalValidation();
|
||||
testConditionReadSynchronization();
|
||||
testEventWrite();
|
||||
testReleaseMemDataSelects();
|
||||
testAcquireMem();
|
||||
testConditionalWrite();
|
||||
testConditionalWriteSubmission();
|
||||
|
||||
@@ -117,6 +117,32 @@ void ReleaseWatched(void* block, std::size_t bytes) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool collectMappingHistoryTests() {
|
||||
constexpr std::size_t bytes = 131072;
|
||||
auto* memory = static_cast<std::uint8_t*>(AllocateWatched(bytes, 65536));
|
||||
if (memory == nullptr) return false;
|
||||
GuestAllocations::Mutation().Add(memory, bytes, true, true, false);
|
||||
struct Cleanup {
|
||||
std::uint8_t* memory;
|
||||
~Cleanup() {
|
||||
GuestAllocations::Mutation().Remove(memory);
|
||||
ReleaseWatched(memory, bytes);
|
||||
}
|
||||
} cleanup{memory};
|
||||
const auto address = reinterpret_cast<std::uint64_t>(memory);
|
||||
std::memset(memory, 0x11, bytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
const auto before = AgcDriver::GuestMemory::CollectWrites(address, bytes);
|
||||
Require(before != 0, "the mapping fixture has no complete write history");
|
||||
memory[4096] = 0x22;
|
||||
GuestAllocations::Mutation().Remove(memory);
|
||||
GuestAllocations::Mutation().Add(memory, bytes, true, true, false);
|
||||
Require(AgcDriver::GuestMemory::CollectWrites(address, bytes) != 0, "the replacement mapping lost write tracking");
|
||||
Require(!AgcDriver::GuestMemory::UnchangedSince(address, bytes, before), "a cached collect hid a CPU store across allocation replacement");
|
||||
Require(memory[4096] == 0x22, "collecting the replacement mapping changed its CPU bytes");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool partialWatchRestorationTests() {
|
||||
#ifdef _WIN32
|
||||
constexpr std::size_t bytes = 131072;
|
||||
@@ -191,6 +217,41 @@ std::optional<std::size_t> selectDevice(std::span<const VkPhysicalDeviceProperti
|
||||
return selected;
|
||||
}
|
||||
|
||||
bool hintMappingHistoryTests() {
|
||||
#ifdef _WIN32
|
||||
constexpr std::size_t bytes = 131072;
|
||||
auto* memory = static_cast<std::uint8_t*>(AllocateWatched(bytes, 65536));
|
||||
if (memory == nullptr) return false;
|
||||
GuestAllocations::Mutation().Add(memory, bytes, true, true, false);
|
||||
struct Cleanup {
|
||||
std::uint8_t* memory;
|
||||
~Cleanup() {
|
||||
GuestAllocations::Mutation().Remove(memory);
|
||||
ReleaseWatched(memory, bytes);
|
||||
}
|
||||
} cleanup{memory};
|
||||
const auto address = reinterpret_cast<std::uint64_t>(memory);
|
||||
std::memset(memory, 0x11, bytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
AgcDriver::GuestMemory::Unwatch(address, bytes);
|
||||
const auto before = AgcDriver::GuestMemory::UntrackedHint(address, bytes);
|
||||
Require(before != 0, "the hint mapping fixture has no write hint");
|
||||
Require(AgcDriver::GuestMemory::UntrackedHint(address, bytes) == before, "an unchanged hint range advanced its history");
|
||||
memory[4096] = 0x22;
|
||||
GuestAllocations::Mutation().Remove(memory);
|
||||
GuestAllocations::Mutation().Add(memory, bytes, true, true, false);
|
||||
const auto after = AgcDriver::GuestMemory::UntrackedHint(address, bytes);
|
||||
Require(after > before, "a cached hint walk hid a CPU store across allocation replacement");
|
||||
Require(memory[4096] == 0x22 && memory[bytes - 4096] == 0x11, "collecting replacement hints changed guest bytes");
|
||||
GuestAllocations::Mutation().Remove(memory);
|
||||
GuestAllocations::Mutation().Add(memory, bytes, true, true, false);
|
||||
Require(AgcDriver::GuestMemory::UntrackedHint(address, bytes) == after, "allocation replacement invented a CPU write hint");
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void deviceSelectionTests() {
|
||||
const auto properties = [](const char* name, VkPhysicalDeviceType type) {
|
||||
VkPhysicalDeviceProperties result{};
|
||||
@@ -2505,6 +2566,12 @@ int reimportBench(const Device& device) {
|
||||
constexpr std::size_t moves = 5;
|
||||
constexpr std::size_t page = 4096;
|
||||
constexpr std::size_t alignment = 2ull << 20u;
|
||||
constexpr std::size_t requiredImportBytes = bytes + inside;
|
||||
const auto availableImportBytes = HostImportBudgetRemaining(context);
|
||||
if (availableImportBytes < requiredImportBytes) {
|
||||
std::cout << "skipped, the re-import bench needs " << (requiredImportBytes >> 20u) << " MiB of simultaneous host imports; the current budget has " << (availableImportBytes >> 20u) << " MiB available\n";
|
||||
return 77;
|
||||
}
|
||||
struct Release {
|
||||
void* mapping = nullptr;
|
||||
std::int64_t physical = -1;
|
||||
@@ -2619,15 +2686,15 @@ int reimportBench(const Device& device) {
|
||||
throw;
|
||||
}
|
||||
gpu.lock();
|
||||
Require(importedAgain.empty(), "the kept side was imported again instead of trimmed after the " + importedAgain + " cuts");
|
||||
std::printf("re-import bench: every cut at the head, at the tail and inside the range kept the larger side by trimming the first import\n");
|
||||
Require(importedAgain == "head, tail, inside", "a cut kept memory imported across the changed host range instead of importing the remaining side again: " + importedAgain);
|
||||
std::printf("re-import bench: every cut at the head, at the tail and inside the range retired the original import before importing the remaining side\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int trimmedImportTests(const Device& device) {
|
||||
int hostImportCutTests(const Device& device) {
|
||||
const auto& context = device.GetContext();
|
||||
if (context.hostImportAlignment == 0 || context.settleQueue == VK_NULL_HANDLE || !AgcDriver::GuestMemory::WriteWatched()) {
|
||||
std::cout << "skipped, the trimmed import tests require host imports, a settle queue and write watching\n";
|
||||
std::cout << "skipped, the host import cut tests require host imports, a settle queue and write watching\n";
|
||||
return 77;
|
||||
}
|
||||
constexpr std::size_t bytes = 32ull << 20u;
|
||||
@@ -2644,14 +2711,13 @@ int trimmedImportTests(const Device& device) {
|
||||
std::size_t keptEnd;
|
||||
std::size_t smaller;
|
||||
std::size_t smallerEnd;
|
||||
bool trimmed;
|
||||
};
|
||||
constexpr std::array<Case, 5> cases{{
|
||||
{"a page made inaccessible inside the import, nearer its start", inside + guard, guard, false, inside + 2 * guard, bytes, 0, inside + guard, true},
|
||||
{"a page made inaccessible inside the import, nearer its end", bytes - inside - 2 * guard, guard, false, 0, bytes - inside - 2 * guard, bytes - inside - guard, bytes, true},
|
||||
{"an unmap of a 2 MiB run inside the import", inside, run, true, inside + run, bytes, 0, inside, true},
|
||||
{"an unmap of the import's first 2 MiB run", 0, run, true, run, bytes, 0, 0, true},
|
||||
{"an unmap of 1440 KiB at the start of the import's first 2 MiB run", 0, 1440ull << 10u, true, 1440ull << 10u, bytes, 0, 0, false},
|
||||
{"a page made inaccessible inside the import, nearer its start", inside + guard, guard, false, inside + 2 * guard, bytes, 0, inside + guard},
|
||||
{"a page made inaccessible inside the import, nearer its end", bytes - inside - 2 * guard, guard, false, 0, bytes - inside - 2 * guard, bytes - inside - guard, bytes},
|
||||
{"an unmap of a 2 MiB run inside the import", inside, run, true, inside + run, bytes, 0, inside},
|
||||
{"an unmap of the import's first 2 MiB run", 0, run, true, run, bytes, 0, 0},
|
||||
{"an unmap of 1440 KiB at the start of the import's first 2 MiB run", 0, 1440ull << 10u, true, 1440ull << 10u, bytes, 0, 0},
|
||||
}};
|
||||
struct Backing {
|
||||
void* mapping = nullptr;
|
||||
@@ -2679,15 +2745,14 @@ int trimmedImportTests(const Device& device) {
|
||||
const auto check = [&](const Case& test) {
|
||||
const std::string name = test.name;
|
||||
Backing backing;
|
||||
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, run, 0, &backing.physical) == 0, "allocate the trimmed import test backing");
|
||||
Require(sceKernelMapDirectMemory(&backing.mapping, bytes, 0x33, 0, backing.physical, run) == 0, "map the trimmed import test backing");
|
||||
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, run, 0, &backing.physical) == 0, "allocate the host import cut test backing");
|
||||
Require(sceKernelMapDirectMemory(&backing.mapping, bytes, 0x33, 0, backing.physical, run) == 0, "map the host import cut test backing");
|
||||
const auto address = reinterpret_cast<std::uint64_t>(backing.mapping);
|
||||
auto* memory = static_cast<std::byte*>(backing.mapping);
|
||||
for (std::size_t offset = 0; offset < bytes; offset += page) std::memset(memory + offset, static_cast<int>(0x40u + (offset / page) % 64u), 64);
|
||||
gpu.lock();
|
||||
const auto* whole = HostImportFor(context, address, bytes);
|
||||
Require(whole != nullptr && whole->base == address && whole->bytes == bytes && whole->alias == nullptr && !whole->unwatched, name + ": the range was not imported whole, settled and watched");
|
||||
const auto original = whole->memory;
|
||||
auto* cut = memory + test.offset;
|
||||
gpu.unlock();
|
||||
if (test.unmap) {
|
||||
@@ -2702,15 +2767,13 @@ int trimmedImportTests(const Device& device) {
|
||||
const auto end = address + test.keptEnd;
|
||||
const auto* kept = HostImportFor(context, begin, static_cast<std::size_t>(end - begin));
|
||||
Require(kept != nullptr && kept->base == begin && kept->base + kept->bytes == end, name + ": the larger side is not imported whole");
|
||||
const auto keptMemory = kept->memory;
|
||||
if (test.trimmed) Require(kept->memory == original, "the larger side of " + name + " was imported again instead of kept");
|
||||
else Require(kept->memory != original, "the side of " + name + " whose 2 MiB run the unmap replaced is served from the import that pinned the run's old pages");
|
||||
Require(kept->memoryBase == begin && kept->pinnedBytes == end - begin, "the side of " + name + " is served from the import that pinned the changed host range");
|
||||
requireImportServesGuest(context, recorder, *kept, begin, end, ++salt, "the larger side of " + name);
|
||||
if (test.smallerEnd != test.smaller) {
|
||||
const auto* smaller = HostImportFor(context, address + test.smaller, test.smallerEnd - test.smaller);
|
||||
Require(smaller != nullptr && smaller->base == address + test.smaller && smaller->bytes == test.smallerEnd - test.smaller && smaller->memory != original, "the smaller side of " + name + " is not imported apart from the larger one");
|
||||
Require(smaller != nullptr && smaller->base == address + test.smaller && smaller->bytes == test.smallerEnd - test.smaller && smaller->memoryBase == address + test.smaller && smaller->pinnedBytes == test.smallerEnd - test.smaller, "the smaller side of " + name + " is not imported apart from the larger one");
|
||||
requireImportServesGuest(context, recorder, *smaller, address + test.smaller, address + test.smallerEnd, ++salt, "the smaller side of " + name);
|
||||
Require(HostImportFor(context, begin, static_cast<std::size_t>(end - begin)) == kept && kept->memory == original, "importing the smaller side of " + name + " dropped the larger one");
|
||||
Require(HostImportFor(context, begin, static_cast<std::size_t>(end - begin)) == kept && kept->memoryBase == begin && kept->pinnedBytes == end - begin, "importing the smaller side of " + name + " replaced the safe import of the larger one");
|
||||
}
|
||||
if (test.unmap) {
|
||||
gpu.unlock();
|
||||
@@ -2722,26 +2785,25 @@ int trimmedImportTests(const Device& device) {
|
||||
const auto from = reinterpret_cast<std::uint64_t>(cut);
|
||||
const auto* remapped = HostImportFor(context, address, bytes);
|
||||
Require(remapped != nullptr && remapped->base == address && remapped->bytes == bytes, name + ": the range mapped again is not imported whole");
|
||||
Require(remapped->memory != keptMemory, "after " + name + " the pages mapped again are served from the import made before they were mapped again");
|
||||
Require(remapped->memoryBase == address && remapped->pinnedBytes == bytes, "after " + name + " the pages mapped again are served from the import that pinned the old ones");
|
||||
requireImportServesGuest(context, recorder, *remapped, from, from + test.length, ++salt, "the pages mapped again after " + name);
|
||||
requireImportServesGuest(context, recorder, *remapped, begin, end, ++salt, "the larger side after " + name + " and a new mapping");
|
||||
}
|
||||
cleanup();
|
||||
gpu.unlock();
|
||||
std::cout << "trimmed import: " << name << ": the larger side was " << (test.trimmed ? "kept" : "imported again") << " and the GPU and the write watch see the guest's bytes" << (test.unmap ? ", also after the pages were mapped again" : "") << '\n';
|
||||
std::cout << "host import after cut: " << name << ": the changed import was retired before the remaining range was imported, and the GPU and write watch see the guest's bytes" << (test.unmap ? ", also after the pages were mapped again" : "") << '\n';
|
||||
};
|
||||
const auto remapWhileTrimmed = [&] {
|
||||
const std::string name = "an unmap and a new mapping of the kept side while the import waits trimmed";
|
||||
const std::string name = "an unmap and a new mapping of the retained side while the old import is retired";
|
||||
Backing backing;
|
||||
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, run, 0, &backing.physical) == 0, "allocate the trimmed import test backing");
|
||||
Require(sceKernelMapDirectMemory(&backing.mapping, bytes, 0x33, 0, backing.physical, run) == 0, "map the trimmed import test backing");
|
||||
Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, run, 0, &backing.physical) == 0, "allocate the host import cut test backing");
|
||||
Require(sceKernelMapDirectMemory(&backing.mapping, bytes, 0x33, 0, backing.physical, run) == 0, "map the host import cut test backing");
|
||||
const auto address = reinterpret_cast<std::uint64_t>(backing.mapping);
|
||||
auto* memory = static_cast<std::byte*>(backing.mapping);
|
||||
for (std::size_t offset = 0; offset < bytes; offset += page) std::memset(memory + offset, static_cast<int>(0x40u + (offset / page) % 64u), 64);
|
||||
gpu.lock();
|
||||
const auto* whole = HostImportFor(context, address, bytes);
|
||||
Require(whole != nullptr && whole->base == address && whole->bytes == bytes && whole->alias == nullptr && !whole->unwatched, name + ": the range was not imported whole, settled and watched");
|
||||
const auto original = whole->memory;
|
||||
gpu.unlock();
|
||||
Require(sceKernelMprotect(memory, run, 0x03) == 0, name + ": the protection of the head failed");
|
||||
Require(sceKernelMunmap(memory + run, bytes - run) == 0, name + ": the unmap of the kept side failed");
|
||||
@@ -2757,10 +2819,10 @@ int trimmedImportTests(const Device& device) {
|
||||
const auto* remapped = HostImportFor(context, begin, static_cast<std::size_t>(end - begin));
|
||||
Require(remapped != nullptr && remapped->base == begin && remapped->base + remapped->bytes == end, name + ": the pages mapped again are not imported whole");
|
||||
requireImportServesGuest(context, recorder, *remapped, begin, end, ++salt, "the pages mapped again after " + name);
|
||||
Require(remapped->memory != original, "after " + name + " the pages mapped again are served from the import that pinned the old ones");
|
||||
Require(remapped->memoryBase == begin && remapped->pinnedBytes == end - begin, "after " + name + " the pages mapped again are served from the import that pinned the old ones");
|
||||
cleanup();
|
||||
gpu.unlock();
|
||||
std::cout << "trimmed import: " << name << ": the pages mapped again were imported again and the GPU and the write watch see the guest's bytes\n";
|
||||
std::cout << "host import cut: " << name << ": the pages mapped again were imported again and the GPU and the write watch see the guest's bytes\n";
|
||||
};
|
||||
gpu.unlock();
|
||||
try {
|
||||
@@ -2771,7 +2833,7 @@ int trimmedImportTests(const Device& device) {
|
||||
throw;
|
||||
}
|
||||
gpu.lock();
|
||||
std::cout << "Trimmed host import tests passed\n";
|
||||
std::cout << "Host import cut lifetime tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -4677,6 +4739,79 @@ bool unitShadowTests(const Device& device, Recorder& recorder) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void idlePublishWaitTests(const Device& device, Recorder& recorder) {
|
||||
using namespace AgcDriver::GuestMemory;
|
||||
const auto& context = device.GetContext();
|
||||
constexpr std::uint64_t unit = 65536;
|
||||
constexpr std::size_t bytes = 8 * unit;
|
||||
void* block = AllocateWatched(bytes, 65536);
|
||||
Require(block != nullptr, "unit shadows are on without write watching");
|
||||
struct Release {
|
||||
void* block;
|
||||
~Release() { ReleaseWatched(block, bytes); }
|
||||
} release{block};
|
||||
std::memset(block, 0x11, bytes);
|
||||
const auto address = reinterpret_cast<std::uint64_t>(block);
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Add(block, bytes, true, true, true);
|
||||
}
|
||||
struct Unregister {
|
||||
const Context& context;
|
||||
void* block;
|
||||
std::uint64_t address;
|
||||
~Unregister() {
|
||||
{
|
||||
GuestAllocations::Mutation mutation;
|
||||
mutation.Remove(block);
|
||||
}
|
||||
HostImportFor(context, address, bytes);
|
||||
}
|
||||
} unregister{context, block, address};
|
||||
const auto* import = HostImportFor(context, address, bytes);
|
||||
if (import == nullptr || !Watched(address, bytes)) {
|
||||
std::cout << "host import of the idle publish test block refused or not watched: idle publish wait not tested\n";
|
||||
return;
|
||||
}
|
||||
recorder.Sync();
|
||||
CollectWritesUncached(address, bytes);
|
||||
auto* words = static_cast<std::uint8_t*>(block);
|
||||
const auto copyBuffer = context.Function<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer");
|
||||
const auto retile = [&](std::uint64_t begin, std::uint8_t value) {
|
||||
auto destination = ShadowDestinationFor(context, *import, begin, begin + unit);
|
||||
Require(destination.has_value() && destination->seedUnits.empty(), "a whole-unit retile piece has no destination");
|
||||
auto pattern = std::make_shared<Buffer>(context, static_cast<std::size_t>(unit), VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
std::memset(pattern->Bytes().data(), value, pattern->Bytes().size());
|
||||
const auto commands = recorder.Commands();
|
||||
recorder.Keep(pattern);
|
||||
recorder.Keep(destination->slab);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
const VkBufferCopy piece{0, destination->offset, unit};
|
||||
copyBuffer(commands, pattern->Handle(), destination->buffer, 1, &piece);
|
||||
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
recorder.MarkCovered(VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
||||
const ShadowedRange range{begin, begin + unit, destination->slab};
|
||||
MarkShadowed(*import, std::span(&range, 1), TrackerGeneration());
|
||||
Require(AnyShadowedOverlaps(begin, unit), "the retiled unit is not shadowed");
|
||||
};
|
||||
retile(address + 2 * unit, 0x22);
|
||||
retile(address + 3 * unit, 0x33);
|
||||
StorageTexture::FlushAllPending("idle");
|
||||
Require(!AnyShadowedOverlaps(address + 2 * unit, static_cast<std::size_t>(2 * unit)), "the idle flush did not publish the shadowed units");
|
||||
Require(!recorder.PendingWriteOverlaps(address + 2 * unit, static_cast<std::size_t>(2 * unit)), "the idle flush returned before its publish's copies completed");
|
||||
words[2 * unit] = 0x44;
|
||||
words[4 * unit - 1] = 0x45;
|
||||
recorder.Sync();
|
||||
Require(words[2 * unit] == 0x44 && words[4 * unit - 1] == 0x45, "a CPU store made after the idle flush returned was overwritten by its publish");
|
||||
Require(words[2 * unit + 1] == 0x22 && words[3 * unit - 1] == 0x22 && words[3 * unit] == 0x33 && words[1 * unit] == 0x11 && words[4 * unit] == 0x11, "the idle publish's bytes are wrong");
|
||||
retile(address + 5 * unit, 0x55);
|
||||
StorageTexture::FlushAllPending("label");
|
||||
Require(recorder.PendingWriteOverlaps(address + 5 * unit, unit), "a publish of a flush other than the idle one was waited");
|
||||
recorder.Sync();
|
||||
Require(words[5 * unit] == 0x55 && words[6 * unit - 1] == 0x55, "the label flush's publish did not land");
|
||||
std::cout << "idle publish wait verified\n";
|
||||
}
|
||||
|
||||
void storageRefreshTests(const Device& device, Recorder& recorder, bool watched) {
|
||||
const auto& base = device.GetContext();
|
||||
if (base.hostImportAlignment == 0) {
|
||||
@@ -8866,8 +9001,8 @@ bool vertexTargetRangeTests(const Device& device, Recorder& recorder, bool impor
|
||||
const std::array range{std::pair{address + unit, address + 2 * unit}};
|
||||
Require(StorageTexture::AnyPendingOverlaps(range), "the color producer did not leave GPU results pending");
|
||||
};
|
||||
const auto attributeFor = [](std::uint64_t base, std::uint32_t stride, std::uint32_t fetchIndex) {
|
||||
return ShaderRecompiler::VertexAttribute{0, 4, {{static_cast<std::uint32_t>(base), static_cast<std::uint32_t>(base >> 32u) | (stride << 16u), static_cast<std::uint32_t>(bytes / 16), 77u << 12u}}, fetchIndex};
|
||||
const auto attributeFor = [](std::uint64_t base, std::uint32_t stride, std::uint32_t fetchIndex, std::uint32_t outOfBoundsSelect = 0) {
|
||||
return ShaderRecompiler::VertexAttribute{0, 4, {{static_cast<std::uint32_t>(base), static_cast<std::uint32_t>(base >> 32u) | (stride << 16u), static_cast<std::uint32_t>(bytes / 16), (77u << 12u) | (outOfBoundsSelect << 28u)}}, fetchIndex};
|
||||
};
|
||||
const auto check = [&](std::uint32_t edge) {
|
||||
std::vector<std::byte> texels(64 * 64 * 4);
|
||||
@@ -8938,9 +9073,12 @@ bool vertexTargetRangeTests(const Device& device, Recorder& recorder, bool impor
|
||||
reject([&] { Draw(context, consumer, indexed, shaders); }, "index buffer aliases the render target");
|
||||
}
|
||||
reject([&] { Draw(context, consumer, triangle, shaders); }, "vertex buffer aliases the render target");
|
||||
vertex.vertexAttributes = {attributeFor(address, 0, fetchIndex)};
|
||||
vertex.vertexAttributes = {attributeFor(address, 0, fetchIndex, 3)};
|
||||
reject([&] { Draw(context, consumer, draw, shaders); }, "vertex buffer aliases the render target");
|
||||
}
|
||||
vertex.vertexAttributes = {attributeFor(address, 0, 0)};
|
||||
Draw(context, consumer, triangle, shaders);
|
||||
WaitForOptimizedPipelines(context.device);
|
||||
std::cout << "Vertex range and pending color producer tests passed (" << (imported ? "imported" : "copied") << ")\n";
|
||||
return true;
|
||||
}
|
||||
@@ -9945,6 +10083,16 @@ void Run(Context context) {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--collect-mapping-history-only") {
|
||||
if (!collectMappingHistoryTests()) return 77;
|
||||
std::cout << "collect mapping history tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--hint-mapping-history-only") {
|
||||
if (!hintMappingHistoryTests()) return 77;
|
||||
std::cout << "hint mapping history tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--partial-watch-restoration-only") {
|
||||
if (!partialWatchRestorationTests()) return 77;
|
||||
std::cout << "partial private write-watch restoration tests passed\n";
|
||||
@@ -9962,7 +10110,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
std::unique_ptr<Device> created;
|
||||
try {
|
||||
created = std::make_unique<Device>(argc == 2 && (std::string_view(argv[1]) == "--import-watch-other-family-only" || std::string_view(argv[1]) == "--deferred-settle-other-family-only"));
|
||||
created = std::make_unique<Device>(argc == 2 && (std::string_view(argv[1]) == "--import-watch-other-family-only" || std::string_view(argv[1]) == "--deferred-settle-other-family-only" || std::string_view(argv[1]) == "--host-import-lifetime-other-family-only"));
|
||||
} catch (const std::exception& error) {
|
||||
if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr) throw;
|
||||
std::cout << "skipped, no usable Vulkan device: " << error.what() << '\n';
|
||||
@@ -9987,6 +10135,13 @@ int main(int argc, char** argv) {
|
||||
return 0;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--host-import-lifetime-other-family-only") {
|
||||
if (device.GetContext().settleFamily == device.GetContext().queueFamily) {
|
||||
std::cout << "skipped, no other queue family to settle imports on\n";
|
||||
return 77;
|
||||
}
|
||||
return RunHostImportLifetimeTests(device.GetContext());
|
||||
}
|
||||
if (argc == 2 && (std::string_view(argv[1]) == "--deferred-settle-only" || std::string_view(argv[1]) == "--deferred-settle-other-family-only")) {
|
||||
if (std::string_view(argv[1]) == "--deferred-settle-other-family-only" && device.GetContext().settleFamily == device.GetContext().queueFamily) {
|
||||
std::cout << "skipped, no other queue family to settle imports on\n";
|
||||
@@ -9999,7 +10154,7 @@ int main(int argc, char** argv) {
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--settle-retire-only") return settleRetireTests(device) ? 0 : 77;
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--settle-storage-image-only") return settleStorageImageTests(device) ? 0 : 77;
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--reimport-bench") return reimportBench(device);
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--trimmed-import-only") return trimmedImportTests(device);
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--host-import-cut-only") return hostImportCutTests(device);
|
||||
#endif
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--host-import-retirement-only") {
|
||||
const auto alignment = device.GetContext().hostImportAlignment;
|
||||
@@ -10164,6 +10319,14 @@ int main(int argc, char** argv) {
|
||||
parkedImportUnmapTests(device);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--idle-publish-wait-only") {
|
||||
if (!AgcDriver::Graphics::UnitShadowEnabled() || device.GetContext().hostImportAlignment == 0) {
|
||||
std::cout << "skipped, unit shadows or host imports are unavailable\n";
|
||||
return 77;
|
||||
}
|
||||
idlePublishWaitTests(device, recorder);
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::string_view(argv[1]) == "--unit-shadow-only") {
|
||||
if (!AgcDriver::Graphics::UnitShadowEnabled() || device.GetContext().hostImportAlignment == 0) {
|
||||
std::cout << "skipped, unit shadows or host imports are unavailable\n";
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/RobustBufferAccess.hpp"
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Case {
|
||||
VkDriverId driver;
|
||||
bool usable;
|
||||
};
|
||||
|
||||
constexpr Case Cases[]{
|
||||
{VK_DRIVER_ID_AMD_PROPRIETARY, false},
|
||||
{VK_DRIVER_ID_AMD_OPEN_SOURCE, true},
|
||||
{VK_DRIVER_ID_MESA_RADV, true},
|
||||
{VK_DRIVER_ID_NVIDIA_PROPRIETARY, true},
|
||||
{VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, true},
|
||||
{VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, true},
|
||||
{VK_DRIVER_ID_MESA_LLVMPIPE, true},
|
||||
{VK_DRIVER_ID_MOLTENVK, true},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
int failures = 0;
|
||||
for (const auto& item : Cases) {
|
||||
if (AgcDriver::RobustBufferAccess2Usable(item.driver) != item.usable) {
|
||||
std::fprintf(stderr, "driver %d: expected robustBufferAccess2 to be %s\n", static_cast<int>(item.driver), item.usable ? "usable" : "unusable");
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -3551,6 +3551,40 @@ void verifyWaterfallImageTable() {
|
||||
require(!SrtWalker().ValidateRuntimeValue(planA, &firstOther), "waterfall image table: non-implied condition was accepted by validator");
|
||||
}
|
||||
|
||||
void verifySplitWaterfallImageTable() {
|
||||
using namespace ShaderRecompiler;
|
||||
namespace Abi = ImageTableAbi;
|
||||
static TableTexture texture;
|
||||
alignas(256) static std::array<std::uint32_t, 0x3000> root{};
|
||||
static std::array<std::uint32_t, 4> output{};
|
||||
const auto outputV = TableV(output.data(), 0u, static_cast<std::uint32_t>(sizeof(output)));
|
||||
std::copy(outputV.begin(), outputV.end(), root.begin() + 4);
|
||||
const auto descriptor = TableT(texture.bytes.data(), TableFormat8888UNorm, TableType2D);
|
||||
std::copy(descriptor.begin(), descriptor.end(), root.begin() + 0x40u / 4u);
|
||||
const auto base = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(root.data()));
|
||||
const std::array<std::uint32_t, 2> userData{static_cast<std::uint32_t>(base), static_cast<std::uint32_t>(base >> 32u)};
|
||||
const auto code = [](std::uint32_t addend) {
|
||||
return std::vector<std::uint32_t>{0xf4080700u, 0xfa000010u, 0xf4080800u, 0xfa000020u, 0x7da80086u, 0x7e220500u, 0xd7460001u, 0x00010b00u | (addend << 18u), 0x7e260501u, 0x8f128511u,
|
||||
0xf4080900u, 0x24000040u, 0xf4080a00u, 0x26000040u, 0xf09c0f08u, 0x01090000u, 0xe0700000u, 0x80070000u, 0xbf810000u};
|
||||
};
|
||||
const auto split = code(0x90u);
|
||||
auto request = TableRequest(split, userData);
|
||||
request.shader.codeAddress = 0x3a000u;
|
||||
const auto plan = GetResourcePlan(request);
|
||||
require(plan->info.images.size() == 1u && plan->info.images[0].table == 0u, "split waterfall image table: the T# was not planned as a table");
|
||||
const auto& column = *plan->descriptorSources.at(plan->info.images[0].source).tableColumn;
|
||||
require(column.address && column.stride == 32u && column.offset == 0x40u && column.dwordCount == 8u && column.foldedCarry == 16u, "split waterfall image table: the column is wrong");
|
||||
const auto compiled = CompileTable(request);
|
||||
const auto& snapshot = compiled.capture->snapshot.tables.tables.at(0);
|
||||
require((snapshot.codes[0] & Abi::PoisonFlag) == 0u && snapshot.words.at(snapshot.codes[0]).dwords == descriptor, "split waterfall image table: record 0 was not read whole");
|
||||
for (const std::uint32_t addend : {0xa0u, 0x88u}) {
|
||||
const auto otherCode = code(addend);
|
||||
auto other = TableRequest(otherCode, userData);
|
||||
other.shader.codeAddress = 0x3b000u + addend * 0x100u;
|
||||
expectFailure([&] { static_cast<void>(GetResourcePlan(other)); }, "GetImageResource dword 0 is not a valid runtime value", "split waterfall image table: a second half outside the record was accepted");
|
||||
}
|
||||
}
|
||||
|
||||
void verifyDescriptorPhis() {
|
||||
using namespace ShaderRecompiler;
|
||||
constexpr std::uint32_t Format8888UNorm = 56;
|
||||
@@ -7622,6 +7656,7 @@ int main(int argc, char** argv) {
|
||||
verifyChainedTableOffset();
|
||||
verifyPointerChainImageTable();
|
||||
verifyWaterfallImageTable();
|
||||
verifySplitWaterfallImageTable();
|
||||
std::cout << "Image table keys, maps, records and registration passed\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -7685,6 +7720,7 @@ int main(int argc, char** argv) {
|
||||
verifyChainedTableOffset();
|
||||
verifyPointerChainImageTable();
|
||||
verifyWaterfallImageTable();
|
||||
verifySplitWaterfallImageTable();
|
||||
verifyDescriptorPhis();
|
||||
verifyUniformTableImage();
|
||||
verifyLaneSelectedDescriptors();
|
||||
|
||||
@@ -386,6 +386,29 @@ void RunTextureTilingTests() {
|
||||
Require(dense.pitchBytes == 1024 && CoveredMipBytes(TextureTileMode::kLinear, 4, dense) == std::vector<std::pair<std::uint64_t, std::uint64_t>>{{0, 3072}}, "linear rows without padding are not one range");
|
||||
}
|
||||
|
||||
{
|
||||
const auto* equation = FindTextureSwizzleEquation(XorSwizzleMode(TextureTileMode::kZ64KBX), 4u);
|
||||
Require(equation != nullptr, "missing the 64KB Z_X swizzle equation of 4-byte elements");
|
||||
for (const auto mask : equation->bits) Require((mask & 0x00f80f80u) == 0u, "a 64KB Z_X address bit of a 4-byte element reads x or y bits past one 128x128 tile");
|
||||
GuestTextureResource plane{};
|
||||
plane.width = 3840;
|
||||
plane.height = 2160;
|
||||
plane.mipCount = 1;
|
||||
plane.tileMode = TextureTileMode::kZ64KBX;
|
||||
plane.dimension = TextureDimension::k2D;
|
||||
plane.format = 22;
|
||||
const auto geometry = DescribeSurface(plane);
|
||||
const auto& mip = geometry.mips.front();
|
||||
const auto block = ThinBlockLayout(TextureTileMode::kZ64KBX, 4u);
|
||||
Require(block[0] == 65536u && block[1] == 128u && block[2] == 128u, "a 64KB Z_X tile of 4-byte elements is not 128x128");
|
||||
Require(!mip.tail && mip.tiledOffset == 0u && mip.blocksPerRow == 30u && geometry.guestBytes == 510ull * 65536ull, "a 3840x2160 D32 Z_X plane is not 30x17 tiles");
|
||||
const std::array<std::pair<std::uint32_t, std::uint32_t>, 8> samples{{{0, 0}, {127, 127}, {128, 0}, {3839, 0}, {0, 128}, {1000, 700}, {2047, 1151}, {3839, 2159}}};
|
||||
for (const auto& [x, y] : samples) {
|
||||
const auto offset = mip.tiledOffset + (static_cast<std::uint64_t>(y / block[2]) * mip.blocksPerRow + x / block[1]) * block[0] + equationOffset(*equation, x, y, 0);
|
||||
Require(offset / 65536u == static_cast<std::uint64_t>(y >> 7u) * 30u + (x >> 7u), "element (" + std::to_string(x) + ", " + std::to_string(y) + ") of a 3840x2160 D32 Z_X plane lies outside the 64 KiB unit of its 128x128 tile");
|
||||
}
|
||||
}
|
||||
|
||||
reject([] { ComputeSurfaceSize({}, 1); }, "empty mip chain");
|
||||
reject([] { ComputeSurfaceSize(ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 1), 0); }, "zero array layers");
|
||||
|
||||
|
||||
@@ -151,10 +151,25 @@ void run() {
|
||||
const auto zeroStride = live.TryGet(101, input);
|
||||
Require(VertexFetchOutOfRange(input[0]), "zero records did not select zero fill");
|
||||
input[0].resource.fields[2] = 64;
|
||||
Require(live.TryGet(101, input) == zeroStride && !VertexFetchOutOfRange(input[0]) && VertexBufferExtent(input[0]) == 64, "cached zero-stride layout froze the live zero-fill decision");
|
||||
Require(live.TryGet(101, input) == zeroStride && !VertexFetchOutOfRange(input[0]) && VertexBufferExtent(input[0]) == 16, "cached zero-stride layout froze the live zero-fill decision");
|
||||
input[0].resource.fields[2] = 0;
|
||||
Require(live.TryGet(101, input) == zeroStride && VertexFetchOutOfRange(input[0]), "record count zero reused a live fetch");
|
||||
input[0] = attribute();
|
||||
input[0].resource.fields[1] = 0;
|
||||
input[0].resource.fields[3] |= 3u << 28u;
|
||||
input[0].resource.fields[2] = 16;
|
||||
const auto wholeFetch = live.TryGet(105, input);
|
||||
for (const auto records : {0u, 8u, 15u}) {
|
||||
input[0].resource.fields[2] = records;
|
||||
Require(live.TryGet(105, input) == wholeFetch && VertexFetchOutOfRange(input[0]), "a fetch that only partly fits was not read as zero");
|
||||
}
|
||||
for (const auto records : {16u, 17u, 0xffffffffu}) {
|
||||
input[0].resource.fields[2] = records;
|
||||
Require(live.TryGet(105, input) == wholeFetch && !VertexFetchOutOfRange(input[0]) && VertexBufferExtent(input[0]) == 16, "a fetch inside the byte range was read as zero or sized by NUM_RECORDS");
|
||||
}
|
||||
input[0].resource.fields[3] &= ~(3u << 28u);
|
||||
Require(VertexFetchOutOfRange(input[0]), "OOB_SELECT 0 read a zero-stride fetch");
|
||||
input[0] = attribute();
|
||||
input[0].formatComponents = 1;
|
||||
const auto narrow = live.TryGet(102, input);
|
||||
input[0].formatComponents = 2;
|
||||
|
||||
@@ -71,7 +71,7 @@ int main() {
|
||||
std::uint32_t provokingVertex;
|
||||
bool accepted;
|
||||
};
|
||||
constexpr std::array meshExpectations{MeshExpectation{0u, 0u, false}, MeshExpectation{1u, 0u, false}, MeshExpectation{2u, 0u, true}, MeshExpectation{2u, 1u, true}, MeshExpectation{2u, 2u, false}, MeshExpectation{3u, 2u, true}, MeshExpectation{3u, 3u, false}, MeshExpectation{4u, 0u, false}, MeshExpectation{4u, 3u, false}};
|
||||
constexpr std::array meshExpectations{MeshExpectation{0u, 0u, false}, MeshExpectation{1u, 0u, true}, MeshExpectation{1u, 1u, false}, MeshExpectation{2u, 0u, true}, MeshExpectation{2u, 1u, true}, MeshExpectation{2u, 2u, false}, MeshExpectation{3u, 2u, true}, MeshExpectation{3u, 3u, false}, MeshExpectation{4u, 0u, false}, MeshExpectation{4u, 3u, false}};
|
||||
const auto meshWith = [&](const MeshExpectation& expectation) {
|
||||
auto varied = mesh;
|
||||
varied.outputPrimitiveSize = expectation.outputPrimitiveSize;
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef __GLIBC__
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -1153,6 +1156,38 @@ void CheckImportWindowOverUnwatchedBlock() {
|
||||
Require(after != 0 && !UnchangedSince(base, Block, after), "a store after an import window over an unwatched block was missed");
|
||||
}
|
||||
|
||||
void CheckImportWindowFootprint() {
|
||||
#ifdef __GLIBC__
|
||||
constexpr std::size_t bytes = std::size_t{64} << 20;
|
||||
void* memory = AllocateWatched(bytes);
|
||||
const auto base = reinterpret_cast<std::uint64_t>(memory);
|
||||
std::memset(memory, 0x5a, bytes);
|
||||
Require(CollectWritesUncached(base, bytes) != 0, "the footprint import window block is not collected");
|
||||
const auto heapBytes = [] {
|
||||
const auto info = mallinfo2();
|
||||
return info.uordblks + info.hblkhd;
|
||||
};
|
||||
const auto before = heapBytes();
|
||||
std::size_t during = 0;
|
||||
Require(ImportWatched(base, bytes, [&] {
|
||||
during = heapBytes();
|
||||
return true;
|
||||
}), "the footprint import window import failed");
|
||||
Require(Watched(base, bytes), "the footprint import window left its range unwatched");
|
||||
const auto held = during > before ? during - before : 0;
|
||||
Require(held <= bytes / 64, "an import window over " + std::to_string(bytes >> 20) + " MiB held " + std::to_string(held >> 20) + " MiB of host memory during the import: it copies the range instead of hashing its pages");
|
||||
auto* plain = static_cast<volatile std::uint8_t*>(memory);
|
||||
const auto synced = CollectWritesUncached(base, bytes);
|
||||
Require(ImportWatched(base, bytes, [&] {
|
||||
plain[0x100] = 0x5a;
|
||||
plain[Block + 0x100] = 0x5b;
|
||||
return true;
|
||||
}), "the footprint import window re-import failed");
|
||||
Require(UnchangedSince(base, Block, synced), "a page a hashed import window resolved without changing its bytes reads as written");
|
||||
Require(WrittenSince(base + Block, Block, synced), "a store of new bytes within a hashed import window was missed");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProtectPage(std::uint64_t address, bool readable) {
|
||||
#ifdef _WIN32
|
||||
DWORD previous = 0;
|
||||
@@ -1442,6 +1477,7 @@ int main() {
|
||||
CheckImportWindowOtherThread();
|
||||
CheckImportWindowOverUnwatchedBlock();
|
||||
CheckStoreAcrossImports();
|
||||
CheckImportWindowFootprint();
|
||||
CheckUnwatchedImport();
|
||||
CheckCommittedRanges();
|
||||
CheckAdoptLatestEpoch();
|
||||
|
||||
@@ -68,6 +68,8 @@ alignas(256) std::array<std::uint32_t, 16> PixelCode{};
|
||||
alignas(256) std::array<std::uint32_t, Threads> Output{};
|
||||
|
||||
void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode);
|
||||
void OverlappingArrays(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode, bool cpuWrite = false);
|
||||
void OverlappingArrayLastSlice(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode);
|
||||
|
||||
std::uint64_t AddressOf(const void* data) {
|
||||
return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(data));
|
||||
@@ -81,22 +83,24 @@ std::string Hex(std::uint32_t value) {
|
||||
|
||||
class GuestBlock {
|
||||
public:
|
||||
explicit GuestBlock(std::size_t registeredBytes = SurfaceBytes, std::size_t registeredOffset = 0) : registeredOffset(registeredOffset) {
|
||||
explicit GuestBlock(std::size_t registeredBytes = SurfaceBytes, std::size_t registeredOffset = 0, bool commitAll = true) : registeredOffset(registeredOffset) {
|
||||
#ifdef _WIN32
|
||||
block = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, LayerBytes));
|
||||
if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, SurfaceBytes, PAGE_READWRITE, SurfaceBytes);
|
||||
if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block + (commitAll ? 0 : registeredOffset), commitAll ? SurfaceBytes : registeredBytes, PAGE_READWRITE, commitAll ? SurfaceBytes : registeredBytes);
|
||||
#else
|
||||
void* mapped = mmap(nullptr, SurfaceBytes + LayerBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
void* mapped = mmap(nullptr, SurfaceBytes + LayerBytes, commitAll ? PROT_READ | PROT_WRITE : PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (mapped != MAP_FAILED) {
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(mapped);
|
||||
const auto aligned = (begin + LayerBytes - 1) & ~(static_cast<std::uintptr_t>(LayerBytes) - 1);
|
||||
if (aligned != begin) munmap(mapped, aligned - begin);
|
||||
if (aligned + SurfaceBytes != begin + SurfaceBytes + LayerBytes) munmap(reinterpret_cast<void*>(aligned + SurfaceBytes), begin + LayerBytes - aligned);
|
||||
block = reinterpret_cast<std::uint8_t*>(aligned);
|
||||
if (AgcDriver::GuestMemory::WriteWatched()) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, SurfaceBytes);
|
||||
if (!commitAll) Require(mprotect(block + registeredOffset, registeredBytes, PROT_READ | PROT_WRITE) == 0, "cannot commit the active array slice");
|
||||
if (AgcDriver::GuestMemory::WriteWatched()) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block + (commitAll ? 0 : registeredOffset), commitAll ? SurfaceBytes : registeredBytes);
|
||||
}
|
||||
#endif
|
||||
Require(block != nullptr, "array layer target: cannot allocate the guest block");
|
||||
if (!commitAll) GuestAllocations::GuestAllocationsInvalidate_nid_postfix(reinterpret_cast<std::uintptr_t>(block), SurfaceBytes);
|
||||
GuestAllocations::Mutation().Add(block + registeredOffset, registeredBytes, true, true, true);
|
||||
}
|
||||
|
||||
@@ -170,10 +174,10 @@ void Load(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code,
|
||||
Dispatch(device, code, userData);
|
||||
}
|
||||
|
||||
void LoadLayer(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t layer, std::uint32_t swizzleMode) {
|
||||
void LoadLayer(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t layer, std::uint32_t swizzleMode, std::uint32_t lastLayer = Layers - 1u) {
|
||||
auto& code = LoadCodes[layer];
|
||||
code = {0x7e020280, 0x7e040280u | layer, 0xf0001128, 0x00010300, 0x34080082, 0xbf8c3f70, 0xe0701000, 0x80000304, 0xbf810000};
|
||||
Load(device, code, SurfaceDescriptor(surface, swizzleMode, Type2DArray, Layers - 1u));
|
||||
Load(device, code, SurfaceDescriptor(surface, swizzleMode, Type2DArray, lastLayer));
|
||||
}
|
||||
|
||||
void Sample2D(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t swizzleMode) {
|
||||
@@ -187,23 +191,23 @@ void RequireOutput(std::uint32_t inside, std::uint32_t outside, const std::strin
|
||||
}
|
||||
}
|
||||
|
||||
AgcDriver::Graphics::ColorTarget DecodeTarget(std::uint64_t address, std::uint32_t slice, std::uint32_t swizzleMode, bool collapsed) {
|
||||
AgcDriver::Graphics::ColorTarget DecodeTarget(std::uint64_t address, std::uint32_t slice, std::uint32_t swizzleMode, bool collapsed, std::uint32_t lastSlice) {
|
||||
AgcDriver::Registers cx;
|
||||
cx[0x318] = static_cast<std::uint32_t>(address >> 8u);
|
||||
cx[0x31b] = slice | (slice << 13u);
|
||||
cx[0x31c] = Color32FloatInfo;
|
||||
cx[0x31d] = 0;
|
||||
cx[0x3b0] = ((Side - 1u) << 14u) | (Side - 1u);
|
||||
cx[0x3b8] = (TiledAttrib3 & ~(0x1fu << 14u)) | (swizzleMode << 14u) | (collapsed ? 0u : Layers - 1u);
|
||||
cx[0x3b8] = (TiledAttrib3 & ~(0x1fu << 14u)) | (swizzleMode << 14u) | (collapsed ? 0u : lastSlice);
|
||||
cx[0x390] = static_cast<std::uint32_t>(address >> 40u);
|
||||
const auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0);
|
||||
Require(color.format == VK_FORMAT_R32_SFLOAT && color.bytes == LayerBytes && color.address == address + slice * LayerBytes, "the COLOR_32 float view did not decode as a 128x128 R32 slice of the array");
|
||||
if (collapsed) Require(color.arrayAddress == 0 && color.arrayLastSlice == 0, "a collapsed slice view unexpectedly describes a whole array");
|
||||
else Require(color.arrayAddress == address && color.arrayLastSlice == Layers - 1u && color.arraySlice == slice, "the color view did not keep its array and slice");
|
||||
else Require(color.arrayAddress == address && color.arrayLastSlice == lastSlice && color.arraySlice == slice, "the color view did not keep its array and slice");
|
||||
return color;
|
||||
}
|
||||
|
||||
void DrawSlice(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t slice, std::uint32_t swizzleMode, bool collapsed) {
|
||||
void DrawSlice(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint32_t slice, std::uint32_t swizzleMode, bool collapsed, std::uint32_t lastSlice = Layers - 1u, std::uint32_t width = ScissorWidth) {
|
||||
const auto target = device.Target();
|
||||
constexpr std::uint32_t waveSize = 64;
|
||||
std::size_t at = 0;
|
||||
@@ -230,7 +234,7 @@ void DrawSlice(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint
|
||||
const auto vertexResult = ShaderRecompiler::Recompile(vertex);
|
||||
const auto vertexPush = static_cast<std::uint32_t>(vertexResult.pushConstants.size());
|
||||
|
||||
const auto color = DecodeTarget(surface, slice, swizzleMode, collapsed);
|
||||
const auto color = DecodeTarget(surface, slice, swizzleMode, collapsed, lastSlice);
|
||||
VkPipelineColorBlendAttachmentState blend{};
|
||||
blend.colorWriteMask = 0xf;
|
||||
AgcDriver::Graphics::State state{};
|
||||
@@ -242,7 +246,7 @@ void DrawSlice(AgcDriver::VulkanDevice& device, std::uint64_t surface, std::uint
|
||||
state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
state.viewport = {0, static_cast<float>(Side), static_cast<float>(Side), -static_cast<float>(Side), 0, 1};
|
||||
state.negativeOneToOne = false;
|
||||
state.scissor = {{0, 0}, {ScissorWidth, Side}};
|
||||
state.scissor = {{0, 0}, {width, Side}};
|
||||
state.cullMode = VK_CULL_MODE_NONE;
|
||||
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
state.blends = {blend};
|
||||
@@ -296,6 +300,9 @@ void Run(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t sw
|
||||
RequireOutput(drawn, Initial, "slice " + std::to_string(slice) + " uploaded again from guest memory");
|
||||
AgcDriver::Graphics::ClearCachedTextures(device.Device());
|
||||
SampleSliceZero(device, texels, swizzleMode);
|
||||
OverlappingArrays(device, texels, swizzleMode);
|
||||
OverlappingArrays(device, texels, swizzleMode, true);
|
||||
OverlappingArrayLastSlice(device, texels, swizzleMode);
|
||||
}
|
||||
|
||||
void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode) {
|
||||
@@ -315,6 +322,32 @@ void SampleSliceZero(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std:
|
||||
AgcDriver::Graphics::ClearCachedTextures(device.Device());
|
||||
}
|
||||
|
||||
void OverlappingArrayLastSlice(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode) {
|
||||
const auto neighbourAddress = AddressOf(texels);
|
||||
const auto surface = neighbourAddress + LayerBytes;
|
||||
const auto cpuValue = std::bit_cast<std::uint32_t>(0.125f);
|
||||
const auto drawn = std::bit_cast<std::uint32_t>(Drawn);
|
||||
std::fill_n(reinterpret_cast<std::uint32_t*>(texels), SurfaceBytes / sizeof(std::uint32_t), Initial);
|
||||
AgcDriver::GuestMemory::CollectWrites(neighbourAddress, SurfaceBytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
DrawSlice(device, surface, 1u, swizzleMode, false, 1u);
|
||||
DrawSlice(device, neighbourAddress, 1u, swizzleMode, false, 1u, Side);
|
||||
const auto neighbour = StorageTexture::FindPending(neighbourAddress, 2u * LayerBytes);
|
||||
Require(neighbour != nullptr && neighbour->Descriptor().baseAddress == neighbourAddress, "the preceding target has no pending results");
|
||||
std::fill_n(reinterpret_cast<std::uint32_t*>(texels + 2u * LayerBytes), LayerBytes / sizeof(std::uint32_t), cpuValue);
|
||||
AgcDriver::GuestMemory::CollectWrites(surface + LayerBytes, LayerBytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
DrawSlice(device, surface, 1u, swizzleMode, false, 1u);
|
||||
Require(StorageTexture::FindPending(neighbourAddress, 2u * LayerBytes) == neighbour, "CPU refresh of the last slice stored the preceding target's results");
|
||||
LoadLayer(device, surface, 1u, swizzleMode, 1u);
|
||||
RequireOutput(drawn, cpuValue, "the refreshed last slice keeps the CPU's untouched pixels");
|
||||
LoadLayer(device, surface, 0u, swizzleMode, 1u);
|
||||
RequireOutput(drawn, drawn, "the first slice sees the preceding target's pending results");
|
||||
StorageTexture::FlushPending(neighbourAddress, SurfaceBytes, nullptr, "test");
|
||||
device.WaitIdle();
|
||||
AgcDriver::Graphics::ClearCachedTextures(device.Device());
|
||||
}
|
||||
|
||||
void UnregisteredUnusedLayer(AgcDriver::VulkanDevice& device, std::uint32_t slice) {
|
||||
GuestBlock block(LayerBytes, slice * LayerBytes);
|
||||
auto* texels = block.Data();
|
||||
@@ -354,6 +387,75 @@ void RetiredArrayLayer(AgcDriver::VulkanDevice& device) {
|
||||
Require(untouched, "CPU write-back stored results over a retired allocation");
|
||||
}
|
||||
|
||||
void UncommittedUnusedLayers(AgcDriver::VulkanDevice& device, std::uint32_t slice) {
|
||||
if (!AgcDriver::GuestMemory::WriteWatched()) return;
|
||||
GuestBlock block(LayerBytes, slice * LayerBytes, false);
|
||||
const auto surface = AddressOf(block.Data());
|
||||
const auto active = surface + slice * LayerBytes;
|
||||
auto* texels = reinterpret_cast<std::uint32_t*>(active);
|
||||
std::fill_n(texels, LayerBytes / sizeof(std::uint32_t), Initial);
|
||||
Require(AgcDriver::GuestMemory::CollectWrites(active, LayerBytes) != 0, "the committed array slice has no write history");
|
||||
Require(AgcDriver::GuestMemory::CollectWritesUncached(surface, SurfaceBytes) == 0, "the uncommitted array fixture unexpectedly has complete backend write history");
|
||||
Require(AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes) == 0, "the unused array slices unexpectedly have complete write history");
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
DrawSlice(device, surface, slice, TileR64KBX, false);
|
||||
const auto pending = StorageTexture::FindPending(surface, SurfaceBytes);
|
||||
Require(pending != nullptr, "the committed array slice has no pending draw results");
|
||||
Require(pending->GenerationFor(active, LayerBytes) != 0, "an uncommitted unused slice erased the active slice's write history");
|
||||
const auto version = pending->Version();
|
||||
for (unsigned repeat = 0; repeat < 3; ++repeat) {
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
Require(pending->RefreshLayers(slice, 1), "an unchanged committed slice was uploaded again");
|
||||
Require(pending->GenerationFor(active, LayerBytes) != 0 && pending->Version() == version, "repeated refresh erased the committed slice's write history");
|
||||
Require(StorageTexture::FindPending(surface, SurfaceBytes) == pending, "refresh settled unchanged GPU results for an uncommitted unused slice");
|
||||
}
|
||||
const auto cpu = std::bit_cast<std::uint32_t>(0.125f);
|
||||
std::fill_n(texels, LayerBytes / sizeof(std::uint32_t), cpu);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
Require(!pending->RefreshLayers(slice, 1), "the active array slice ignored a genuine CPU write");
|
||||
LoadLayer(device, surface, slice, TileR64KBX);
|
||||
RequireOutput(cpu, cpu, "a committed array slice after a genuine CPU write");
|
||||
DrawSlice(device, surface, slice, TileR64KBX, false);
|
||||
StorageTexture::FlushPending(active, LayerBytes, nullptr, "test");
|
||||
device.WaitIdle();
|
||||
Require(std::count(texels, texels + LayerBytes / sizeof(std::uint32_t), std::bit_cast<std::uint32_t>(Drawn)) == ScissorWidth * Side, "write-back lost results in the committed array slice");
|
||||
AgcDriver::Graphics::ClearCachedTextures(device.Device());
|
||||
}
|
||||
|
||||
void OverlappingArrays(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::uint32_t swizzleMode, bool cpuWrite) {
|
||||
const auto surface = AddressOf(texels);
|
||||
const auto next = surface + LayerBytes;
|
||||
const auto drawn = std::bit_cast<std::uint32_t>(Drawn);
|
||||
std::fill_n(reinterpret_cast<std::uint32_t*>(texels), SurfaceBytes / 4u, Initial);
|
||||
AgcDriver::GuestMemory::CollectWrites(surface, SurfaceBytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
DrawSlice(device, surface, 0, swizzleMode, false, 1u);
|
||||
DrawSlice(device, next, 0, swizzleMode, false, 1u, Side);
|
||||
const auto neighbour = StorageTexture::FindPending(next, 2u * LayerBytes);
|
||||
Require(neighbour != nullptr && neighbour->Descriptor().baseAddress == next && neighbour->Descriptor().dimension == TextureDimension::k2DArray, "the next target did not render into the image of its own array");
|
||||
if (cpuWrite) {
|
||||
std::fill_n(reinterpret_cast<std::uint32_t*>(texels), LayerBytes / sizeof(std::uint32_t), std::bit_cast<std::uint32_t>(0.125f));
|
||||
AgcDriver::GuestMemory::CollectWrites(surface, LayerBytes);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
}
|
||||
DrawSlice(device, surface, 0, swizzleMode, false, 1u);
|
||||
Require(StorageTexture::FindPending(next, 2u * LayerBytes) == neighbour, "a draw into slice 0 of the first array stored the next target's results, which only its unused second slice overlaps");
|
||||
Sample2D(device, surface, swizzleMode);
|
||||
RequireOutput(drawn, cpuWrite ? std::bit_cast<std::uint32_t>(0.125f) : Initial, "slice 0 of the first array sampled as a 2D texture after both draws");
|
||||
Require(StorageTexture::FindPending(next, 2u * LayerBytes) == neighbour, "a 2D texture over slice 0 of the first array stored the next target's results, which only its unused second slice overlaps");
|
||||
LoadLayer(device, next, 0, swizzleMode, 1u);
|
||||
RequireOutput(drawn, drawn, "slice 0 of the next array after the draw into the first");
|
||||
LoadLayer(device, surface, 1, swizzleMode, 1u);
|
||||
RequireOutput(drawn, drawn, "slice 1 of the first array (the next target's slice 0) read while the next target's results were pending");
|
||||
LoadLayer(device, surface, 0, swizzleMode, 1u);
|
||||
RequireOutput(drawn, cpuWrite ? std::bit_cast<std::uint32_t>(0.125f) : Initial, "slice 0 of the first array after both draws");
|
||||
LoadLayer(device, next, 1, swizzleMode, 1u);
|
||||
RequireOutput(Initial, Initial, "slice 1 of the next array, which nothing drew");
|
||||
StorageTexture::FlushPending(surface, SurfaceBytes, nullptr, "test");
|
||||
device.WaitIdle();
|
||||
AgcDriver::Graphics::ClearCachedTextures(device.Device());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -367,6 +469,8 @@ int main() {
|
||||
UnregisteredUnusedLayer(*device, 0u);
|
||||
UnregisteredUnusedLayer(*device, Layers - 1u);
|
||||
RetiredArrayLayer(*device);
|
||||
UncommittedUnusedLayers(*device, 0u);
|
||||
UncommittedUnusedLayers(*device, Layers - 1u);
|
||||
std::puts("array layer target tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#include "DepthFastClearHarness.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
struct VulkanDeviceTestAccess {
|
||||
static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::DepthPlaneStoreStatistics;
|
||||
using DepthFastClearHarness::Compile;
|
||||
using DepthFastClearHarness::Draw;
|
||||
using DepthFastClearHarness::DrawOptions;
|
||||
using DepthFastClearHarness::Require;
|
||||
using DepthFastClearHarness::Surface;
|
||||
|
||||
constexpr std::uint32_t PlaneWidth = 384;
|
||||
constexpr std::uint32_t PlaneHeight = 200;
|
||||
constexpr VkExtent2D PlaneExtent{PlaneWidth, PlaneHeight};
|
||||
constexpr std::size_t Block = 65536;
|
||||
constexpr std::size_t Units = 6;
|
||||
constexpr std::size_t PlaneBytes = Units * Block;
|
||||
constexpr std::size_t UnitTexels = Block / sizeof(float);
|
||||
constexpr std::size_t EdgeTexels = 128u * (PlaneHeight - 128u);
|
||||
constexpr std::size_t PlaneTexels = static_cast<std::size_t>(PlaneWidth) * PlaneHeight;
|
||||
constexpr VkExtent2D SmallExtent{64, 32};
|
||||
constexpr std::size_t SmallColorBytes = static_cast<std::size_t>(SmallExtent.width) * SmallExtent.height * 4u;
|
||||
constexpr std::uint64_t ReadBefore = 4096;
|
||||
constexpr std::size_t ReadBytes = 7u * 1024u;
|
||||
alignas(256) std::array<std::byte, PlaneTexels * 4u> Colors{};
|
||||
|
||||
void* AllocateBlocks(std::size_t bytes, bool watched) {
|
||||
#ifdef _WIN32
|
||||
void* block = nullptr;
|
||||
if (watched) {
|
||||
block = GuestArena::GuestArenaAllocate_nid_postfix(bytes, Block);
|
||||
if (block != nullptr) GuestArena::GuestArenaCommit_nid_postfix(block, bytes, PAGE_READWRITE, bytes);
|
||||
} else {
|
||||
block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
}
|
||||
Require(block != nullptr, "cannot allocate the depth plane blocks");
|
||||
#else
|
||||
void* raw = mmap(nullptr, bytes + Block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Require(raw != MAP_FAILED, "cannot map the depth plane blocks");
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
|
||||
const auto aligned = (begin + Block - 1) & ~static_cast<std::uintptr_t>(Block - 1);
|
||||
if (aligned != begin) munmap(raw, aligned - begin);
|
||||
if (aligned + bytes != begin + bytes + Block) munmap(reinterpret_cast<void*>(aligned + bytes), begin + Block - aligned);
|
||||
void* block = reinterpret_cast<void*>(aligned);
|
||||
if (watched) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
|
||||
#endif
|
||||
GuestAllocations::Mutation().Add(block, bytes, true, true, true);
|
||||
return block;
|
||||
}
|
||||
|
||||
void Fill(float* plane, float value) {
|
||||
std::fill(plane, plane + PlaneBytes / sizeof(float), value);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
}
|
||||
|
||||
void FillUnit(float* plane, std::size_t unit, float value) {
|
||||
std::fill(plane + unit * UnitTexels, plane + (unit + 1u) * UnitTexels, value);
|
||||
AgcDriver::GuestMemory::BumpCollectEpoch();
|
||||
}
|
||||
|
||||
std::size_t Texels(std::size_t unit) {
|
||||
return unit < 3u ? UnitTexels : EdgeTexels;
|
||||
}
|
||||
|
||||
void RequireUnit(const float* plane, std::size_t unit, float depth, std::size_t count, const std::string& what) {
|
||||
const auto* first = plane + unit * UnitTexels;
|
||||
const auto held = static_cast<std::size_t>(std::count(first, first + UnitTexels, depth));
|
||||
Require(held == count, what + ": unit " + std::to_string(unit) + " holds " + std::to_string(held) + " texels of " + std::to_string(depth) + " instead of " + std::to_string(count));
|
||||
}
|
||||
|
||||
DepthPlaneStoreStatistics Since(const DepthPlaneStoreStatistics& before) {
|
||||
const auto now = AgcDriver::Graphics::DepthPlaneStoreCounts();
|
||||
DepthPlaneStoreStatistics difference;
|
||||
difference.stores = now.stores - before.stores;
|
||||
difference.units = now.units - before.units;
|
||||
difference.bytes = now.bytes - before.bytes;
|
||||
return difference;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::uint64_t, std::uint64_t>> ReadStart(AgcDriver::VulkanDevice& device, std::uint64_t base) {
|
||||
auto stored = AgcDriver::Graphics::FlushDepthPlanes(base - ReadBefore, ReadBytes);
|
||||
device.WaitIdle();
|
||||
return stored;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Require(AgcDriver::Graphics::DepthSliceBytes(PlaneExtent, 4u) == PlaneBytes, "the 384x200 depth plane is not six 64 KiB tiles");
|
||||
auto* blocks = static_cast<std::byte*>(AllocateBlocks(Block + PlaneBytes, true));
|
||||
auto* plane = reinterpret_cast<float*>(blocks + Block);
|
||||
const auto base = reinterpret_cast<std::uintptr_t>(plane);
|
||||
const auto shaders = Compile(*device);
|
||||
const auto swizzle = AgcDriver::Graphics::XorSwizzleMode(AgcDriver::Graphics::TextureTileMode::kZ64KBX);
|
||||
const Surface depth{base, 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, swizzle};
|
||||
const auto draw = [&](const Surface& surface, const DrawOptions& options) { return Draw(*device, shaders, surface, options, PlaneExtent, Colors); };
|
||||
const DrawOptions writeHalf{.depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS};
|
||||
|
||||
Fill(plane, 1.0f);
|
||||
Require(draw(depth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 did not cover the plane");
|
||||
Require(AgcDriver::Graphics::DepthPlanesStale(), "the draw writing depth left the plane unregistered");
|
||||
auto before = AgcDriver::Graphics::DepthPlaneStoreCounts();
|
||||
const auto first = ReadStart(*device, base);
|
||||
auto stored = Since(before);
|
||||
Require(first.size() == 1 && first[0].first == base && first[0].second == Block, "a read ending 3 KiB into the stale plane did not store exactly its first 64 KiB unit");
|
||||
Require(stored.stores == 1 && stored.units == 1 && stored.bytes == Block, "a read ending 3 KiB into the stale plane did not count one store of one unit");
|
||||
RequireUnit(plane, 0, 0.5f, UnitTexels, "after a read of the first unit");
|
||||
for (std::size_t unit = 1; unit < Units; ++unit) RequireUnit(plane, unit, 1.0f, UnitTexels, "after a read of the first unit, a unit it does not overlap");
|
||||
Require(AgcDriver::Graphics::DepthPlanesStale(), "a store of one unit left no stale plane");
|
||||
|
||||
before = AgcDriver::Graphics::DepthPlaneStoreCounts();
|
||||
Require(ReadStart(*device, base).empty(), "the same read again stored the first unit again");
|
||||
stored = Since(before);
|
||||
Require(stored.stores == 0 && stored.units == 0 && stored.bytes == 0, "the same read again counted a store");
|
||||
|
||||
before = AgcDriver::Graphics::DepthPlaneStoreCounts();
|
||||
const auto rest = AgcDriver::Graphics::FlushDepthPlanes(base, PlaneBytes);
|
||||
device->WaitIdle();
|
||||
stored = Since(before);
|
||||
Require(rest.size() == 1 && rest[0].first == base + Block && rest[0].second == PlaneBytes - Block, "a read of the whole plane did not store the five units still stale");
|
||||
Require(stored.stores == 1 && stored.units == Units - 1u && stored.bytes == PlaneBytes - Block, "a read of the whole plane did not count one store of five units");
|
||||
for (std::size_t unit = 0; unit < Units; ++unit) RequireUnit(plane, unit, 0.5f, Texels(unit), "after a read of the whole plane");
|
||||
for (std::size_t unit = 3; unit < Units; ++unit) RequireUnit(plane, unit, 1.0f, UnitTexels - EdgeTexels, "after a read of the whole plane, the padding below the last row");
|
||||
Require(!AgcDriver::Graphics::DepthPlanesStale(), "a read of the whole plane left it stale");
|
||||
|
||||
if (!AgcDriver::GuestMemory::WriteWatched() || !AgcDriver::GuestMemory::Watched(base, PlaneBytes)) {
|
||||
std::puts("guest memory is not write-watched here: guest writes into a partly stored plane are not exercised");
|
||||
} else {
|
||||
Fill(plane, 1.0f);
|
||||
Require(draw(depth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 over the reloaded plane did not cover it");
|
||||
Require(!ReadStart(*device, base).empty(), "a read of the first unit of the drawn plane stored nothing");
|
||||
FillUnit(plane, 4, 0.75f);
|
||||
draw(depth, {.depthTest = false});
|
||||
before = AgcDriver::Graphics::DepthPlaneStoreCounts();
|
||||
Require(!AgcDriver::Graphics::FlushDepthPlanes(base, PlaneBytes).empty(), "a read of the whole plane after a guest write into unit 4 stored nothing");
|
||||
device->WaitIdle();
|
||||
stored = Since(before);
|
||||
Require(stored.units == 4 && stored.bytes == 4u * Block, "a read of the whole plane after a guest write into unit 4 did not store the four units still stale");
|
||||
for (const std::size_t unit : {0u, 1u, 2u, 3u, 5u}) RequireUnit(plane, unit, 0.5f, Texels(unit), "a guest write into unit 4 after unit 0 was stored, then a read of the whole plane");
|
||||
RequireUnit(plane, 4, 0.75f, UnitTexels, "a guest write into unit 4 after unit 0 was stored, then a read of the whole plane, the written unit");
|
||||
Require(draw(depth, {.minDepth = 0.6f, .maxDepth = 0.6f}) == EdgeTexels, "a draw at depth 0.6 did not pass LESS exactly over the unit the guest wrote 0.75 into");
|
||||
|
||||
Fill(plane, 1.0f);
|
||||
Require(draw(depth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 before a guest write and an unmatched view did not cover the plane");
|
||||
Require(!ReadStart(*device, base).empty(), "a read of the first unit before a guest write and an unmatched view stored nothing");
|
||||
FillUnit(plane, 2, 0.75f);
|
||||
const std::array<std::uint32_t, 8> words{0, 0, 0, 0, PlaneWidth, PlaneHeight, 1, 0};
|
||||
AgcDriver::Graphics::GuestTextureResource levels{};
|
||||
levels.baseAddress = base;
|
||||
levels.width = PlaneWidth;
|
||||
levels.height = PlaneHeight;
|
||||
levels.mipCount = 2;
|
||||
levels.lastLevel = 1;
|
||||
levels.dimension = AgcDriver::Graphics::TextureDimension::k2D;
|
||||
levels.tileMode = AgcDriver::Graphics::TextureTileMode::kZ64KBX;
|
||||
levels.format = 22;
|
||||
Require(AgcDriver::Graphics::DepthSurfaceTexture(AgcDriver::VulkanDeviceTestAccess::ContextOf(*device), words, levels, {}) == nullptr, "a view with two levels of a plane the guest wrote was served by the depth surface");
|
||||
device->WaitIdle();
|
||||
Require(!AgcDriver::Graphics::DepthSurfaceAt(base), "a view with two levels of a plane the guest wrote left the depth surface live");
|
||||
for (const std::size_t unit : {0u, 1u, 3u, 4u, 5u}) RequireUnit(plane, unit, 0.5f, Texels(unit), "the retire of a partly stored plane the guest wrote into");
|
||||
RequireUnit(plane, 2, 0.75f, UnitTexels, "the retire of a partly stored plane the guest wrote into, the written unit");
|
||||
}
|
||||
|
||||
Fill(plane, 1.0f);
|
||||
Require(draw(depth, writeHalf) == PlaneTexels, "the draw writing depth 0.5 under a later color target did not cover the plane");
|
||||
Require(!ReadStart(*device, base).empty(), "a read of the first unit before a color target stored nothing");
|
||||
auto* smallPlane = AllocateBlocks(Block, true);
|
||||
const Surface smallDepth{reinterpret_cast<std::uintptr_t>(smallPlane), 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, swizzle};
|
||||
const std::span<std::byte> overLastUnit(reinterpret_cast<std::byte*>(plane + 5u * UnitTexels), SmallColorBytes);
|
||||
Draw(*device, shaders, smallDepth, {.depthTest = false}, SmallExtent, overLastUnit);
|
||||
Require(!AgcDriver::Graphics::DepthSurfaceAt(base), "a color target over the last unit left the depth surface live");
|
||||
for (std::size_t unit = 0; unit < 5u; ++unit) RequireUnit(plane, unit, 0.5f, Texels(unit), "a color target over the last unit of a partly stored plane");
|
||||
|
||||
auto* looseBlocks = static_cast<std::byte*>(AllocateBlocks(Block + PlaneBytes, false));
|
||||
auto* loose = reinterpret_cast<float*>(looseBlocks + Block);
|
||||
const auto looseBase = reinterpret_cast<std::uintptr_t>(loose);
|
||||
if (AgcDriver::GuestMemory::Watched(looseBase, PlaneBytes)) {
|
||||
std::puts("memory outside the guest arena is write-watched here: a partial store over unwatched memory is not exercised");
|
||||
} else {
|
||||
const Surface unwatched{looseBase, 0, 0, false, VK_FORMAT_D32_SFLOAT, 0, 0, swizzle};
|
||||
Fill(loose, 1.0f);
|
||||
Require(draw(unwatched, writeHalf) == PlaneTexels, "the draw writing depth 0.5 into an unwatched plane did not cover it");
|
||||
before = AgcDriver::Graphics::DepthPlaneStoreCounts();
|
||||
Require(ReadStart(*device, looseBase).size() == 1, "a read of the first unit of an unwatched plane did not store it");
|
||||
stored = Since(before);
|
||||
Require(stored.units == 1 && stored.bytes == Block, "a read of the first unit of an unwatched plane did not count one unit");
|
||||
RequireUnit(loose, 0, 0.5f, UnitTexels, "unwatched plane after a read of the first unit");
|
||||
for (std::size_t unit = 1; unit < Units; ++unit) RequireUnit(loose, unit, 1.0f, UnitTexels, "unwatched plane after a read of the first unit, a unit it does not overlap");
|
||||
Require(draw(unwatched, {.minDepth = 0.6f, .maxDepth = 0.6f}) == 0u, "a draw at depth 0.6 passed LESS over unwatched units still holding depth 0.5 on the GPU");
|
||||
}
|
||||
std::puts("depth plane partial store tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
@@ -29,6 +32,7 @@ using namespace AgcDriver::Graphics;
|
||||
constexpr std::uint32_t Side = 64;
|
||||
constexpr std::size_t BlockBytes = 65536;
|
||||
alignas(4096) std::array<std::uint8_t, BlockBytes * 2> Storage{};
|
||||
alignas(4096) std::array<std::uint8_t, BlockBytes * 2> CopyStorage{};
|
||||
constexpr std::uint32_t Format32Float = 22;
|
||||
constexpr std::uint32_t Format8Uint = 5;
|
||||
|
||||
@@ -301,6 +305,69 @@ void RunPairings(Context context, std::uint8_t* block) {
|
||||
ExpectFailure([&] { static_cast<void>(DepthSurfaceStoreBack(context, *depth)); }, "through a view that another view of the plane", "a store back through a replaced view was taken by an older pairing that covers the plane");
|
||||
}
|
||||
|
||||
void DepthCopyCase(Context context, std::uint8_t* block) {
|
||||
const auto colorAddress = reinterpret_cast<std::uint64_t>(block);
|
||||
const auto depthAddress = colorAddress + BlockBytes / 4;
|
||||
Recorder recorder(context);
|
||||
recorder.Activate();
|
||||
const auto submit = [&] {
|
||||
recorder.Submit();
|
||||
Check(context.Function<PFN_vkQueueWaitIdle>("vkQueueWaitIdle")(context.queue), "vkQueueWaitIdle");
|
||||
};
|
||||
ColorTarget color{};
|
||||
color.address = colorAddress;
|
||||
color.surfaceAddress = colorAddress;
|
||||
color.extent = {Side, Side};
|
||||
color.surfaceExtent = color.extent;
|
||||
color.format = VK_FORMAT_R32_SFLOAT;
|
||||
color.elementBytes = 4;
|
||||
color.tileMode = ColorTileMode::RenderTarget;
|
||||
color.bytes = ColorTargetLayout(Side, Side, ColorTileMode::RenderTarget, 4).Bytes();
|
||||
const auto copy = [&](std::uint32_t samples, std::uint32_t sample, VkExtent2D extent) {
|
||||
RunDepthCopyPass(context, DepthCopyPass{depthAddress, extent, samples, sample, color});
|
||||
submit();
|
||||
const auto resident = CachedStorageSurface(context, SurfaceForTarget(color));
|
||||
resident->MarkDirty();
|
||||
resident->WriteBack();
|
||||
submit();
|
||||
};
|
||||
const auto countBlock = [&](float value) {
|
||||
std::size_t count = 0;
|
||||
for (std::size_t offset = 0; offset < BlockBytes; offset += sizeof(float)) {
|
||||
float texel = 0;
|
||||
std::memcpy(&texel, block + offset, sizeof(texel));
|
||||
if (texel == value) ++count;
|
||||
}
|
||||
return count;
|
||||
};
|
||||
std::fill(block, block + BlockBytes, std::uint8_t{0});
|
||||
ExpectFailure([&] { copy(1, 0, {Side, Side}); }, "no draw rendered", "a DB depth copy from memory without a depth surface was accepted");
|
||||
const auto sampledCounts = context.limits.sampledImageDepthSampleCounts & context.limits.framebufferDepthSampleCounts;
|
||||
const std::uint32_t samples = (sampledCounts & VK_SAMPLE_COUNT_2_BIT) != 0 ? 2u : (sampledCounts & VK_SAMPLE_COUNT_4_BIT) != 0 ? 4u : 0u;
|
||||
if (samples != 0) {
|
||||
Require(DepthSurfaceView(context, DepthTarget{depthAddress, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 0.375f, 0, 0, false, samples}) != VK_NULL_HANDLE, "the multisample depth surface was not created");
|
||||
for (std::uint32_t sample = 0; sample < samples; ++sample) {
|
||||
std::fill(block, block + BlockBytes, std::uint8_t{0});
|
||||
copy(samples, sample, {Side, Side});
|
||||
Require(countBlock(0.375f) == Side * Side, "the DB depth copy of sample " + std::to_string(sample) + " of " + std::to_string(samples) + " did not store the cleared depth in every texel of the R32 float target");
|
||||
}
|
||||
ExpectFailure([&] { copy(samples, samples, {Side, Side}); }, "does not have", "a DB depth copy of a sample past the last of a multisample surface was accepted");
|
||||
ExpectFailure([&] { copy(samples, 0, {Side / 2, Side / 2}); }, "of another size", "a DB depth copy of another extent than the depth surface was accepted");
|
||||
ClearDepthSurfaces(context.device);
|
||||
}
|
||||
if ((context.limits.sampledImageDepthSampleCounts & VK_SAMPLE_COUNT_2_BIT) == 0) {
|
||||
Require(DepthSurfaceView(context, DepthTarget{depthAddress, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 0.375f, 0, 0, false, 2}) != VK_NULL_HANDLE, "the unsampleable two-sample depth surface was not created");
|
||||
ExpectFailure([&] { copy(2, 0, {Side, Side}); }, "cannot sample at its sample count", "a DB depth copy from an unsampleable two-sample depth surface was accepted");
|
||||
ClearDepthSurfaces(context.device);
|
||||
std::puts("DB depth copy: unsupported two-sample source refused cleanly");
|
||||
}
|
||||
Require(DepthSurfaceView(context, DepthTarget{depthAddress, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 0.625f, 0, 0, false, 1}) != VK_NULL_HANDLE, "the one-sample depth surface was not created");
|
||||
std::fill(block, block + BlockBytes, std::uint8_t{0});
|
||||
copy(1, 0, {Side, Side});
|
||||
Require(countBlock(0.625f) == Side * Side, "the DB depth copy of a one-sample depth surface did not store the cleared depth in every texel of the R32 float target");
|
||||
ExpectFailure([&] { copy(1, 1, {Side, Side}); }, "does not have", "a DB depth copy of a second sample of a one-sample surface was accepted");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@@ -321,6 +388,11 @@ int main() {
|
||||
{
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
Run(context, block);
|
||||
auto* copyBlock = CopyStorage.data() + (BlockBytes - reinterpret_cast<std::uintptr_t>(CopyStorage.data()) % BlockBytes) % BlockBytes;
|
||||
GuestAllocations::Mutation().Add(copyBlock, BlockBytes, true, true, true);
|
||||
DepthCopyCase(context, copyBlock);
|
||||
ClearCachedTextures(context.device);
|
||||
GuestAllocations::Mutation().Remove(copyBlock);
|
||||
ClearDepthSurfaces(context.device);
|
||||
RunPairings(context, block);
|
||||
ClearDepthSurfaces(context.device);
|
||||
|
||||
@@ -27,6 +27,7 @@ constexpr std::uint32_t Side = 2;
|
||||
constexpr std::uint32_t Format8888UNorm = 56;
|
||||
constexpr std::uint32_t Format8UNorm = 1;
|
||||
constexpr std::uint32_t Format8_8UNorm = 14;
|
||||
constexpr std::uint32_t Format16UNorm = 7;
|
||||
constexpr std::uint32_t Format1616UNorm = 23;
|
||||
constexpr std::uint32_t Format9995Float = 132;
|
||||
constexpr std::uint32_t Format8Srgb = 128;
|
||||
@@ -178,45 +179,6 @@ void FillInput() {
|
||||
Extra[0] = 0x00001f20u;
|
||||
}
|
||||
|
||||
std::int32_t OffsetComponent(std::uint32_t tid, std::uint32_t component) {
|
||||
const auto field = (Extra[tid * 3u] >> (component * 8u)) & 0x3fu;
|
||||
return static_cast<std::int32_t>(field ^ 0x20u) - 0x20;
|
||||
}
|
||||
|
||||
float ReferenceTexel(int x, int y, float reference, const Sampler& sampler) {
|
||||
const std::uint32_t clamp = sampler.clamp;
|
||||
const int size = static_cast<int>(Side);
|
||||
if (clamp == ClampBorder && (x < 0 || x >= size || y < 0 || y >= size)) {
|
||||
const float border = sampler.border == BorderWhite ? 1.0f : 0.0f;
|
||||
return reference <= border ? 1.0f : 0.0f;
|
||||
}
|
||||
const auto address = [&](int value) {
|
||||
if (clamp == ClampEdge) return std::clamp(value, 0, static_cast<int>(Side) - 1);
|
||||
const int size = static_cast<int>(Side);
|
||||
return ((value % size) + size) % size;
|
||||
};
|
||||
const float red = static_cast<float>(Red[address(y) * Side + address(x)]) / 255.0f;
|
||||
return reference <= red ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
float Expected(std::uint32_t tid, const Sampler& sampler, bool offsets) {
|
||||
const float u = Input[tid * 3u + 1u] * static_cast<float>(Side);
|
||||
const float v = Input[tid * 3u + 2u] * static_cast<float>(Side);
|
||||
const float reference = std::clamp(Input[tid * 3u + 0u], 0.0f, 1.0f);
|
||||
const int offsetX = offsets ? OffsetComponent(tid, 0u) : 0;
|
||||
const int offsetY = offsets ? OffsetComponent(tid, 1u) : 0;
|
||||
if (sampler.filter == FilterPoint) return ReferenceTexel(static_cast<int>(std::floor(u)) + offsetX, static_cast<int>(std::floor(v)) + offsetY, reference, sampler);
|
||||
const float cu = u - 0.5f;
|
||||
const float cv = v - 0.5f;
|
||||
const int x = static_cast<int>(std::floor(cu)) + offsetX;
|
||||
const int y = static_cast<int>(std::floor(cv)) + offsetY;
|
||||
const float a = cu - std::floor(cu);
|
||||
const float b = cv - std::floor(cv);
|
||||
const float top = ReferenceTexel(x, y, reference, sampler) * (1.0f - a) + ReferenceTexel(x + 1, y, reference, sampler) * a;
|
||||
const float bottom = ReferenceTexel(x, y + 1, reference, sampler) * (1.0f - a) + ReferenceTexel(x + 1, y + 1, reference, sampler) * a;
|
||||
return top * (1.0f - b) + bottom * b;
|
||||
}
|
||||
|
||||
ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, std::span<const std::uint32_t> code = Code, bool useCache = false, bool nativeSampleOffsets = true, bool cube = false) {
|
||||
std::vector<std::uint32_t> userData(24, 0u);
|
||||
const auto input = cube ? BufferDescriptor(CubeInput.data(), static_cast<std::uint32_t>(sizeof(CubeInput))) : BufferDescriptor(Input.data(), static_cast<std::uint32_t>(sizeof(Input)));
|
||||
@@ -254,9 +216,7 @@ std::uint64_t Run(AgcDriver::VulkanDevice& device, const Sampler& sampler, const
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const float expected = Expected(tid, sampler, offsets);
|
||||
const float tolerance = sampler.filter == FilterPoint ? 0.0f : 1e-4f;
|
||||
Require(std::fabs(Output[tid] - expected) <= tolerance, std::string(name) + ": thread " + std::to_string(tid) + " compared to " + std::to_string(Output[tid]) + ", expected " + std::to_string(expected));
|
||||
Require(Output[tid] == 0.0f, std::string(name) + ": thread " + std::to_string(tid) + " compared to " + std::to_string(Output[tid]) + ", expected 0");
|
||||
}
|
||||
return result.variantId;
|
||||
}
|
||||
@@ -274,12 +234,11 @@ void RunChannels(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t>
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
const float compared = Expected(tid, sampler, false);
|
||||
for (std::uint32_t reg = 0; reg < 4u; ++reg) {
|
||||
float expected = -1.0f;
|
||||
if (reg < registers) {
|
||||
const auto select = channels[firstChannel + reg];
|
||||
expected = select == 0u ? 0.0f : select == 1u ? 1.0f : compared;
|
||||
expected = select == 0u ? 0.0f : select == 1u ? 1.0f : 0.0f;
|
||||
}
|
||||
const float actual = Output[tid * 4u + reg];
|
||||
Require(std::fabs(actual - expected) <= 1e-4f, std::string(name) + ": thread " + std::to_string(tid) + " register " + std::to_string(reg) + " returned " + std::to_string(actual) + ", expected " + std::to_string(expected));
|
||||
@@ -304,7 +263,7 @@ void RunCube(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(CubeCode.data()));
|
||||
device.WaitIdle();
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
Require(Output[tid] == 1.0f, std::string(name) + ": +X cube face comparison returned " + std::to_string(Output[tid]) + ", expected 1");
|
||||
Require(Output[tid] == 0.0f, std::string(name) + ": +X cube face comparison returned " + std::to_string(Output[tid]) + ", expected 0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +273,7 @@ bool BindsDepthCompare(const ShaderRecompiler::RecompileResult& result) {
|
||||
});
|
||||
}
|
||||
|
||||
void RunSrgbCubeZero(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name) {
|
||||
void RunZeroCube(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name) {
|
||||
FillCubeTexels();
|
||||
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
|
||||
CubeInput[tid * 4u + 0u] = 0.5f;
|
||||
@@ -332,7 +291,7 @@ void RunSrgbCubeZero(AgcDriver::VulkanDevice& device, std::uint32_t format, cons
|
||||
}
|
||||
}
|
||||
|
||||
void RunSrgbZero(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name, bool useCache, std::uint64_t expectedVariant) {
|
||||
void RunZero(AgcDriver::VulkanDevice& device, std::uint32_t format, const Sampler& sampler, const char* name, bool useCache, std::uint64_t expectedVariant) {
|
||||
Output.fill(-1.0f);
|
||||
const auto result = Compile(device, format, sampler, Code, useCache);
|
||||
if (useCache) Require(result.cacheHit && result.variantId == expectedVariant, std::string(name) + " did not reuse the compiled comparison artifact");
|
||||
@@ -366,25 +325,38 @@ int main() {
|
||||
FillTexels();
|
||||
FillInput();
|
||||
RunCube(*device, {ClampEdge, FilterPoint}, "point cube, clamp to edge");
|
||||
Reject(*device, Format1616UNorm, {ClampEdge, FilterBilinear}, "bilinear cube comparison requires DISABLE_CUBE_WRAP", CubeCode, true, true);
|
||||
RunCube(*device, {ClampEdge, FilterBilinear}, "bilinear cube without DISABLE_CUBE_WRAP");
|
||||
for (std::uint32_t function = 0; function < 8u; ++function) {
|
||||
const auto name = "bilinear 8_8_8_8 srgb cube function " + std::to_string(function);
|
||||
RunSrgbCubeZero(*device, Format8888Srgb, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, name.c_str());
|
||||
RunZeroCube(*device, Format8888Srgb, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, name.c_str());
|
||||
}
|
||||
RunSrgbCubeZero(*device, Format8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8 srgb cube without DISABLE_CUBE_WRAP");
|
||||
RunSrgbCubeZero(*device, Format8_8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8_8 srgb cube without DISABLE_CUBE_WRAP");
|
||||
RunZeroCube(*device, Format8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8 srgb cube without DISABLE_CUBE_WRAP");
|
||||
RunZeroCube(*device, Format8_8Srgb, {ClampEdge, FilterBilinear}, "bilinear 8_8 srgb cube without DISABLE_CUBE_WRAP");
|
||||
RunCube(*device, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, true}, "bilinear cube with DISABLE_CUBE_WRAP");
|
||||
Reject(*device, Format9995Float, {ClampEdge, FilterPoint}, "implemented only for float, unorm and snorm formats");
|
||||
Reject(*device, Format9995Float, {ClampEdge, FilterPoint}, "implemented only for floating-point, UNORM and SNORM formats");
|
||||
for (std::uint32_t function = 0; function < 8u; ++function) {
|
||||
const auto name = "bilinear 8_8_8_8 unorm cube function " + std::to_string(function);
|
||||
RunSrgbCubeZero(*device, Format8888UNorm, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, name.c_str());
|
||||
RunZeroCube(*device, Format8888UNorm, {ClampEdge, FilterBilinear, BorderBlack, 0u, 0u, false, function}, name.c_str());
|
||||
}
|
||||
RunSrgbCubeZero(*device, Format8888UNorm, {ClampEdge, FilterAnisoBilinear, BorderBlack, 0u, 0u, false, GreaterEqual}, "anisotropic bilinear 8_8_8_8 unorm cube without DISABLE_CUBE_WRAP");
|
||||
Reject(*device, Format1616UNorm, {ClampEdge, FilterAnisoBilinear}, "point or bilinear");
|
||||
Reject(*device, Format1616UNorm, {ClampEdge, FilterAnisoPoint}, "point or bilinear");
|
||||
RunZeroCube(*device, Format8888UNorm, {ClampEdge, FilterAnisoBilinear, BorderBlack, 0u, 0u, false, GreaterEqual}, "anisotropic bilinear 8_8_8_8 unorm cube without DISABLE_CUBE_WRAP");
|
||||
RunZero(*device, Format1616UNorm, {ClampEdge, FilterAnisoBilinear}, "anisotropic bilinear 16_16 unorm", false, 0u);
|
||||
RunZero(*device, Format1616UNorm, {ClampEdge, FilterAnisoPoint}, "anisotropic point 16_16 unorm", false, 0u);
|
||||
FillTexels();
|
||||
Require(BindsDepthCompare(Compile(*device, Format16UNorm, {ClampEdge, FilterPoint})), "an R16 unorm texture left the native comparison path");
|
||||
Require(BindsDepthCompare(Compile(*device, Format32Float, {ClampEdge, FilterBilinear})), "an R32 float texture left the native comparison path");
|
||||
Require(!BindsDepthCompare(Compile(*device, Format8888UNorm, {ClampEdge, FilterBilinear})), "a color texture kept a depth-compare binding");
|
||||
for (const auto format : {1u, 2u, 8u, 13u, 14u, 15u, 23u, 24u, 29u, 36u, 50u, 51u, 56u, 57u, 65u, 66u, 71u, 77u}) {
|
||||
if (format == 51u && !device->SupportsImageFormat(VK_FORMAT_A2B10G10R10_SNORM_PACK32, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)) {
|
||||
std::puts("measured GFX10 color format 51 returns zero: skipped, Vulkan device does not support the image format");
|
||||
continue;
|
||||
}
|
||||
const auto name = "measured GFX10 color format " + std::to_string(format) + " returns zero";
|
||||
RunZero(*device, format, {ClampEdge, FilterPoint}, name.c_str(), false, 0u);
|
||||
}
|
||||
for (std::uint32_t function = 0; function < 8u; ++function) {
|
||||
const auto name = "16_16 unorm compare function " + std::to_string(function) + " returns zero";
|
||||
RunZero(*device, Format1616UNorm, {ClampEdge, FilterPoint, BorderBlack, 0u, 0u, false, function}, name.c_str(), false, 0u);
|
||||
}
|
||||
for (const bool useCache : {false, true}) {
|
||||
const auto variant = useCache ? Run(*device, {ClampEdge, FilterPoint}, "cached UNORM before sRGB", false, true) : 0u;
|
||||
if (useCache) Require(variant != 0, "the cached comparison has no compiled artifact identity");
|
||||
@@ -392,7 +364,7 @@ int main() {
|
||||
for (std::uint32_t function = 0; function < 8u; ++function) {
|
||||
for (const auto filter : {FilterPoint, FilterBilinear}) {
|
||||
const auto name = std::string(useCache ? "cached" : "uncached") + " sRGB format " + std::to_string(format) + " function " + std::to_string(function) + (filter == FilterPoint ? " point" : " bilinear");
|
||||
RunSrgbZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), useCache, variant);
|
||||
RunZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), useCache, variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -403,10 +375,11 @@ int main() {
|
||||
for (const auto filter : {FilterPoint, FilterBilinear, FilterAnisoBilinear}) {
|
||||
if (format != Format8888UNorm && function != LessEqual) continue;
|
||||
const auto name = "8-bit unorm format " + std::to_string(format) + " function " + std::to_string(function) + " filter " + std::to_string(filter);
|
||||
RunSrgbZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), false, 0u);
|
||||
RunZero(*device, format, {ClampEdge, filter, BorderBlack, 0u, 0u, false, function}, name.c_str(), false, 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
RunZero(*device, Format1616UNorm, {ClampEdge, FilterPoint}, "16_16 unorm comparison returns zero", false, 0u);
|
||||
Run(*device, {ClampEdge, FilterPoint}, "point, clamp to edge");
|
||||
Run(*device, {ClampWrap, FilterPoint}, "point, wrap");
|
||||
if (lodClamp) Run(*device, {ClampWrap, FilterPoint}, "point, wrap with negative offsets", true);
|
||||
|
||||
@@ -63,16 +63,16 @@ void* Map(std::size_t bytes = BlockBytes) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void CheckPattern(const void* pointer) {
|
||||
void CheckPattern(const void* pointer, std::size_t bytes = FillBytes) {
|
||||
const auto* data = static_cast<const std::byte*>(pointer);
|
||||
for (std::size_t offset = 0; offset < FillBytes; offset += sizeof(Pattern))
|
||||
for (std::size_t offset = 0; offset < bytes; offset += sizeof(Pattern))
|
||||
Require(std::memcmp(data + offset, Pattern.data(), sizeof(Pattern)) == 0, "guest mutation lost the pending GPU fill");
|
||||
}
|
||||
|
||||
void Fill(AgcDriver::VulkanDevice& device, void* pointer) {
|
||||
void Fill(AgcDriver::VulkanDevice& device, void* pointer, std::size_t bytes = FillBytes) {
|
||||
std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex());
|
||||
Require(device.FillBuffer(Address(pointer), FillBytes, Pattern), "Vulkan device did not import the guest allocation");
|
||||
Require(Imported(device, pointer), "GPU fill did not create a host import");
|
||||
Require(device.FillBuffer(Address(pointer), bytes, Pattern), "Vulkan device did not import the guest allocation");
|
||||
Require(Imported(device, pointer, bytes), "GPU fill did not create a host import");
|
||||
}
|
||||
|
||||
void CheckMappings(AgcDriver::VulkanDevice& device) {
|
||||
@@ -105,14 +105,25 @@ void CheckMappings(AgcDriver::VulkanDevice& device) {
|
||||
Require(!Imported(device, mapping), "host import survived guest munmap");
|
||||
|
||||
mapping = Map(3 * BlockBytes);
|
||||
Fill(device, mapping);
|
||||
Fill(device, mapping, 3 * BlockBytes);
|
||||
auto* middle = static_cast<std::uint8_t*>(mapping) + BlockBytes;
|
||||
Require(mprotect_nid_postfix(middle, BlockBytes, 1) == 0, "guest partial protection failed");
|
||||
Require(!Imported(device, mapping), "whole host import survived partial protection change");
|
||||
CheckPattern(mapping);
|
||||
Fill(device, mapping);
|
||||
Require(!Imported(device, mapping, 3 * BlockBytes), "whole host import survived partial protection change");
|
||||
CheckPattern(mapping, 3 * BlockBytes);
|
||||
Require(munmap_nid_postfix(middle, BlockBytes) == 0, "guest middle unmap after protection change failed");
|
||||
Require(!Imported(device, mapping, 3 * BlockBytes), "host import still covered the protected and unmapped middle range");
|
||||
Require(munmap_nid_postfix(mapping, BlockBytes) == 0, "guest prefix unmap after protection change failed");
|
||||
Require(munmap_nid_postfix(middle + BlockBytes, BlockBytes) == 0, "guest suffix unmap after protection change failed");
|
||||
|
||||
mapping = Map(3 * BlockBytes);
|
||||
Fill(device, mapping, 3 * BlockBytes);
|
||||
Require(Imported(device, mapping, 3 * BlockBytes), "whole host import did not cover the three-block mapping before middle unmap");
|
||||
middle = static_cast<std::uint8_t*>(mapping) + BlockBytes;
|
||||
Require(munmap_nid_postfix(middle, BlockBytes) == 0, "guest middle unmap failed");
|
||||
Require(Imported(device, mapping), "unrelated prefix import was retired by middle unmap");
|
||||
Require(!Imported(device, mapping, 3 * BlockBytes), "middle unmap left a host import spanning the unmapped range");
|
||||
Require(!Imported(device, mapping), "middle unmap retained the prefix of the retired host import");
|
||||
Fill(device, mapping);
|
||||
Require(Imported(device, mapping), "the retained prefix was not imported again after middle unmap");
|
||||
Require(munmap_nid_postfix(mapping, BlockBytes) == 0, "guest prefix unmap failed");
|
||||
Require(!Imported(device, mapping), "prefix host import survived guest munmap");
|
||||
Require(munmap_nid_postfix(middle + BlockBytes, BlockBytes) == 0, "guest suffix unmap failed");
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -23,9 +24,9 @@ constexpr std::uint32_t Width = 64;
|
||||
constexpr std::uint32_t Height = 64;
|
||||
using Image = std::array<std::byte, Width * Height * 4>;
|
||||
alignas(256) Image Pixels{};
|
||||
alignas(256) constexpr std::array<std::uint16_t, 4> Indices{0, 1, 2, 0};
|
||||
alignas(256) constexpr std::array<std::array<float, 8>, 3> Vertices{{
|
||||
{-0.9f, -0.8f, 0, 1, 1, 0, 0, 1}, {-0.1f, -0.8f, 0, 1, 0, 1, 0, 1}, {-0.5f, 0.8f, 0, 1, 0, 0, 1, 1}
|
||||
alignas(256) constexpr std::array<std::uint16_t, 4> Indices{0, 1, 2, 3};
|
||||
alignas(256) constexpr std::array<std::array<float, 8>, 4> Vertices{{
|
||||
{-0.9f, -0.8f, 0, 1, 1, 0, 0, 1}, {-0.1f, -0.8f, 0, 1, 0, 1, 0, 1}, {-0.5f, 0.8f, 0, 1, 0, 0, 1, 1}, {0.3f, 0.8f, 0, 1, 1, 1, 0, 1}
|
||||
}};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 7> FlatPixelCode{0xc8120002u, 0xc8160102u, 0xc81a0202u, 0xc81e0302u, 0xf800180fu, 0x07060504u, 0xbf810000u};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 11> SmoothPixelCode{0xc8100000u, 0xc8110001u, 0xc8140100u, 0xc8150101u, 0xc8180200u, 0xc8190201u, 0xc81c0300u, 0xc81d0301u, 0xf800180fu, 0x07060504u, 0xbf810000u};
|
||||
@@ -64,7 +65,7 @@ void Write(std::vector<std::uint32_t>& commands, std::uint32_t opcode, std::uint
|
||||
commands.insert(commands.end(), {0xc0010000u | (opcode << 8u), offset, value});
|
||||
}
|
||||
|
||||
Image Draw(const std::uint32_t* pixelCode, bool flat, bool last) {
|
||||
Image Draw(const std::uint32_t* pixelCode, bool flat, bool last, bool strip = false) {
|
||||
Pixels.fill(std::byte{0});
|
||||
const auto color = reinterpret_cast<std::uintptr_t>(Pixels.data());
|
||||
const auto geometry = reinterpret_cast<std::uintptr_t>(GeometryCode.data());
|
||||
@@ -98,13 +99,14 @@ Image Draw(const std::uint32_t* pixelCode, bool flat, bool last) {
|
||||
{0x8, static_cast<std::uint32_t>(pixel >> 8u)}, {0x9, static_cast<std::uint32_t>(pixel >> 40u)}, {0xb, 0}
|
||||
};
|
||||
std::vector<std::uint32_t> commands;
|
||||
Write(commands, 0x79, 0x242, 4);
|
||||
Write(commands, 0x79, 0x242, strip ? 6u : 4u);
|
||||
Write(commands, 0x79, 0x25b, (12u << 9u) | 4u);
|
||||
for (const auto& [offset, value] : context) Write(commands, 0x69, offset, value);
|
||||
for (const auto& [offset, value] : shaders) Write(commands, 0x76, offset, value);
|
||||
commands.insert(commands.end(), {0xc0002a00u, 0u});
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(Indices.data());
|
||||
commands.insert(commands.end(), {0xc0042700u, 3u, static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u), 3u, 0u});
|
||||
const std::uint32_t count = strip ? 4u : 3u;
|
||||
commands.insert(commands.end(), {0xc0042700u, count, static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u), count, 0u});
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, "mesh submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
@@ -132,6 +134,25 @@ void FlatInputs() {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<std::pair<std::uint32_t, std::uint32_t>, 4> SecondInside{{{26, 48}, {32, 48}, {29, 44}, {29, 52}}};
|
||||
|
||||
void CheckFlat(const Image& image, std::span<const std::pair<std::uint32_t, std::uint32_t>> pixels, const std::array<unsigned, 4>& expected, const std::string& what) {
|
||||
for (const auto& [x, y] : pixels) {
|
||||
for (std::uint32_t channel = 0; channel < 4; ++channel) {
|
||||
Require(Channel(image, x, y, channel) == expected[channel], what + ": pixel (" + std::to_string(x) + ", " + std::to_string(y) + ") channel " + std::to_string(channel) + " is " + std::to_string(Channel(image, x, y, channel)) + ", expected " + std::to_string(expected[channel]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StripFlatInputs() {
|
||||
const auto last = Draw(FlatPixelCode.data(), true, true, true);
|
||||
CheckFlat(last, Inside, {0u, 0u, 255u, 255u}, "triangle strip, last provoking vertex, triangle 0 (the color of vertex 2)");
|
||||
CheckFlat(last, SecondInside, {255u, 255u, 0u, 255u}, "triangle strip, last provoking vertex, triangle 1 (the color of vertex 3)");
|
||||
Require(Channel(last, 48, 48, 3) == 0, "triangle strip, last provoking vertex: the draw wrote outside its triangles");
|
||||
const auto first = Draw(FlatPixelCode.data(), true, false, true);
|
||||
CheckFlat(first, Inside, {255u, 0u, 0u, 255u}, "triangle strip, first provoking vertex, triangle 0 (the color of vertex 0)");
|
||||
}
|
||||
|
||||
void SmoothInputs() {
|
||||
const auto first = Draw(SmoothPixelCode.data(), false, false);
|
||||
for (const auto& [x, y] : Inside) {
|
||||
@@ -169,9 +190,10 @@ int main() {
|
||||
Register(FlatPixelHeader, FlatPixelCode.data(), sizeof(FlatPixelCode), 1);
|
||||
Register(SmoothPixelHeader, SmoothPixelCode.data(), sizeof(SmoothPixelCode), 1);
|
||||
FlatInputs();
|
||||
StripFlatInputs();
|
||||
if (smooth) SmoothInputs();
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::cout << "geometry-program triangles take flat inputs from vertex 0 with the first provoking vertex and from vertex 2 with the last" << (smooth ? ", and interpolate smooth inputs the same in both modes\n" : "; smooth inputs skipped without fragment shader barycentrics\n");
|
||||
std::cout << "geometry-program triangles take flat inputs from vertex 0 with the first provoking vertex and from vertex 2 with the last, and the second triangle of a strip takes them from vertex 3 with the last" << (smooth ? ", and interpolate smooth inputs the same in both modes\n" : "; smooth inputs skipped without fragment shader barycentrics\n");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include "MsaaReadbackVertex_spv.h"
|
||||
#include "MsaaReadbackUint_spv.h"
|
||||
@@ -69,8 +70,8 @@ void CheckSupportSelection() {
|
||||
static_assert(!SurfaceFits(VkImageFormatProperties{}, 4u));
|
||||
}
|
||||
|
||||
bool SurfaceSupported(const Context& context, VkFormat format, std::uint32_t samples, std::uint32_t width = 17u, std::uint32_t height = 5u, std::uint32_t layers = 2u) {
|
||||
const VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | (format == VK_FORMAT_D32_SFLOAT ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
|
||||
bool SurfaceSupported(const Context& context, VkFormat format, std::uint32_t samples, std::uint32_t width = 17u, std::uint32_t height = 5u, std::uint32_t layers = 2u, bool writable = false) {
|
||||
const VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | (format == VK_FORMAT_D32_SFLOAT ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) | (writable ? VK_IMAGE_USAGE_STORAGE_BIT : 0u);
|
||||
VkImageFormatProperties supported{};
|
||||
const auto result = context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage, 0u, &supported);
|
||||
if (result != VK_ERROR_FORMAT_NOT_SUPPORTED) Check(result, "readback fixture format query");
|
||||
@@ -97,11 +98,11 @@ struct Surface {
|
||||
std::vector<VkImageView> views;
|
||||
std::vector<VkFramebuffer> framebuffers;
|
||||
|
||||
Surface(const Context& context, VkFormat format, std::uint32_t elementBytes, std::uint32_t samples, std::uint32_t width = 17u, std::uint32_t height = 5u, std::uint32_t layers = 2u) : context(context), format(format), elementBytes(elementBytes), samples(samples), width(width), height(height), layers(layers) {
|
||||
Surface(const Context& context, VkFormat format, std::uint32_t elementBytes, std::uint32_t samples, std::uint32_t width = 17u, std::uint32_t height = 5u, std::uint32_t layers = 2u, bool writable = false) : context(context), format(format), elementBytes(elementBytes), samples(samples), width(width), height(height), layers(layers) {
|
||||
try {
|
||||
const bool depth = format == VK_FORMAT_D32_SFLOAT;
|
||||
const auto aspect = depth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | (depth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
|
||||
const VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | (depth ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) | (writable ? VK_IMAGE_USAGE_STORAGE_BIT : 0u);
|
||||
VkImageFormatProperties supported{};
|
||||
Check(context.imageFormatProperties(context.physical, format, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, usage, 0u, &supported), "readback fixture format query");
|
||||
Require(SurfaceFits(supported, samples, width, height, layers), "readback fixture format does not support its sample count or extent");
|
||||
@@ -275,15 +276,15 @@ struct Surface {
|
||||
const auto layer = pixel / (width * height);
|
||||
const auto value = x + y * width + layer * width * height + sample * 31u;
|
||||
std::array<std::uint32_t, 4> words{0x91352700u ^ value, 0x45219b00u ^ (value * 3u), 0xc7391d00u ^ (value * 5u), 0x6b43e100u ^ (value * 7u)};
|
||||
if (format == VK_FORMAT_D32_SFLOAT) words[0] = std::bit_cast<std::uint32_t>(0.125f + static_cast<float>(element + 1u) / 65536.0f);
|
||||
if (format == VK_FORMAT_D32_SFLOAT) words[0] = std::bit_cast<std::uint32_t>(0.125f + static_cast<float>(element + 1u) / 1048576.0f);
|
||||
std::memcpy(result.data() + static_cast<std::size_t>(index) * elementBytes, words.data(), elementBytes);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
void CheckReadback(TextureDetiler& reader, const Surface& surface, std::uint32_t first, std::uint32_t elements, std::uint32_t repetitions, std::size_t extraBytes = 0u) {
|
||||
const auto expected = surface.Expected(first, elements);
|
||||
std::vector<std::byte> CheckReadback(TextureDetiler& reader, const Surface& surface, std::uint32_t first, std::uint32_t elements, std::uint32_t repetitions, std::size_t extraBytes = 0u, std::span<const std::byte> supplied = {}) {
|
||||
const auto expected = supplied.empty() ? surface.Expected(first, elements) : std::vector<std::byte>(supplied.begin(), supplied.end());
|
||||
const auto bytes = ((expected.size() + 3u) & ~std::size_t{3u}) + extraBytes;
|
||||
const auto alignment = std::max<std::size_t>(4u, surface.context.limits.minStorageBufferOffsetAlignment);
|
||||
const auto offset = (16u + alignment - 1u) / alignment * alignment;
|
||||
@@ -291,7 +292,7 @@ void CheckReadback(TextureDetiler& reader, const Surface& surface, std::uint32_t
|
||||
std::fill(output.Bytes().begin(), output.Bytes().end(), std::byte{0xa5});
|
||||
reader.BeginBatch();
|
||||
CommandBatch batch(surface.context);
|
||||
RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
for (std::uint32_t repeat = 0u; repeat < repetitions; ++repeat) {
|
||||
reader.DispatchMultisampleReadback(batch.Handle(), surface.format, output.Handle(), offset, bytes, surface.arrayView, surface.width, surface.height, surface.layers, surface.samples, first, elements);
|
||||
if (repeat + 1u < repetitions) RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
@@ -304,6 +305,61 @@ void CheckReadback(TextureDetiler& reader, const Surface& surface, std::uint32_t
|
||||
if (mismatch.first != expected.end()) std::printf("readback mismatch: format=%u first=%u byte=%zu expected=0x%02x actual=0x%02x\n", static_cast<std::uint32_t>(surface.format), first, static_cast<std::size_t>(mismatch.first - expected.begin()), std::to_integer<unsigned>(*mismatch.first), std::to_integer<unsigned>(*mismatch.second));
|
||||
Require(std::equal(expected.begin(), expected.end(), data.begin() + offset), "multisample readback changed a rendered sample, component, layer or pixel");
|
||||
for (std::size_t index = 0u; index < data.size(); ++index) Require((index >= offset && index < offset + expected.size()) || data[index] == std::byte{0xa5}, "multisample readback changed a buffer sentinel or packed tail");
|
||||
return {data.begin() + offset, data.begin() + offset + expected.size()};
|
||||
}
|
||||
|
||||
void CheckRetile(TextureDetiler& reader, const Surface& surface) {
|
||||
const auto elements = surface.width * surface.height * surface.layers * surface.samples;
|
||||
const auto linear = CheckReadback(reader, surface, 0u, elements, 1u);
|
||||
const auto expected = surface.Expected(0u, elements);
|
||||
constexpr std::array<std::array<std::uint32_t, 3>, 7> coordinates{{
|
||||
{0u, 0u, 0u}, {128u, 66u, 16u}, {64u, 32u, 1u}, {32u, 64u, 2u},
|
||||
{63u, 31u, 7u}, {127u, 65u, 15u}, {1u, 1u, 3u}
|
||||
}};
|
||||
constexpr std::array<TextureTileMode, 2> modes{TextureTileMode::kR64KBX, TextureTileMode::kZ64KBX};
|
||||
constexpr std::array<std::array<std::uint64_t, 7>, 2> references{{
|
||||
{0ull, 6621216ull, 493568ull, 1016832ull, 2766588ull, 6170780ull, 1215508ull},
|
||||
{0ull, 6637700ull, 460808ull, 1000460ull, 2815728ull, 6219892ull, 1182776ull}
|
||||
}};
|
||||
for (std::size_t mode = 0u; mode < modes.size(); ++mode) {
|
||||
for (const auto pipeBankXor : {0u, 0x8100u}) {
|
||||
const MultisampleTextureLayout layout(modes[mode], surface.elementBytes, surface.samples, surface.width, surface.height, surface.layers, pipeBankXor);
|
||||
std::vector<std::byte> tiled(static_cast<std::size_t>(layout.Bytes()) + 32u, std::byte{0xa5});
|
||||
auto payload = std::span(tiled).subspan(16u, layout.Bytes());
|
||||
layout.Tile(linear, payload);
|
||||
std::vector<bool> covered(payload.size());
|
||||
std::size_t position = 0u;
|
||||
for (std::uint32_t layer = 0u; layer < surface.layers; ++layer) {
|
||||
for (std::uint32_t y = 0u; y < surface.height; ++y) {
|
||||
for (std::uint32_t x = 0u; x < surface.width; ++x) {
|
||||
for (std::uint32_t fragment = 0u; fragment < surface.samples; ++fragment) {
|
||||
const auto address = layout.Offset(x, y, layer, fragment);
|
||||
Require(address + surface.elementBytes <= payload.size(), "readback retile exceeds its allocation");
|
||||
for (std::uint32_t byte = 0u; byte < surface.elementBytes; ++byte) {
|
||||
Require(!covered[address + byte], "readback retile elements overlap");
|
||||
covered[address + byte] = true;
|
||||
Require(payload[address + byte] == expected[position++], "readback and retile disagree on layer, pixel or fragment order");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::size_t index = 0u; index < covered.size(); ++index) Require(covered[index] || payload[index] == std::byte{0xa5}, "readback retile changed tile padding");
|
||||
for (std::size_t index = 0u; index < coordinates.size(); ++index) {
|
||||
const auto& coordinate = coordinates[index];
|
||||
const auto fragment = static_cast<std::uint32_t>(index) % surface.samples;
|
||||
const auto source = (((coordinate[2] * surface.height + coordinate[1]) * surface.width + coordinate[0]) * surface.samples + fragment) * surface.elementBytes;
|
||||
const auto reference = references[mode][index];
|
||||
const auto address = (reference & ~65535ull) | ((reference & 65535ull) ^ pipeBankXor);
|
||||
Require(address + surface.elementBytes <= payload.size() && std::equal(expected.begin() + source, expected.begin() + source + surface.elementBytes, payload.begin() + address), "rendered readback differs at an AMD reference fragment address");
|
||||
}
|
||||
std::vector<std::byte> restored(expected.size());
|
||||
layout.Detile(payload, restored);
|
||||
Require(restored == expected && linear == expected, "readback retile roundtrip changed rendered element bits");
|
||||
for (std::size_t index = 0u; index < tiled.size(); ++index) Require((index >= 16u && index < 16u + payload.size()) || tiled[index] == std::byte{0xa5}, "readback retile changed an allocation sentinel");
|
||||
std::printf("multisample readback retile: format=%u mode=%s xor=0x%x samples=%u layers=%u elements=%u reference-points=7 padding/sentinels/roundtrip=passed\n", static_cast<std::uint32_t>(surface.format), mode == 0u ? "R_X" : "Z_X", pipeBankXor, surface.samples, surface.layers, elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
@@ -331,6 +387,106 @@ void CheckRefusals(TextureDetiler& reader, const Surface& surface) {
|
||||
std::puts("multisample readback invalid-input refusals passed");
|
||||
}
|
||||
|
||||
std::vector<std::byte> Upload(TextureDetiler& transfer, TextureDetiler& reader, const Surface& surface, std::vector<std::byte>& expected, std::uint32_t first, std::uint32_t elements, std::uint32_t repetitions = 1u, std::span<const std::byte> supplied = {}) {
|
||||
const auto bytes = static_cast<std::size_t>(elements) * surface.elementBytes;
|
||||
const auto padded = (bytes + 3u) & ~std::size_t{3};
|
||||
const auto alignment = std::max<std::size_t>({4u, surface.elementBytes, surface.context.limits.minStorageBufferOffsetAlignment});
|
||||
const auto offset = (16u + alignment - 1u) / alignment * alignment;
|
||||
Buffer source(surface.context, offset + padded + 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
std::fill(source.Bytes().begin(), source.Bytes().end(), std::byte{0x5a});
|
||||
Require(supplied.empty() || supplied.size() == bytes, "multisample upload fixture source size mismatch");
|
||||
for (std::size_t index = 0; index < bytes; ++index) source.Bytes()[offset + index] = supplied.empty() ? static_cast<std::byte>((index * 29u + first * 17u + elements * 7u) & 255u) : supplied[index];
|
||||
const std::vector<std::byte> retained(source.Bytes().begin(), source.Bytes().end());
|
||||
std::copy_n(retained.begin() + offset, bytes, expected.begin() + static_cast<std::size_t>(first) * surface.elementBytes);
|
||||
const auto stride = 256u / alignment * alignment;
|
||||
Require(stride != 0u, "upload fixture cannot hold an aligned chunk");
|
||||
transfer.BeginBatch();
|
||||
CommandBatch batch(surface.context);
|
||||
RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
for (std::uint32_t repeat = 0; repeat < repetitions; ++repeat) {
|
||||
for (std::size_t done = 0; done < bytes; done += stride) {
|
||||
const auto count = std::min(stride, bytes - done);
|
||||
transfer.DispatchMultisampleImage(batch.Handle(), surface.elementBytes, source.Handle(), offset + done, (count + 3u) & ~std::size_t{3}, surface.arrayView, surface.width, surface.height, surface.layers, surface.samples, first + static_cast<std::uint32_t>(done / surface.elementBytes), static_cast<std::uint32_t>(count / surface.elementBytes));
|
||||
}
|
||||
RecordMemoryBarrier(surface.context, batch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
|
||||
}
|
||||
batch.SubmitAndWait();
|
||||
source.Invalidate();
|
||||
Require(std::equal(retained.begin(), retained.end(), source.Bytes().begin()), "multisample upload changed source bytes or sentinels");
|
||||
return CheckReadback(reader, surface, 0u, surface.width * surface.height * surface.layers * surface.samples, 1u, 0u, expected);
|
||||
}
|
||||
|
||||
void CheckUploadRefusals(TextureDetiler& transfer, const Surface& surface) {
|
||||
Buffer source(surface.context, 256u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
||||
CommandBatch batch(surface.context);
|
||||
const auto call = [&](std::uint32_t bytes, std::uint32_t fragments, std::uint32_t first, std::uint32_t elements, std::uint64_t offset = 0u, std::uint64_t range = 4u) {
|
||||
transfer.DispatchMultisampleImage(batch.Handle(), bytes, source.Handle(), offset, range, surface.arrayView, 1u, 1u, 1u, fragments, first, elements);
|
||||
};
|
||||
for (const auto bytes : {0u, 3u, 32u}) Refused([&] { call(bytes, 4u, 0u, 1u); }, "element size is unsupported");
|
||||
for (const auto fragments : {0u, 1u, 3u, 16u}) Refused([&] { call(4u, fragments, 0u, 1u); }, "fragment count is unsupported");
|
||||
for (const auto range : {std::array{0u, 0u}, std::array{4u, 1u}, std::array{3u, 2u}, std::array{0xffffffffu, 2u}}) Refused([&] { call(4u, 4u, range[0], range[1]); }, "element range is invalid");
|
||||
for (const auto range : {std::array<std::uint64_t, 2>{1u, 4u}, std::array<std::uint64_t, 2>{0u, 0u}, std::array<std::uint64_t, 2>{0u, 3u}, std::array<std::uint64_t, 2>{0u, 260u}, std::array<std::uint64_t, 2>{~std::uint64_t{0} - 3u, 8u}}) Refused([&] { call(4u, 4u, 0u, 1u, range[0], range[1]); }, "buffer range is invalid");
|
||||
Refused([&] { transfer.DispatchMultisampleImage(VK_NULL_HANDLE, 4u, source.Handle(), 0u, 4u, surface.arrayView, 1u, 1u, 1u, 4u, 0u, 1u); }, "requires commands, buffer and array view");
|
||||
Refused([&] { transfer.DispatchMultisampleImage(batch.Handle(), 4u, VK_NULL_HANDLE, 0u, 4u, surface.arrayView, 1u, 1u, 1u, 4u, 0u, 1u); }, "requires commands, buffer and array view");
|
||||
Refused([&] { transfer.DispatchMultisampleImage(batch.Handle(), 4u, source.Handle(), 0u, 4u, VK_NULL_HANDLE, 1u, 1u, 1u, 4u, 0u, 1u); }, "requires commands, buffer and array view");
|
||||
for (const auto extent : {std::array{0u, 1u, 1u}, std::array{1u, 0u, 1u}, std::array{1u, 1u, 0u}, std::array{16385u, 1u, 1u}, std::array{1u, 16385u, 1u}, std::array{1u, 1u, 8193u}}) Refused([&] { transfer.DispatchMultisampleImage(batch.Handle(), 4u, source.Handle(), 0u, 4u, surface.arrayView, extent[0], extent[1], extent[2], 4u, 0u, 1u); }, "extent is unsupported");
|
||||
Refused([&] { transfer.DispatchMultisampleImage(batch.Handle(), 4u, source.Handle(), 0u, 4u, surface.arrayView, 16384u, 16384u, 8192u, 8u, 0u, 1u); }, "element range is invalid");
|
||||
for (const bool multisample : {false, true}) {
|
||||
auto unsupported = surface.context;
|
||||
if (multisample) unsupported.storageImageMultisample = false;
|
||||
else unsupported.storageImageWriteWithoutFormat = false;
|
||||
TextureDetiler unavailable(unsupported);
|
||||
Refused([&] { unavailable.DispatchMultisampleImage(batch.Handle(), 4u, source.Handle(), 0u, 4u, surface.arrayView, 1u, 1u, 1u, 4u, 0u, 1u); }, "does not enable multisampled storage image writes without format");
|
||||
}
|
||||
}
|
||||
|
||||
void CheckUpload(const Context& context) {
|
||||
Require(context.storageImageMultisample && context.storageImageWriteWithoutFormat, "upload fixture requires enabled multisampled formatless storage writes");
|
||||
auto limited = context;
|
||||
limited.limits.maxComputeWorkGroupCount[0] = 1u;
|
||||
limited.limits.maxStorageBufferRange = 256u;
|
||||
TextureDetiler transfer(limited);
|
||||
TextureDetiler reader(context);
|
||||
std::uint32_t exercised = 0u;
|
||||
for (const auto bytes : {1u, 2u, 4u, 8u, 16u}) {
|
||||
const auto format = TextureDetiler::ImageElementFormat(bytes);
|
||||
for (const auto samples : {2u, 4u, 8u}) {
|
||||
if (!SurfaceSupported(context, format, samples, 17u, 5u, 2u, true)) continue;
|
||||
Surface surface(context, format, bytes, samples, 17u, 5u, 2u, true);
|
||||
surface.Render();
|
||||
const auto elements = surface.width * surface.height * surface.layers * samples;
|
||||
auto expected = surface.Expected(0u, elements);
|
||||
Upload(transfer, reader, surface, expected, 3u, 1u);
|
||||
Upload(transfer, reader, surface, expected, 1u, elements - 3u);
|
||||
Upload(transfer, reader, surface, expected, 0u, elements);
|
||||
Upload(transfer, reader, surface, expected, elements - 1u, 1u, samples == 4u ? 65u : 1u);
|
||||
if (bytes == 4u && samples == 4u) CheckUploadRefusals(transfer, surface);
|
||||
std::printf("multisample upload: bytes=%u samples=%u layers=2 full/partial/packed/source-sentinels/chunks/reuse=passed\n", bytes, samples);
|
||||
++exercised;
|
||||
}
|
||||
}
|
||||
Require(exercised != 0u, "no supported multisample upload subcase was exercised");
|
||||
const MultisampleTextureLayout active(TextureTileMode::kR64KBX, 8u, 4u, 17u, 5u, 2u);
|
||||
if (SurfaceSupported(context, VK_FORMAT_R32G32_UINT, 4u, active.Pitch(), active.PaddedHeight(), 2u, true)) {
|
||||
for (const auto xorValue : {0u, 0x8100u}) {
|
||||
const MultisampleTextureLayout layout(TextureTileMode::kR64KBX, 8u, 4u, active.Pitch(), active.PaddedHeight(), 2u, xorValue);
|
||||
std::vector<std::byte> tiled(static_cast<std::size_t>(layout.Bytes()));
|
||||
for (std::size_t byte = 0; byte < tiled.size(); ++byte) tiled[byte] = static_cast<std::byte>((byte * 37u + (byte >> 8u) * 13u + (byte >> 15u) * 71u + (byte >> 16u) * 101u) & 255u);
|
||||
std::vector<std::byte> linear(static_cast<std::size_t>(layout.LinearBytes()));
|
||||
layout.Detile(tiled, linear);
|
||||
Surface surface(context, VK_FORMAT_R32G32_UINT, 8u, 4u, layout.Pitch(), layout.PaddedHeight(), 2u, true);
|
||||
surface.Render();
|
||||
const auto elements = surface.width * surface.height * surface.layers * surface.samples;
|
||||
auto expected = surface.Expected(0u, elements);
|
||||
const auto actual = Upload(transfer, reader, surface, expected, 0u, elements, 1u, linear);
|
||||
std::vector<std::byte> retiled(tiled.size(), std::byte{0xa5});
|
||||
layout.Tile(actual, retiled);
|
||||
Require(retiled == tiled, "multisample padded initialization changed color bytes, fragments, layers, XOR or padding");
|
||||
std::printf("multisample padded upload: active=17x5 backing=%ux%u layers=2 fragments=4 bytes=%zu xor=0x%x exact roundtrip passed\n", surface.width, surface.height, retiled.size(), xorValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -343,8 +499,8 @@ int main(int argc, char** argv) {
|
||||
else if (mode == "uint16") { format = VK_FORMAT_R16_UINT; bytes = 2u; }
|
||||
else if (mode == "uint64") { format = VK_FORMAT_R32G32_UINT; bytes = 8u; }
|
||||
else if (mode == "uint128") { format = VK_FORMAT_R32G32B32A32_UINT; bytes = 16u; }
|
||||
else Require(mode == "uint32" || mode == "depth", "unknown multisample readback test mode");
|
||||
if (mode == "depth") format = VK_FORMAT_D32_SFLOAT;
|
||||
else Require(mode == "uint32" || mode == "depth" || mode == "upload" || mode == "retile" || mode == "depth_retile", "unknown multisample readback test mode");
|
||||
if (mode == "depth" || mode == "depth_retile") format = VK_FORMAT_D32_SFLOAT;
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
@@ -357,9 +513,28 @@ int main(int argc, char** argv) {
|
||||
std::puts("skipped, the device does not support per-sample shading");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
if (mode == "upload") {
|
||||
if (!context.storageImageMultisample || !context.storageImageWriteWithoutFormat) {
|
||||
std::puts("skipped, the device does not enable multisampled formatless storage writes");
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
CheckUpload(context);
|
||||
Require(checkedPipelineCacheUses != 0u, "upload fixture did not check pipeline cache use");
|
||||
std::printf("multisample upload guarded pipeline creation checks=%u passed\n", checkedPipelineCacheUses);
|
||||
return 0;
|
||||
}
|
||||
auto limited = context;
|
||||
limited.limits.maxComputeWorkGroupCount[0] = 1u;
|
||||
TextureDetiler reader(limited);
|
||||
if (mode == "retile" || mode == "depth_retile") {
|
||||
if (!SurfaceSupported(context, format, 4u, 129u, 67u, 17u)) return VulkanTestSkipped;
|
||||
Surface surface(context, format, bytes, 4u, 129u, 67u, 17u);
|
||||
surface.Render();
|
||||
CheckRetile(reader, surface);
|
||||
Require(checkedPipelineCacheUses != 0u, "retile fixture did not check pipeline cache use");
|
||||
std::printf("multisample retile guarded pipeline creation checks=%u passed\n", checkedPipelineCacheUses);
|
||||
return 0;
|
||||
}
|
||||
std::uint32_t checked = 0u;
|
||||
for (const auto samples : {2u, 4u, 8u}) {
|
||||
if (!SurfaceSupported(context, format, samples)) continue;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <exception>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -141,14 +142,17 @@ void CheckLdsSplitThrows(AgcDriver::VulkanDevice& device) {
|
||||
request.useCache = false;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
Require(result.workgroupMemoryDwords != 0u, "oversized dispatch: the LDS shader did not take the device-memory path");
|
||||
bool threw = false;
|
||||
try {
|
||||
device.Dispatch(result, 1, device.WorkGroupCountLimit()[1] + 1u, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
} catch (const std::runtime_error&) {
|
||||
threw = true;
|
||||
for (const auto z : {1u, std::numeric_limits<std::uint32_t>::max()}) {
|
||||
bool threw = false;
|
||||
try {
|
||||
device.Dispatch(result, 1, device.WorkGroupCountLimit()[1] + 1u, z, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
} catch (const std::runtime_error& error) {
|
||||
Require(std::string(error.what()).find("a split aliases the device-memory LDS slots") != std::string::npos, "oversized dispatch: wrong LDS split refusal: " + std::string(error.what()));
|
||||
threw = true;
|
||||
}
|
||||
device.WaitIdle();
|
||||
Require(threw, "oversized dispatch: a Y count over the limit with device-memory LDS did not throw");
|
||||
}
|
||||
device.WaitIdle();
|
||||
Require(threw, "oversized dispatch: a Y count over the limit with device-memory LDS did not throw");
|
||||
}
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 6> GroupYCode{
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
|
||||
constexpr std::byte Kept{0x40};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 16> VertexCode{
|
||||
0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u,
|
||||
0x7e060280u, 0x7e0802f2u, 0xf80008cfu, 0x04030201u, 0xbf810000u, 0xbf810000u, 0xbf810000u, 0xbf810000u,
|
||||
};
|
||||
alignas(256) constexpr std::array<std::uint32_t, 4> WhitePixelCode{0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u};
|
||||
alignas(256) std::array<std::byte, 1024> ColorMemory{};
|
||||
alignas(256) std::array<std::byte, 65536> DepthMemory{};
|
||||
alignas(256) std::array<std::byte, 65536> StencilMemory{};
|
||||
|
||||
struct Header {
|
||||
Shader shader{};
|
||||
ShaderUserData userData{};
|
||||
std::array<ShaderRegister, 4> registers{};
|
||||
};
|
||||
|
||||
alignas(8) Header VertexHeader{};
|
||||
alignas(8) Header PixelHeader{};
|
||||
|
||||
void Registered(Header& header, const std::uint32_t* code, std::size_t bytes, std::uint8_t type) {
|
||||
header.shader.file_header = 0x34333231u;
|
||||
header.shader.version = 0x18u;
|
||||
header.shader.header_size = sizeof(Header);
|
||||
header.shader.shader_size = static_cast<std::uint32_t>(bytes);
|
||||
header.shader.code = code;
|
||||
header.shader.type = type;
|
||||
header.shader.user_data = &header.userData;
|
||||
if (type == 1) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(code);
|
||||
header.registers = {{{0x008, static_cast<std::uint32_t>(address >> 8u)}, {0x009, static_cast<std::uint32_t>(address >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast<std::uint32_t>(1.0f)}}};
|
||||
header.shader.sh_registers = header.registers.data();
|
||||
header.shader.num_sh_registers = header.registers.size();
|
||||
}
|
||||
AgcDriverRegisterShader_nid_postfix(&header.shader);
|
||||
}
|
||||
|
||||
void Append(std::vector<std::uint32_t>& commands, std::uint32_t opcode, std::initializer_list<std::uint32_t> payload) {
|
||||
commands.push_back(0xc0000000u | (static_cast<std::uint32_t>(payload.size() - 1) << 16u) | (opcode << 8u));
|
||||
commands.insert(commands.end(), payload);
|
||||
}
|
||||
|
||||
struct StencilState {
|
||||
std::uint32_t depthControl;
|
||||
std::uint32_t operations;
|
||||
std::uint32_t refMask;
|
||||
};
|
||||
|
||||
std::vector<std::uint32_t> Draw(const StencilState& stencilState) {
|
||||
const auto vertex = reinterpret_cast<std::uintptr_t>(VertexCode.data());
|
||||
const auto pixel = reinterpret_cast<std::uintptr_t>(WhitePixelCode.data());
|
||||
const auto color = reinterpret_cast<std::uintptr_t>(ColorMemory.data());
|
||||
const auto depth = reinterpret_cast<std::uintptr_t>(DepthMemory.data());
|
||||
const auto stencil = reinterpret_cast<std::uintptr_t>(StencilMemory.data());
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> context{
|
||||
{0x2d5, 0x2000},
|
||||
{0x1b3, 0}, {0x1b4, 0}, {0x1b6, 0}, {0x207, 0}, {0x200, stencilState.depthControl}, {0x203, 0x800},
|
||||
{0x2dc, 0xaa00}, {0x2f8, 0}, {0x293, 0},
|
||||
{0x80, 0}, {0x8d, 0}, {0x83, 0xffff}, {0x8c, 0xa},
|
||||
{0x2f9, 0x2d}, {0x313, 0x6000}, {0x30e, 0xffffffff}, {0x30f, 0xffffffff},
|
||||
{0x206, 0x43f}, {0x204, 0x80000}, {0x205, 0x240},
|
||||
{0x8e, 0xf}, {0x8f, 0xf}, {0x202, 0xcc0010},
|
||||
{0x1c4, 9}, {0x1c5, 9}, {0x1c3, 4}, {0x31c, 0x28028},
|
||||
{0x31b, 0}, {0x31d, 0}, {0x3b0, (63u << 14u) | 3u},
|
||||
{0x3b8, 0x9000000}, {0x1e0, 0},
|
||||
{0xc, 0}, {0xd, 0x40040},
|
||||
{0x81, 0x80000000}, {0x82, 0x40040},
|
||||
{0x90, 0x80000000}, {0x91, 0x40040},
|
||||
{0x94, 0x80000000}, {0x95, 0x40040},
|
||||
{0x10f, std::bit_cast<std::uint32_t>(32.0f)}, {0x110, std::bit_cast<std::uint32_t>(32.0f)},
|
||||
{0x111, std::bit_cast<std::uint32_t>(-2.0f)}, {0x112, std::bit_cast<std::uint32_t>(2.0f)},
|
||||
{0x113, std::bit_cast<std::uint32_t>(1.0f)}, {0x114, 0},
|
||||
{0xb4, 0}, {0xb5, std::bit_cast<std::uint32_t>(1.0f)},
|
||||
{0x2fe, 0}, {0x302, 0}, {0x306, 0}, {0x30a, 0},
|
||||
{0x318, static_cast<std::uint32_t>(color >> 8u)}, {0x390, static_cast<std::uint32_t>(color >> 40u)}, {0x292, 2},
|
||||
{0x000, 0}, {0x002, 0}, {0x007, (3u << 16u) | 63u},
|
||||
{0x008, 0}, {0x009, std::bit_cast<std::uint32_t>(1.0f)}, {0x00a, 0}, {0x00b, 0},
|
||||
{0x010, 3}, {0x011, 1},
|
||||
{0x012, static_cast<std::uint32_t>(depth >> 8u)}, {0x014, static_cast<std::uint32_t>(depth >> 8u)},
|
||||
{0x013, static_cast<std::uint32_t>(stencil >> 8u)}, {0x015, static_cast<std::uint32_t>(stencil >> 8u)},
|
||||
{0x01a, static_cast<std::uint32_t>(depth >> 40u)}, {0x01c, static_cast<std::uint32_t>(depth >> 40u)},
|
||||
{0x01b, static_cast<std::uint32_t>(stencil >> 40u)}, {0x01d, static_cast<std::uint32_t>(stencil >> 40u)},
|
||||
{0x10b, stencilState.operations}, {0x10c, stencilState.refMask}, {0x10d, stencilState.refMask},
|
||||
};
|
||||
const std::vector<std::pair<std::uint32_t, std::uint32_t>> shader{
|
||||
{0xc8, static_cast<std::uint32_t>(vertex >> 8u)}, {0xc9, static_cast<std::uint32_t>(vertex >> 40u)}, {0x8b, 1u << 1u}, {0x8c, 0},
|
||||
{0x008, static_cast<std::uint32_t>(pixel >> 8u)}, {0x009, static_cast<std::uint32_t>(pixel >> 40u)}, {0x00b, 1u << 1u}, {0x00c, std::bit_cast<std::uint32_t>(1.0f)},
|
||||
};
|
||||
std::vector<std::uint32_t> commands;
|
||||
Append(commands, 0x79, {0x242, 4});
|
||||
for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value});
|
||||
for (const auto& [offset, value] : shader) Append(commands, 0x76, {offset, value});
|
||||
Append(commands, 0x2d, {3, 2});
|
||||
return commands;
|
||||
}
|
||||
|
||||
void Submit(const StencilState& stencilState) {
|
||||
ColorMemory.fill(Kept);
|
||||
auto commands = Draw(stencilState);
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
Require(sceAgcDriverSubmitDcb(&packet) == 0, "the stencil draw submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
}
|
||||
|
||||
constexpr std::uint32_t StencilEnable = 1u;
|
||||
constexpr std::uint32_t Equal = 2u << 8u;
|
||||
constexpr std::uint32_t LessEqual = 3u << 8u;
|
||||
constexpr std::uint32_t Greater = 4u << 8u;
|
||||
constexpr std::uint32_t NotEqual = 5u << 8u;
|
||||
constexpr std::uint32_t Always = 7u << 8u;
|
||||
|
||||
std::uint32_t RefMask(std::uint32_t test, std::uint32_t compareMask, std::uint32_t writeMask, std::uint32_t opValue) {
|
||||
return test | (compareMask << 8u) | (writeMask << 16u) | (opValue << 24u);
|
||||
}
|
||||
|
||||
std::array<std::byte, 1024> Fill(std::uint32_t value) {
|
||||
Submit({StencilEnable | Always, 0x30u, RefMask(value, 0xff, 0xff, 0)});
|
||||
const auto coverage = ColorMemory;
|
||||
Require(std::count(coverage.begin(), coverage.end(), Kept) != static_cast<std::ptrdiff_t>(coverage.size()), "the stencil fill covered no pixels");
|
||||
return coverage;
|
||||
}
|
||||
|
||||
void Expect(const std::array<std::byte, 1024>& coverage, std::uint32_t value, const std::string& what) {
|
||||
Submit({StencilEnable | Equal, 0u, RefMask(value, 0xff, 0, 0)});
|
||||
std::size_t drawn = 0;
|
||||
for (std::size_t pixel = 0; pixel < coverage.size(); pixel += 4u) {
|
||||
const bool covered = coverage[pixel] != Kept;
|
||||
drawn += covered;
|
||||
for (std::size_t channel = 0; channel < 4u; ++channel) Require(ColorMemory[pixel + channel] == (covered ? std::byte{255} : Kept), what + ": the stencil is not " + std::to_string(value) + " exactly where the draw covered");
|
||||
}
|
||||
Require(drawn != 0, what + ": the probe covered no pixels");
|
||||
}
|
||||
|
||||
void Case(const std::string& what, std::uint32_t initial, const StencilState& stencilState, std::uint32_t expected, bool testPasses = true) {
|
||||
const auto coverage = Fill(initial);
|
||||
Submit(stencilState);
|
||||
if (testPasses) Require(ColorMemory == coverage, what + ": the draw under test did not color exactly the filled pixels");
|
||||
else Require(std::all_of(ColorMemory.begin(), ColorMemory.end(), [](auto value) { return value == Kept; }), what + ": a draw whose stencil test fails wrote color");
|
||||
Expect(coverage, expected, what);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
{
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
}
|
||||
Registered(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2);
|
||||
Registered(PixelHeader, WhitePixelCode.data(), sizeof(WhitePixelCode), 1);
|
||||
const std::array<ShaderRegister, 3> pixelContext{{{0x203, 0x800}, {0x1c4, 9}, {0x1b6, 0}}};
|
||||
AgcDriverResolveShaderAbi_nid_postfix(&PixelHeader.shader, pixelContext, {});
|
||||
Case("an equal test replaced by the operation value", 0xff, {StencilEnable | Equal, 0x40u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40);
|
||||
Case("an equal test replaced by the operation value on pass and depth fail", 0xff, {StencilEnable | Equal, 0x440u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40);
|
||||
Case("an equal test replaced by 0xff", 0x25, {StencilEnable | Equal, 0x20u, RefMask(0x25, 0xff, 0xff, 0)}, 0xff);
|
||||
Case("an equal test replaced through a partial write mask", 0xff, {StencilEnable | Equal, 0x40u, RefMask(0xff, 0xff, 0xf0, 0x40)}, 0x4f);
|
||||
Case("a failed not-equal test replaced by the operation value", 0xff, {StencilEnable | NotEqual, 0x4u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40, false);
|
||||
Case("a less-or-equal test against 0xff replaced by the operation value", 0xff, {StencilEnable | LessEqual, 0x40u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40);
|
||||
Case("a failed greater test against 0xff replaced by the operation value", 0xff, {StencilEnable | Greater, 0x4u, RefMask(0xff, 0xff, 0xff, 0x40)}, 0x40, false);
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
std::puts("stencil replacements with a known stencil value passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
try { AgcDriverShutdown_nid_postfix(); } catch (...) {}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(mapped);
|
||||
const auto aligned = (begin + UnitBytes - 1) & ~(static_cast<std::uintptr_t>(UnitBytes) - 1);
|
||||
if (aligned != begin) munmap(mapped, aligned - begin);
|
||||
if (aligned + bytes != begin + bytes + UnitBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + bytes + UnitBytes - aligned);
|
||||
if (aligned + bytes != begin + bytes + UnitBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + UnitBytes - aligned);
|
||||
block = reinterpret_cast<std::uint8_t*>(aligned);
|
||||
if (AgcDriver::GuestMemory::WriteWatched()) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
|
||||
}
|
||||
|
||||
@@ -142,14 +142,28 @@ int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
constexpr std::array<Case, 7> cases{{
|
||||
{2u, 1u, true, "OOB_SELECT 2, one record"},
|
||||
constexpr std::uint32_t Wzyx = 7u | (6u << 3u) | (1u << 6u) | (0u << 9u);
|
||||
constexpr std::array<Case, 20> cases{{
|
||||
{0u, 0u, false, "OOB_SELECT 0, no records"},
|
||||
{0u, 1u, false, "OOB_SELECT 0, one record"},
|
||||
{0u, 16u, false, "OOB_SELECT 0, records cover the element's bytes"},
|
||||
{0u, 0xffffffffu, false, "OOB_SELECT 0, NUM_RECORDS 0xffffffff"},
|
||||
{0u, 0xffffffffu, false, "OOB_SELECT 0, NUM_RECORDS 0xffffffff, selectors W Z 1 0", Wzyx},
|
||||
{1u, 0u, false, "OOB_SELECT 1, no records"},
|
||||
{1u, 3u, true, "OOB_SELECT 1, NUM_RECORDS counts the three vertices' records"},
|
||||
{1u, 4u, true, "OOB_SELECT 1, NUM_RECORDS past the vertices"},
|
||||
{1u, 0xffffffffu, true, "OOB_SELECT 1, NUM_RECORDS 0xffffffff"},
|
||||
{1u, 3u, true, "OOB_SELECT 1, NUM_RECORDS 3, selectors W Z 1 0", Wzyx},
|
||||
{2u, 0u, false, "OOB_SELECT 2, no records"},
|
||||
{3u, 16u, true, "OOB_SELECT 3, whole element in range"},
|
||||
{2u, 1u, true, "OOB_SELECT 2, one record"},
|
||||
{2u, 16u, true, "OOB_SELECT 2, whole element in range"},
|
||||
{3u, 0u, false, "OOB_SELECT 3, no records"},
|
||||
{0u, 16u, true, "OOB_SELECT 0, whole element in range"},
|
||||
{3u, 16u, true, "OOB_SELECT 3, selectors W Z 1 0", 7u | (6u << 3u) | (1u << 6u) | (0u << 9u)},
|
||||
{3u, 0u, false, "OOB_SELECT 3, no records, selectors W Z 1 0", 7u | (6u << 3u) | (1u << 6u) | (0u << 9u)},
|
||||
{3u, 8u, false, "OOB_SELECT 3, the element only partly fits"},
|
||||
{3u, 15u, false, "OOB_SELECT 3, the element misses its last byte"},
|
||||
{3u, 16u, true, "OOB_SELECT 3, whole element in range"},
|
||||
{3u, 0xffffffffu, true, "OOB_SELECT 3, NUM_RECORDS 0xffffffff"},
|
||||
{3u, 16u, true, "OOB_SELECT 3, selectors W Z 1 0", Wzyx},
|
||||
{3u, 8u, false, "OOB_SELECT 3, partly fits, selectors W Z 1 0", Wzyx},
|
||||
}};
|
||||
for (const auto& value : cases) {
|
||||
Draw(*device, value);
|
||||
|
||||
@@ -10,7 +10,7 @@ void main() {
|
||||
uint y = uint(gl_FragCoord.y);
|
||||
#if READ_DEPTH
|
||||
uint element = (x + y * params.width + params.layer * params.width * params.height) * params.samples + uint(gl_SampleID);
|
||||
gl_FragDepth = 0.125 + float(element + 1u) / 65536.0;
|
||||
gl_FragDepth = 0.125 + float(element + 1u) / 1048576.0;
|
||||
#else
|
||||
uint value = x + y * params.width + params.layer * params.width * params.height + uint(gl_SampleID) * 31u;
|
||||
color = uvec4(0x91352700u ^ value, 0x45219b00u ^ (value * 3u), 0xc7391d00u ^ (value * 5u), 0x6b43e100u ^ (value * 7u));
|
||||
|
||||
@@ -12,6 +12,37 @@ static void AjmStub(const char* name) {
|
||||
NotImplemented_nid_no_patch(name);
|
||||
}
|
||||
|
||||
struct WVorbisHeaderPacketParameter {
|
||||
std::size_t size;
|
||||
std::int32_t channels;
|
||||
std::uint32_t sampleRate;
|
||||
std::int32_t bytesPerSecond;
|
||||
std::int32_t shortBlockExponent;
|
||||
std::int32_t longBlockExponent;
|
||||
std::uint32_t reserved;
|
||||
const void* setupPacket;
|
||||
std::size_t setupPacketSize;
|
||||
void* output;
|
||||
std::size_t outputCapacity;
|
||||
std::size_t outputSize;
|
||||
};
|
||||
|
||||
struct WVorbisSetupPacketParameter {
|
||||
std::size_t size;
|
||||
std::int32_t channels;
|
||||
std::uint32_t reserved;
|
||||
const void* input;
|
||||
std::size_t inputSize;
|
||||
void* output;
|
||||
std::size_t outputCapacity;
|
||||
std::size_t outputSize;
|
||||
};
|
||||
|
||||
static_assert(sizeof(WVorbisHeaderPacketParameter) == 72);
|
||||
static_assert(offsetof(WVorbisHeaderPacketParameter, setupPacket) == 32);
|
||||
static_assert(sizeof(WVorbisSetupPacketParameter) == 56);
|
||||
static_assert(offsetof(WVorbisSetupPacketParameter, input) == 16);
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAjmBatchJobEncode(AjmBatchInfo* info, uint32_t instance, const void* pcm_input, size_t pcm_input_size, void* bitstream_output, size_t bitstream_output_size, void* result) {
|
||||
@@ -32,12 +63,12 @@ const char* APS5_VABI sceAjmStrError(int error) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(void) {
|
||||
int APS5_VABI sceAjmDecWVorbisCreateHeaderPacket(WVorbisHeaderPacketParameter* parameter) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAjmDecWVorbisCreateSetupPacket(void) {
|
||||
int APS5_VABI sceAjmDecWVorbisCreateSetupPacket(WVorbisSetupPacketParameter* parameter) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@ static int AppendNop(Apr::CommandBufferObject* buffer, std::uint32_t dwords, con
|
||||
if (bytes > RemainingCommandBytes(*buffer)) return SCE_KERNEL_ERROR_EBUSY;
|
||||
const Apr::CommandHeader header{Apr::Opcode::Nop, static_cast<std::uint32_t>(bytes)};
|
||||
std::uint8_t* destination = buffer->base + buffer->offset;
|
||||
std::memset(destination, 0, bytes);
|
||||
std::memcpy(destination, &header, sizeof(header));
|
||||
if (data) std::memcpy(destination + sizeof(header), data, std::uint64_t{dwords} * 4u);
|
||||
buffer->offset += static_cast<std::uint32_t>(bytes);
|
||||
@@ -432,7 +431,6 @@ int APS5_VABI sceAmprCommandBufferNop(Apr::CommandBufferObject* buffer, std::uin
|
||||
|
||||
int APS5_VABI sceAmprCommandBufferNopWithData(Apr::CommandBufferObject* buffer, std::uint32_t dwords, const std::uint32_t* data) {
|
||||
if (dwords > 15u) return SCE_KERNEL_ERROR_EINVAL;
|
||||
if (dwords != 0u && !data) throw std::invalid_argument("sceAmprCommandBufferNopWithData: null data");
|
||||
return AppendNop(buffer, dwords, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/RemoveTree.hpp"
|
||||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
static constexpr int SCE_APP_CONTENT_ERROR_NOT_INITIALIZED = static_cast<int>(0x80D90001);
|
||||
static constexpr int SCE_APP_CONTENT_ERROR_PARAMETER = static_cast<int>(0x80D90002);
|
||||
static constexpr int SCE_APP_CONTENT_ERROR_BUSY = static_cast<int>(0x80D90003);
|
||||
static constexpr int SCE_APP_CONTENT_ERROR_NOT_MOUNTED = static_cast<int>(0x80D90004);
|
||||
@@ -30,6 +33,20 @@ struct AppContentAddcontInfo {
|
||||
|
||||
namespace {
|
||||
|
||||
std::atomic_bool initialized{false};
|
||||
std::mutex initializationMutex;
|
||||
|
||||
std::uint32_t compiledSdkVersion() {
|
||||
const auto* process = static_cast<const std::byte*>(ApplicationProcessParameters_nid_no_patch());
|
||||
std::uint32_t version;
|
||||
std::memcpy(&version, process + 16, sizeof(version));
|
||||
return version;
|
||||
}
|
||||
|
||||
bool validEntitlementLabel(const NpUnifiedEntitlementLabel& label, std::uint32_t minimumSdkVersion) {
|
||||
return compiledSdkVersion() < minimumSdkVersion || std::all_of(std::begin(label.padding), std::end(label.padding), [](char value) { return value == 0; });
|
||||
}
|
||||
|
||||
struct TemporaryData {
|
||||
std::mutex mutex;
|
||||
const std::filesystem::path directory = ResolvePath_nid_no_patch(TEMPORARY_MOUNT_POINT);
|
||||
@@ -57,30 +74,37 @@ static void ClearDirectory(const std::filesystem::path& directory) {
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAppContentAddcontMount(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label, AppContentMountPoint* mount_point) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)service_label;
|
||||
if (!entitlement_label || !mount_point) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!validEntitlementLabel(*entitlement_label, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentAddcontUnmount(const AppContentMountPoint* mount_point) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!mount_point) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentGetAddcontInfo(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label, AppContentAddcontInfo* info) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)service_label;
|
||||
if (!entitlement_label || !info) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!validEntitlementLabel(*entitlement_label, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentGetAddcontInfoList(uint32_t service_label, AppContentAddcontInfo* list, uint32_t list_num, uint32_t* hit_num) {
|
||||
(void)service_label;
|
||||
if ((!list || list_num == 0) && !hit_num) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (hit_num) *hit_num = 0;
|
||||
int APS5_VABI sceAppContentGetAddcontInfoList(uint32_t serviceLabel, AppContentAddcontInfo* list, uint32_t listNum, uint32_t* hitNum) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)serviceLabel;
|
||||
if (!hitNum) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
*hitNum = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentAppParamGetInt(uint32_t param_id, int32_t* value) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!value) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
switch (param_id) {
|
||||
case APPPARAM_ID_SKU_FLAG:
|
||||
@@ -95,6 +119,7 @@ int APS5_VABI sceAppContentAppParamGetInt(uint32_t param_id, int32_t* value) {
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentDownloadDataFormat(const AppContentMountPoint* mount_point) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!mount_point) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) return SCE_APP_CONTENT_ERROR_NOT_MOUNTED;
|
||||
const auto directory = ResolvePath_nid_no_patch(DOWNLOAD_MOUNT_POINT);
|
||||
@@ -103,12 +128,13 @@ int APS5_VABI sceAppContentDownloadDataFormat(const AppContentMountPoint* mount_
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMountPoint* mount_point, size_t* available_space_kb) {
|
||||
if (!mount_point || !available_space_kb) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (std::strncmp(mount_point->data, DOWNLOAD_MOUNT_POINT, sizeof(mount_point->data)) != 0) return SCE_APP_CONTENT_ERROR_NOT_MOUNTED;
|
||||
int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMountPoint* mountPoint, size_t* availableSpaceKb) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!mountPoint || !availableSpaceKb) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (std::strncmp(mountPoint->data, DOWNLOAD_MOUNT_POINT, sizeof(mountPoint->data)) != 0) return SCE_APP_CONTENT_ERROR_NOT_MOUNTED;
|
||||
const std::uint64_t quotaKb = GetAppDownloadDataSizeMiB_nid_postfix() * 1024u;
|
||||
if (quotaKb == 0) {
|
||||
*available_space_kb = 0;
|
||||
*availableSpaceKb = 0;
|
||||
return 0;
|
||||
}
|
||||
const auto directory = ResolvePath_nid_no_patch(DOWNLOAD_MOUNT_POINT);
|
||||
@@ -118,19 +144,27 @@ int APS5_VABI sceAppContentDownloadDataGetAvailableSpaceKb(const AppContentMount
|
||||
if (entry.is_regular_file()) usedKb += (entry.file_size() + 1023u) / 1024u;
|
||||
}
|
||||
const std::uint64_t hostKb = std::filesystem::space(directory).available / 1024u;
|
||||
*available_space_kb = static_cast<size_t>(std::min(quotaKb - std::min(quotaKb, usedKb), hostKb));
|
||||
*availableSpaceKb = static_cast<size_t>(std::min(quotaKb - std::min(quotaKb, usedKb), hostKb));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentInitialize(const AppContentInitParam* init_param, AppContentBootParam* boot_param) {
|
||||
(void)init_param;
|
||||
if (!boot_param) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
int APS5_VABI sceAppContentInitialize(const AppContentInitParam* initParam, AppContentBootParam* bootParam) {
|
||||
std::lock_guard lock(initializationMutex);
|
||||
const auto sdkVersion = compiledSdkVersion();
|
||||
if (sdkVersion >= 0x01500000) {
|
||||
if (initialized.load()) return SCE_APP_CONTENT_ERROR_BUSY;
|
||||
if (initParam == nullptr || bootParam == nullptr) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!std::all_of(std::begin(initParam->reserved), std::end(initParam->reserved), [](char value) { return value == 0; }) || !std::all_of(std::begin(bootParam->reserved1), std::end(bootParam->reserved1), [](char value) { return value == 0; }) || !std::all_of(std::begin(bootParam->reserved2), std::end(bootParam->reserved2), [](char value) { return value == 0; })) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
}
|
||||
if (bootParam == nullptr) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
Temporary();
|
||||
std::memset(boot_param, 0, sizeof(*boot_param));
|
||||
std::memset(bootParam, 0, sizeof(*bootParam));
|
||||
initialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentTemporaryDataFormat(const AppContentMountPoint* mountPoint) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!mountPoint) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
auto& state = Temporary();
|
||||
std::lock_guard lock(state.mutex);
|
||||
@@ -140,6 +174,7 @@ int APS5_VABI sceAppContentTemporaryDataFormat(const AppContentMountPoint* mount
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentTemporaryDataGetAvailableSpaceKb(const AppContentMountPoint* mountPoint, size_t* availableSpaceKb) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!mountPoint || !availableSpaceKb) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
auto& state = Temporary();
|
||||
std::lock_guard lock(state.mutex);
|
||||
@@ -149,6 +184,7 @@ int APS5_VABI sceAppContentTemporaryDataGetAvailableSpaceKb(const AppContentMoun
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentTemporaryDataMount2(uint32_t option, AppContentMountPoint* mountPoint) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!mountPoint || option > TEMPORARY_DATA_OPTION_FORMAT) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
auto& state = Temporary();
|
||||
std::lock_guard lock(state.mutex);
|
||||
@@ -163,6 +199,7 @@ int APS5_VABI sceAppContentTemporaryDataMount2(uint32_t option, AppContentMountP
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentTemporaryDataUnmount(const AppContentMountPoint* mountPoint) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
if (!mountPoint) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
auto& state = Temporary();
|
||||
std::lock_guard lock(state.mutex);
|
||||
@@ -174,20 +211,26 @@ int APS5_VABI sceAppContentTemporaryDataUnmount(const AppContentMountPoint* moun
|
||||
|
||||
|
||||
int APS5_VABI sceAppContentAddcontEnqueueDownload(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)service_label;
|
||||
if (!entitlement_label) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!validEntitlementLabel(*entitlement_label, 0x01500000)) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentAddcontDelete(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)service_label;
|
||||
if (!entitlement_label) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!validEntitlementLabel(*entitlement_label, 0x03500000)) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAppContentGetAddcontDownloadProgress(uint32_t service_label, const NpUnifiedEntitlementLabel* entitlement_label, void* progress) {
|
||||
if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED;
|
||||
(void)service_label;
|
||||
if (!entitlement_label || !progress) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
if (!validEntitlementLabel(*entitlement_label, 0x03500000)) return SCE_APP_CONTENT_ERROR_PARAMETER;
|
||||
return SCE_APP_CONTENT_ERROR_NOT_FOUND;
|
||||
}
|
||||
int APS5_VABI sceAppContentAddcontEnqueueDownloadByEntitlemetId(void) {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
struct AppInstallProgressInfo;
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAppInstUtilInitialize(void) {
|
||||
@@ -25,7 +27,7 @@ int APS5_VABI sceAppInstUtilAppIsInInstalling(int32_t* installing) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilAppIsInUpdating() {
|
||||
int APS5_VABI sceAppInstUtilAppIsInUpdating(const char* titleId, bool* updating) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
@@ -33,11 +35,11 @@ int APS5_VABI sceAppInstUtilAppExists(const char* titleId, int32_t* exists) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilAppGetSize() {
|
||||
int APS5_VABI sceAppInstUtilAppGetSize(const char* titleId, std::uint64_t* size) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilAppInstallPkg() {
|
||||
int APS5_VABI sceAppInstUtilAppInstallPkg(const char* packagePath, std::uint64_t serviceArgument) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
@@ -53,11 +55,11 @@ int APS5_VABI sceAppInstUtilAppUnInstall(const char* titleId) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilAppUnInstallAddcont() {
|
||||
int APS5_VABI sceAppInstUtilAppUnInstallAddcont(const char* titleId, const char* contentLabel) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilAppUnInstallPat() {
|
||||
int APS5_VABI sceAppInstUtilAppUnInstallPat(const char* titleId) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
@@ -65,19 +67,19 @@ int APS5_VABI sceAppInstUtilAppUnInstallTheme() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilAppUnInstallTypes() {
|
||||
int APS5_VABI sceAppInstUtilAppUnInstallTypes(const char* titleId, std::uint32_t types) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilGetInstallProgressInfo() {
|
||||
int APS5_VABI sceAppInstUtilGetInstallProgressInfo(const char* titleId, AppInstallProgressInfo* info) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilGetInstallProgress() {
|
||||
int APS5_VABI sceAppInstUtilGetInstallProgress(const char* titleId, void* progress) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAppInstUtilGetPrimaryAppSlot() {
|
||||
int APS5_VABI sceAppInstUtilGetPrimaryAppSlot(const char* titleId, void* slot) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -199,22 +199,17 @@ int APS5_VABI sceAudio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_leve
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudio3dPortGetAttributesSupported(uint32_t port_id, uint32_t* attributes, uint32_t* num_attributes) {
|
||||
int APS5_VABI sceAudio3dPortGetAttributesSupported(uint32_t portId, uint32_t* attributes, uint32_t* numAttributes) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (num_attributes == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (!PortIsOpen(portId)) return AUDIO3D_ERROR_INVALID_PORT;
|
||||
if (numAttributes == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
||||
constexpr std::uint32_t supported[]{
|
||||
AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL,
|
||||
AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS,
|
||||
AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE,
|
||||
};
|
||||
if (attributes == nullptr) {
|
||||
*num_attributes = std::size(supported);
|
||||
return 0;
|
||||
}
|
||||
const std::uint32_t count = std::min<std::uint32_t>(*num_attributes, std::size(supported));
|
||||
std::copy_n(std::begin(supported), count, attributes);
|
||||
*num_attributes = count;
|
||||
*numAttributes = std::size(supported);
|
||||
if (attributes != nullptr) std::copy(std::begin(supported), std::end(supported), attributes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ constexpr int AUDIO_IN_ERROR_INVALID_HANDLE = static_cast<int>(0x80260101);
|
||||
constexpr int AUDIO_IN_ERROR_INVALID_SIZE = static_cast<int>(0x80260102);
|
||||
constexpr int AUDIO_IN_ERROR_INVALID_FREQ = static_cast<int>(0x80260103);
|
||||
constexpr int AUDIO_IN_ERROR_INVALID_TYPE = static_cast<int>(0x80260104);
|
||||
constexpr int AUDIO_IN_ERROR_INVALID_POINTER = static_cast<int>(0x80260105);
|
||||
constexpr int AUDIO_IN_ERROR_INVALID_PARAM = static_cast<int>(0x80260106);
|
||||
constexpr int AUDIO_IN_ERROR_PORT_FULL = static_cast<int>(0x80260107);
|
||||
constexpr int AUDIO_IN_ERROR_BUSY = static_cast<int>(0x8026010A);
|
||||
constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 1;
|
||||
constexpr std::uint32_t MAX_QUEUED_BLOCKS = 4;
|
||||
constexpr std::uint32_t MAX_ASYNC_LEN = 384;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
@@ -175,18 +175,18 @@ int32_t APS5_VABI sceAudioInClose(int32_t handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceAudioInHqOpen(int32_t user_id, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) {
|
||||
(void)user_id;
|
||||
if (const int error = checkArguments(type, index, len, freq); error != 0) return error;
|
||||
if (len != 128) return AUDIO_IN_ERROR_INVALID_SIZE;
|
||||
int32_t APS5_VABI sceAudioInHqOpen(int32_t userId, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) {
|
||||
if (freq != 48000) return AUDIO_IN_ERROR_INVALID_FREQ;
|
||||
if (len != 128) return AUDIO_IN_ERROR_INVALID_SIZE;
|
||||
if (param != 2) return AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
if (const int error = checkArguments(type, index, len, freq); error != 0) return error;
|
||||
return openPort(len, freq, param);
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceAudioInAsyncOpen(int32_t user_id, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) {
|
||||
(void)user_id;
|
||||
if (const int error = checkArguments(type, index, len, freq); error != 0 && error != AUDIO_IN_ERROR_INVALID_SIZE) return error;
|
||||
if (len != 128 && len != 256 && len != MAX_ASYNC_LEN) return AUDIO_IN_ERROR_INVALID_SIZE;
|
||||
int32_t APS5_VABI sceAudioInAsyncOpen(int32_t userId, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) {
|
||||
if (freq != 48000) return AUDIO_IN_ERROR_INVALID_FREQ;
|
||||
if (len != 128) return AUDIO_IN_ERROR_INVALID_SIZE;
|
||||
if (const int error = checkArguments(type, index, len, freq); error != 0) return error;
|
||||
return openPort(len, freq, param);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) {
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceAudioInGetGain(int32_t handle, float* gain) {
|
||||
if (!gain) throw std::runtime_error("sceAudioInGetGain: null gain");
|
||||
if (!gain) return AUDIO_IN_ERROR_INVALID_POINTER;
|
||||
*gain = 1.0f;
|
||||
std::lock_guard lock(g_mutex);
|
||||
return find(handle) ? 0 : AUDIO_IN_ERROR_INVALID_HANDLE;
|
||||
|
||||
@@ -40,7 +40,7 @@ static constexpr std::uint64_t DRAIN_TIMEOUT_US = 200000;
|
||||
static constexpr std::uint64_t DRAIN_SLEEP_US = 1000;
|
||||
static constexpr int MONITOR_TYPE_HDMI = 1;
|
||||
static constexpr std::uint32_t HDMI_MONITOR_INFO_SIZE = 0x180;
|
||||
static constexpr std::uint32_t EX_MONITOR_INFO_MIN_SIZE = 0xA0;
|
||||
static constexpr std::uint32_t EX_MONITOR_INFO_SIZE = 0x400;
|
||||
static constexpr std::uint32_t EX_OPEN_MODE_MAX = 10;
|
||||
static constexpr std::uint32_t EX_OPEN_MODES = 0x61F;
|
||||
static constexpr std::uint32_t EX_CONFIGURE_MODE_DEFAULT = 0xFF;
|
||||
@@ -599,7 +599,7 @@ int APS5_VABI sceAudioOutGetPortState(int handle, AudioOutPortState* state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOutDeviceIdOpen() {
|
||||
int APS5_VABI sceAudioOutDeviceIdOpen(std::uint64_t deviceId, std::int32_t type, std::int32_t index, std::uint32_t length, std::int32_t frequency, std::uint32_t parameter) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
@@ -635,15 +635,10 @@ int APS5_VABI sceAudioOutSysConfigureOutput(int type, std::uint64_t flags, std::
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOutSysGetHdmiMonitorInfo(int type, void* info, std::uint32_t size) {
|
||||
if (type != MONITOR_TYPE_HDMI) {
|
||||
throw std::runtime_error("sceAudioOutSysGetHdmiMonitorInfo: monitor type " + std::to_string(type) + " not supported");
|
||||
}
|
||||
if (size != HDMI_MONITOR_INFO_SIZE) {
|
||||
throw std::runtime_error("sceAudioOutSysGetHdmiMonitorInfo: size " + std::to_string(size) + " not supported");
|
||||
}
|
||||
if (info == nullptr) {
|
||||
return -2144993276;
|
||||
}
|
||||
if (info == nullptr) return static_cast<int>(0x80260004);
|
||||
if (size != HDMI_MONITOR_INFO_SIZE) return static_cast<int>(0x80260006);
|
||||
if (type != MONITOR_TYPE_HDMI && type != 0x10) return static_cast<int>(0x8026000C);
|
||||
if (type == 0x10) throw std::runtime_error("sceAudioOutSysGetHdmiMonitorInfo: monitor type 16 not supported");
|
||||
std::memset(info, 0, HDMI_MONITOR_INFO_SIZE);
|
||||
return 0;
|
||||
}
|
||||
@@ -656,20 +651,17 @@ int APS5_VABI sceAudioOutSysOpen(int userId, std::uint32_t mode) {
|
||||
return allocatePort(PORT_TYPE_SYS, SYS_PORT_SAMPLES, SYS_PORT_FRAME_RATE, Format::S16_8Ch);
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOutSetMixLevelPadSpk(int handle, int mixLevel) {
|
||||
int APS5_VABI sceAudioOutSetMixLevelPadSpk(int handle, std::uint32_t mixLevel) {
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
Port* port = getPort(handle);
|
||||
if (port == nullptr) {
|
||||
return -2144993277;
|
||||
}
|
||||
if (port->type != PORT_TYPE_PADSPK) {
|
||||
return -2144993270;
|
||||
}
|
||||
if (mixLevel > DEFAULT_VOLUME) {
|
||||
return -2144993260;
|
||||
}
|
||||
if (mixLevel < 0) {
|
||||
throw std::runtime_error("sceAudioOutSetMixLevelPadSpk: negative mix level not supported");
|
||||
if (port->type != PORT_TYPE_PADSPK) {
|
||||
return -2144993270;
|
||||
}
|
||||
port->mixLevel = mixLevel;
|
||||
return 0;
|
||||
@@ -706,16 +698,10 @@ int APS5_VABI sceAudioOutExConfigureOutput(int control, std::uint64_t flags, std
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOutExGetMonitorInfo(int type, int index, void* info, std::uint32_t size) {
|
||||
if (type != 0 || index != 0) {
|
||||
throw std::runtime_error("sceAudioOutExGetMonitorInfo: monitor " + std::to_string(type) + ", " + std::to_string(index) + " not supported");
|
||||
}
|
||||
if (size < EX_MONITOR_INFO_MIN_SIZE) {
|
||||
throw std::runtime_error("sceAudioOutExGetMonitorInfo: size " + std::to_string(size) + " not supported");
|
||||
}
|
||||
if (info == nullptr) {
|
||||
return -2144993276;
|
||||
}
|
||||
std::memset(info, 0, size);
|
||||
if (type != 0) return static_cast<int>(0x80260003);
|
||||
if (info == nullptr) return static_cast<int>(0x80260004);
|
||||
if (size != EX_MONITOR_INFO_SIZE) return static_cast<int>(0x80260006);
|
||||
std::memset(info, 0, EX_MONITOR_INFO_SIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ int APS5_VABI sceAudioOut2Initialize(void) {
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t output) {
|
||||
(void)userId;
|
||||
if (output > MAX_SUPPORTED_3D_OUTPUT) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
if (userId != 0 && (userId != 0xFF || output == 0)) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,13 +32,13 @@ int APS5_VABI sceAudioOut2UserDestroy(AudioOut2UserHandle handle) {
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOut2UserGetSupportedAttributes(AudioOut2UserHandle handle, uint32_t* contextAttributes, uint32_t* portAttributes) {
|
||||
if (!contextAttributes || !portAttributes)
|
||||
throw std::runtime_error("sceAudioOut2UserGetSupportedAttributes: null output");
|
||||
if (handle == 0 || handle == std::numeric_limits<AudioOut2UserHandle>::max()) return static_cast<int>(0x80268010);
|
||||
if (!contextAttributes && !portAttributes) return static_cast<int>(0x80268001);
|
||||
std::lock_guard lock(g_usersLock);
|
||||
if (!g_users.contains(handle))
|
||||
throw std::runtime_error("sceAudioOut2UserGetSupportedAttributes: unknown user handle");
|
||||
*contextAttributes = 0;
|
||||
*portAttributes = (1u << 0) | (1u << 1);
|
||||
if (contextAttributes) *contextAttributes = 0;
|
||||
if (portAttributes) *portAttributes = (1u << 0) | (1u << 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,11 +53,14 @@ constexpr int errorMasteringInvalidApiParam = static_cast<int>(0x80268201);
|
||||
constexpr int errorMasteringInvalidStatesId = static_cast<int>(0x80268204);
|
||||
|
||||
void TestSet3DLatency() {
|
||||
Require(sceAudioOut2Set3DLatency(systemUser, 0) == 0, "output 0 for the system user must be accepted");
|
||||
Require(sceAudioOut2Set3DLatency(systemUser, 0) == errorInvalidParam, "output 0 for selector 255 must be refused");
|
||||
for (std::uint32_t output = 0; output <= 2; ++output) {
|
||||
Require(sceAudioOut2Set3DLatency(0, output) == 0, "selector 0 must accept each supported output");
|
||||
}
|
||||
Require(sceAudioOut2Set3DLatency(systemUser, 1) == 0, "output 1 for the system user must be accepted");
|
||||
Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "output 2 for the system user must be accepted");
|
||||
Require(sceAudioOut2Set3DLatency(systemUser, 2) == 0, "output 2 must be accepted again");
|
||||
Require(sceAudioOut2Set3DLatency(user, 1) == 0, "output 1 for another user must be accepted");
|
||||
Require(sceAudioOut2Set3DLatency(user, 1) == errorInvalidParam, "a user id is not a valid latency selector");
|
||||
Require(sceAudioOut2Set3DLatency(systemUser, 3) == errorInvalidParam, "output 3 must be refused");
|
||||
Require(sceAudioOut2Set3DLatency(systemUser, 0xFFFFFFFFu) == errorInvalidParam, "a large output must be refused");
|
||||
}
|
||||
|
||||
@@ -77,11 +77,17 @@ void TestUserAttributes() {
|
||||
}
|
||||
Require(threw && context.value == 0xFFFFFFFFu && port.value == 0xFFFFFFFFu);
|
||||
};
|
||||
rejects(first, true, false);
|
||||
rejects(first, false, true);
|
||||
rejects(first, true, true);
|
||||
rejects(0, false, false);
|
||||
rejects(std::numeric_limits<AudioOut2UserHandle>::max(), false, false);
|
||||
context.value = port.value = 0xFFFFFFFFu;
|
||||
Require(sceAudioOut2UserGetSupportedAttributes(first, nullptr, &port.value) == 0);
|
||||
Require(context.value == 0xFFFFFFFFu && port.value == 3);
|
||||
Require(sceAudioOut2UserGetSupportedAttributes(first, &context.value, nullptr) == 0 && context.value == 0);
|
||||
context.value = port.value = 0xFFFFFFFFu;
|
||||
Require(sceAudioOut2UserGetSupportedAttributes(first, nullptr, nullptr) == static_cast<int>(0x80268001));
|
||||
for (const AudioOut2UserHandle handle : {AudioOut2UserHandle{0}, std::numeric_limits<AudioOut2UserHandle>::max()}) {
|
||||
Require(sceAudioOut2UserGetSupportedAttributes(handle, &context.value, &port.value) == static_cast<int>(0x80268010));
|
||||
Require(sceAudioOut2UserGetSupportedAttributes(handle, nullptr, nullptr) == static_cast<int>(0x80268010));
|
||||
Require(context.value == 0xFFFFFFFFu && port.value == 0xFFFFFFFFu);
|
||||
}
|
||||
Require(sceAudioOut2UserDestroy(first) == 0);
|
||||
rejects(first, false, false);
|
||||
std::thread([&] {
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceAudioOutExGetMonitorInfo(int, int, void*, std::uint32_t);
|
||||
@@ -21,28 +19,16 @@ static void Require(bool value, const char* message) {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint32_t titleSize = 0x400;
|
||||
constexpr std::uint32_t minimumSize = 0xA0;
|
||||
constexpr std::size_t descriptorCountOffset = 0x98;
|
||||
constexpr std::size_t guardSize = 16;
|
||||
constexpr std::uint8_t untouched = 0xA5;
|
||||
constexpr int invalidPointer = static_cast<int>(0x80260004);
|
||||
|
||||
bool Throws(int type, int index, std::uint32_t size, const std::string& expected) {
|
||||
std::array<std::uint8_t, titleSize> info{};
|
||||
try {
|
||||
sceAudioOutExGetMonitorInfo(type, index, info.data(), size);
|
||||
} catch (const std::runtime_error& error) {
|
||||
if (std::string(error.what()) == expected) return true;
|
||||
std::fprintf(stderr, "threw: %s\n", error.what());
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheckEmpty(std::uint32_t size) {
|
||||
void CheckEmpty(int index) {
|
||||
constexpr auto size = titleSize;
|
||||
std::array<std::uint8_t, titleSize + guardSize> buffer{};
|
||||
buffer.fill(untouched);
|
||||
Require(sceAudioOutExGetMonitorInfo(0, 0, buffer.data(), size) == 0, "the monitor query must succeed");
|
||||
Require(sceAudioOutExGetMonitorInfo(0, index, buffer.data(), size) == 0, "the monitor query must succeed");
|
||||
for (std::uint32_t i = 0; i < size; i++) {
|
||||
Require(buffer[i] == 0, "the monitor info must describe a sink without audio descriptors");
|
||||
}
|
||||
@@ -57,15 +43,14 @@ void CheckEmpty(std::uint32_t size) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
CheckEmpty(titleSize);
|
||||
CheckEmpty(minimumSize);
|
||||
Require(sceAudioOutExGetMonitorInfo(0, 0, nullptr, titleSize) == invalidPointer,
|
||||
"a null buffer must report an invalid pointer");
|
||||
Require(Throws(0, 0, minimumSize - 1, "sceAudioOutExGetMonitorInfo: size 159 not supported"),
|
||||
"a buffer without room for the descriptor count must throw");
|
||||
Require(Throws(1, 0, titleSize, "sceAudioOutExGetMonitorInfo: monitor 1, 0 not supported"),
|
||||
"a nonzero first argument must throw");
|
||||
Require(Throws(0, 1, titleSize, "sceAudioOutExGetMonitorInfo: monitor 0, 1 not supported"),
|
||||
"a nonzero second argument must throw");
|
||||
for (const int index : {0, 1, -1, 100}) CheckEmpty(index);
|
||||
std::array<std::uint8_t, titleSize + guardSize> buffer;
|
||||
buffer.fill(untouched);
|
||||
for (const std::uint32_t size : {0u, 0xA0u, titleSize - 1, titleSize + 1, 0xffffffffu}) {
|
||||
Require(sceAudioOutExGetMonitorInfo(0, 0, buffer.data(), size) == static_cast<int>(0x80260006), "exact size required");
|
||||
Require(sceAudioOutExGetMonitorInfo(0, 0, nullptr, size) == invalidPointer, "pointer checked before size");
|
||||
Require(sceAudioOutExGetMonitorInfo(1, 0, nullptr, size) == static_cast<int>(0x80260003), "type checked before pointer");
|
||||
for (const auto value : buffer) Require(value == untouched, "failure preserves output");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::
|
||||
int APS5_VABI sceAudioOutClose(int);
|
||||
int APS5_VABI sceAudioOutOutput(int, const void*);
|
||||
int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*);
|
||||
int APS5_VABI sceAudioOutSetMixLevelPadSpk(int, int);
|
||||
int APS5_VABI sceAudioOutSetMixLevelPadSpk(int, std::uint32_t);
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
@@ -153,7 +153,7 @@ void TestOtherPortType() {
|
||||
Require(Play(portTypeMain, formatS16Mono, block, [](int) {}) == block);
|
||||
Require(Play(portTypeMain, formatS16Mono, block, [](int handle) {
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == invalidPortType);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidPortType);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidMixLevel);
|
||||
}) == block);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ void TestRejectedLevels() {
|
||||
closed = handle;
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == 0);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidMixLevel);
|
||||
Require(ThrowsRuntimeError([handle] { sceAudioOutSetMixLevelPadSpk(handle, -1); }));
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, UINT32_MAX) == invalidMixLevel);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(0, unity) == invalidPort);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle + 1, unity) == invalidPort);
|
||||
}) == Scaled(block, unity / 2));
|
||||
|
||||
@@ -59,13 +59,14 @@ int main() {
|
||||
Require(sceAudioOutSysGetHdmiMonitorInfo(monitorTypeHdmi, nullptr, infoSize) == invalidPointer,
|
||||
"a null buffer must report an invalid pointer");
|
||||
|
||||
Require(Throws(monitorTypeHdmi, infoSize - 1,
|
||||
"sceAudioOutSysGetHdmiMonitorInfo: size 383 not supported"), "a short buffer must throw");
|
||||
Require(Throws(monitorTypeHdmi, infoSize + 1,
|
||||
"sceAudioOutSysGetHdmiMonitorInfo: size 385 not supported"), "a long buffer must throw");
|
||||
buffer.fill(untouched);
|
||||
for (const std::uint32_t size : {0u, infoSize - 1, infoSize + 1, 0xffffffffu}) {
|
||||
Require(sceAudioOutSysGetHdmiMonitorInfo(monitorTypeHdmi, buffer.data(), size) == static_cast<int>(0x80260006), "exact size required");
|
||||
Require(sceAudioOutSysGetHdmiMonitorInfo(0, nullptr, size) == invalidPointer, "pointer checked before size and type");
|
||||
for (const auto value : buffer) Require(value == untouched, "failure preserves output");
|
||||
}
|
||||
for (const int type : {0, 2, -1, 17}) Require(sceAudioOutSysGetHdmiMonitorInfo(type, buffer.data(), infoSize) == static_cast<int>(0x8026000C), "unsupported type");
|
||||
Require(Throws(0x10, infoSize,
|
||||
"sceAudioOutSysGetHdmiMonitorInfo: monitor type 16 not supported"), "monitor type 0x10 must throw");
|
||||
Require(Throws(0, infoSize,
|
||||
"sceAudioOutSysGetHdmiMonitorInfo: monitor type 0 not supported"), "monitor type 0 must throw");
|
||||
"sceAudioOutSysGetHdmiMonitorInfo: monitor type 16 not supported"), "unimplemented device query");
|
||||
return 0;
|
||||
}
|
||||
@@ -9,6 +9,10 @@
|
||||
|
||||
namespace AudioPropagation {
|
||||
|
||||
inline constexpr std::int32_t ErrorInvalidArgument = static_cast<std::int32_t>(0x8A700001);
|
||||
inline constexpr std::int32_t ErrorInvalidHandle = static_cast<std::int32_t>(0x8A700002);
|
||||
inline constexpr std::int32_t ErrorNullPointer = static_cast<std::int32_t>(0x8A700003);
|
||||
|
||||
inline constexpr std::uint32_t MaterialId = 0x010107d1;
|
||||
inline constexpr std::size_t MaterialSize = 0x40;
|
||||
inline constexpr std::uint32_t SystemMemoryId = 0x010107d4;
|
||||
@@ -23,6 +27,107 @@ inline constexpr std::size_t PortalParamsSize = 0x60;
|
||||
inline constexpr std::uint32_t PathEntryId = 0x010107d9;
|
||||
inline constexpr std::size_t PathEntrySize = 0x28;
|
||||
|
||||
inline constexpr std::uint32_t SystemOptionsId = 0x010107d5;
|
||||
inline constexpr std::size_t SystemOptionsSize = 56;
|
||||
|
||||
struct SystemOptions {
|
||||
AudioPropagationStructDescriptor desc;
|
||||
std::uint32_t maxSources;
|
||||
std::uint32_t maxMaterials;
|
||||
std::uint32_t parameter24;
|
||||
std::uint32_t reflectionOrder;
|
||||
std::uint32_t frameSamples;
|
||||
float propagationSpeed;
|
||||
float maxPathLength;
|
||||
std::uint32_t flags;
|
||||
std::uint8_t opaque[8];
|
||||
};
|
||||
|
||||
static_assert(sizeof(SystemOptions) == SystemOptionsSize);
|
||||
static_assert(offsetof(SystemOptions, frameSamples) == 32);
|
||||
static_assert(offsetof(SystemOptions, flags) == 44);
|
||||
|
||||
struct SourceInfo {
|
||||
AudioPropagationHandle system;
|
||||
float position[3];
|
||||
std::uint32_t reserved20;
|
||||
float direction[3];
|
||||
std::uint32_t reserved36;
|
||||
std::uint32_t attribute2Mode;
|
||||
float attribute2Values[3];
|
||||
float attribute3Values[4];
|
||||
std::uint32_t pathCapacity;
|
||||
std::uint32_t parameter76;
|
||||
};
|
||||
|
||||
struct SystemInfo {
|
||||
void* cpuMemory;
|
||||
std::size_t cpuMemorySize;
|
||||
std::uint32_t index;
|
||||
std::uint32_t frameSamples;
|
||||
std::uint32_t parameter24;
|
||||
std::uint32_t maxSources;
|
||||
std::uint32_t maxMaterials;
|
||||
std::uint32_t pathCapacityPerSource;
|
||||
float propagationSpeed;
|
||||
std::uint32_t reserved44;
|
||||
};
|
||||
|
||||
static_assert(sizeof(SourceInfo) == 80);
|
||||
static_assert(offsetof(SourceInfo, position) == 8);
|
||||
static_assert(offsetof(SourceInfo, direction) == 24);
|
||||
static_assert(offsetof(SourceInfo, attribute2Mode) == 40);
|
||||
static_assert(offsetof(SourceInfo, attribute2Values) == 44);
|
||||
static_assert(offsetof(SourceInfo, attribute3Values) == 56);
|
||||
static_assert(offsetof(SourceInfo, pathCapacity) == 72);
|
||||
static_assert(offsetof(SourceInfo, parameter76) == 76);
|
||||
static_assert(sizeof(SystemInfo) == 48);
|
||||
static_assert(offsetof(SystemInfo, cpuMemorySize) == 8);
|
||||
static_assert(offsetof(SystemInfo, index) == 16);
|
||||
static_assert(offsetof(SystemInfo, frameSamples) == 20);
|
||||
static_assert(offsetof(SystemInfo, parameter24) == 24);
|
||||
static_assert(offsetof(SystemInfo, maxSources) == 28);
|
||||
static_assert(offsetof(SystemInfo, maxMaterials) == 32);
|
||||
static_assert(offsetof(SystemInfo, pathCapacityPerSource) == 36);
|
||||
static_assert(offsetof(SystemInfo, propagationSpeed) == 40);
|
||||
struct ReportRegistration;
|
||||
|
||||
struct ReportArguments {
|
||||
std::uint32_t generalOffset;
|
||||
std::uint32_t floatingOffset;
|
||||
void* overflowArguments;
|
||||
void* savedRegisters;
|
||||
};
|
||||
|
||||
struct ReportInfo {
|
||||
std::uint64_t size;
|
||||
AudioPropagationHandle handle;
|
||||
std::uint32_t category;
|
||||
std::int32_t result;
|
||||
std::int32_t operation;
|
||||
std::uint32_t reserved;
|
||||
const char* function;
|
||||
const char* format;
|
||||
ReportArguments arguments;
|
||||
};
|
||||
|
||||
using ReportHandler = void (APS5_VABI*)(const ReportInfo* report, void* userData);
|
||||
|
||||
static_assert(sizeof(ReportArguments) == 24);
|
||||
static_assert(offsetof(ReportArguments, generalOffset) == 0);
|
||||
static_assert(offsetof(ReportArguments, floatingOffset) == 4);
|
||||
static_assert(offsetof(ReportArguments, overflowArguments) == 8);
|
||||
static_assert(offsetof(ReportArguments, savedRegisters) == 16);
|
||||
static_assert(sizeof(ReportInfo) == 72);
|
||||
static_assert(offsetof(ReportInfo, size) == 0);
|
||||
static_assert(offsetof(ReportInfo, handle) == 8);
|
||||
static_assert(offsetof(ReportInfo, category) == 16);
|
||||
static_assert(offsetof(ReportInfo, result) == 20);
|
||||
static_assert(offsetof(ReportInfo, operation) == 24);
|
||||
static_assert(offsetof(ReportInfo, function) == 32);
|
||||
static_assert(offsetof(ReportInfo, format) == 40);
|
||||
static_assert(offsetof(ReportInfo, arguments) == 48);
|
||||
|
||||
struct Attribute {
|
||||
std::uint32_t id;
|
||||
std::uint32_t reserved0;
|
||||
@@ -32,13 +137,38 @@ struct Attribute {
|
||||
std::uint32_t reserved2;
|
||||
};
|
||||
|
||||
struct PathPoint {
|
||||
AudioPropagationStructDescriptor desc;
|
||||
AudioPropagationHandle room;
|
||||
float gain;
|
||||
float position[3];
|
||||
std::byte opaque[8];
|
||||
};
|
||||
|
||||
struct AudioPath {
|
||||
AudioPropagationStructDescriptor desc;
|
||||
PathPoint points[4];
|
||||
std::uint32_t pointCount;
|
||||
std::uint32_t flags;
|
||||
std::byte opaque[8];
|
||||
};
|
||||
|
||||
static_assert(sizeof(PathPoint) == 48);
|
||||
static_assert(offsetof(PathPoint, room) == 16);
|
||||
static_assert(offsetof(PathPoint, gain) == 24);
|
||||
static_assert(offsetof(PathPoint, position) == 28);
|
||||
static_assert(sizeof(AudioPath) == 224);
|
||||
static_assert(offsetof(AudioPath, points) == 16);
|
||||
static_assert(offsetof(AudioPath, pointCount) == 208);
|
||||
static_assert(offsetof(AudioPath, flags) == 212);
|
||||
|
||||
struct RenderInfo {
|
||||
AudioPropagationStructDescriptor desc;
|
||||
AudioPropagationHandle source;
|
||||
void* output;
|
||||
float* output;
|
||||
std::size_t outputSize;
|
||||
std::uint32_t format;
|
||||
std::uint32_t reserved;
|
||||
std::uint32_t channelCount;
|
||||
};
|
||||
|
||||
struct PortalParams {
|
||||
@@ -53,6 +183,7 @@ static_assert(sizeof(Attribute) == 0x20);
|
||||
static_assert(sizeof(RenderInfo) == RenderInfoSize);
|
||||
static_assert(offsetof(RenderInfo, source) == 0x10);
|
||||
static_assert(offsetof(RenderInfo, format) == 0x28);
|
||||
static_assert(offsetof(RenderInfo, channelCount) == 0x2c);
|
||||
static_assert(sizeof(PortalParams) == PortalParamsSize);
|
||||
static_assert(offsetof(PortalParams, rooms) == 0x50);
|
||||
|
||||
@@ -60,8 +191,8 @@ static_assert(offsetof(PortalParams, rooms) == 0x50);
|
||||
|
||||
extern "C" {
|
||||
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const void* options, AudioPropagationSystemMemory* outMemory);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemCreate(const void* options, const AudioPropagationSystemMemory* memory, AudioPropagationHandle* outSystem);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const AudioPropagation::SystemOptions* options, AudioPropagationSystemMemory* outMemory);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemCreate(const AudioPropagation::SystemOptions* options, const AudioPropagationSystemMemory* memory, AudioPropagationHandle* outSystem);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemDestroy(AudioPropagationHandle system);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemSetAttributes(AudioPropagationHandle system, const AudioPropagation::Attribute* attributes, std::uint32_t count);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemRegisterMaterial(AudioPropagationHandle system, const AudioPropagationStructDescriptor* material, AudioPropagationHandle* outMaterial);
|
||||
@@ -79,10 +210,10 @@ std::int32_t APS5_VABI sceAudioPropagationSourceSetAttributes(AudioPropagationHa
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceGetAudioPathCount(AudioPropagationHandle source, std::uint32_t* count);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceGetAudioPath(AudioPropagationHandle source, std::uint32_t index, AudioPropagationHandle* outPath);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceGetRays(AudioPropagationHandle source, void* rays, std::uint32_t* count);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceCalculateAudioPaths(AudioPropagationHandle source, const void* rays, std::uint32_t rayCount, std::uint32_t flags, void* paths, std::uint32_t pathCount);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceCalculateAudioPaths(AudioPropagationHandle source, const void* rays, std::uint32_t rayCount, std::uint32_t reflectionOrder, void* paths, std::uint32_t pathCount);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPaths(AudioPropagationHandle source, const void* entries, std::uint32_t count);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHandle path, const void* data, float gain);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle system, const AudioPropagation::RenderInfo* infos, std::uint32_t count);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHandle path, const AudioPropagation::AudioPath* data, float gain);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle system, AudioPropagation::RenderInfo* infos, std::uint32_t count);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
@@ -22,7 +23,8 @@ constexpr std::uint64_t SystemMagic = [] {
|
||||
|
||||
struct SystemState {
|
||||
std::uint64_t magic;
|
||||
std::uint64_t reserved;
|
||||
std::uint32_t frameSamples;
|
||||
std::uint32_t maxSources;
|
||||
};
|
||||
|
||||
enum class Kind { Room, Portal, Source, Material };
|
||||
@@ -115,23 +117,49 @@ void requireNoPropagation(bool none, const char* function) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const void* options, AudioPropagationSystemMemory* outMemory) {
|
||||
require(options != nullptr, __func__);
|
||||
requireDescriptor(outMemory != nullptr ? &outMemory->desc : nullptr, SystemMemoryId, SystemMemorySize, __func__);
|
||||
outMemory->size_cpu_mem = sizeof(SystemState);
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemQueryMemory(const SystemOptions* options, AudioPropagationSystemMemory* outMemory) {
|
||||
if (!options || !outMemory) return ErrorNullPointer;
|
||||
if (outMemory->desc.id != SystemMemoryId || outMemory->desc.size != SystemMemorySize) return static_cast<std::int32_t>(0x8A700007);
|
||||
outMemory->p_cpu_mem = nullptr;
|
||||
outMemory->size_cpu_mem = 0;
|
||||
outMemory->p_gpu_mem = nullptr;
|
||||
outMemory->size_gpu_mem = 0;
|
||||
if (options->desc.id != SystemOptionsId || options->desc.size != SystemOptionsSize) return static_cast<std::int32_t>(0x8A700007);
|
||||
if (options->maxSources == 0 || options->maxSources > 64 || options->maxMaterials == 0 || options->maxMaterials > 64 || options->parameter24 != 6 || options->reflectionOrder == 0 || options->reflectionOrder > 2) return ErrorInvalidArgument;
|
||||
const auto frameSamples = options->frameSamples;
|
||||
if (frameSamples != 256 && frameSamples != 512 && frameSamples != 1024 && frameSamples != 2048 && frameSamples != 4096) return ErrorInvalidArgument;
|
||||
if (!(options->propagationSpeed > 0x1p-23f) || options->propagationSpeed > 1372.0f || !(options->maxPathLength > 0x1p-23f) || options->maxPathLength > 1372.0f || options->flags > 31 || ((options->flags & 1) && frameSamples != 512)) return ErrorInvalidArgument;
|
||||
if (options->flags & 1) throw std::runtime_error(std::string(__func__) + ": GPU memory requirements depend on an unsupported native runtime query");
|
||||
const std::uint64_t sources = options->maxSources;
|
||||
const std::uint64_t paths = sources * (options->reflectionOrder == 1 ? 6 : 36);
|
||||
const float delaySeconds = options->maxPathLength / options->propagationSpeed;
|
||||
const float delaySamples = delaySeconds * 48000.0f;
|
||||
const auto delayCapacity = static_cast<std::uint64_t>(std::ceil(delaySamples));
|
||||
std::uint64_t bytes = sources * 1024 + paths * (32 * (options->reflectionOrder + 2) + 8928);
|
||||
bytes += frameSamples == 256 ? 2048 : 4 * frameSamples;
|
||||
bytes += 4 * delayCapacity * sources;
|
||||
bytes += bytes & 15;
|
||||
bytes += 160 * paths + 12352 + 4208 * options->maxMaterials;
|
||||
if (frameSamples == 256) bytes += sources * ((options->flags & 2) ? 36896 : 16416);
|
||||
if (options->flags & 4) bytes += 2000656;
|
||||
if (options->flags & 8) bytes += 288 * paths;
|
||||
if (options->flags & 16) bytes += 144 * paths;
|
||||
outMemory->size_cpu_mem = bytes + 5368 - ((bytes & 15) ^ 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemCreate(const void* options, const AudioPropagationSystemMemory* memory, AudioPropagationHandle* outSystem) {
|
||||
std::int32_t APS5_VABI sceAudioPropagationSystemCreate(const SystemOptions* options, const AudioPropagationSystemMemory* memory, AudioPropagationHandle* outSystem) {
|
||||
require(options != nullptr && outSystem != nullptr, __func__);
|
||||
requireDescriptor(memory != nullptr ? &memory->desc : nullptr, SystemMemoryId, SystemMemorySize, __func__);
|
||||
auto requiredMemory = *memory;
|
||||
const auto result = sceAudioPropagationSystemQueryMemory(options, &requiredMemory);
|
||||
if (result != 0) return result;
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(memory->p_cpu_mem);
|
||||
require(address != 0 && address % alignof(SystemState) == 0 && memory->size_cpu_mem >= sizeof(SystemState) && memory->size_gpu_mem == 0, __func__);
|
||||
require(address != 0 && address % alignof(SystemState) == 0 && memory->size_cpu_mem >= requiredMemory.size_cpu_mem && memory->size_gpu_mem == 0, __func__);
|
||||
auto& state = registry();
|
||||
std::lock_guard lock(state.mutex);
|
||||
require(!state.systems.contains(address), __func__);
|
||||
*static_cast<SystemState*>(memory->p_cpu_mem) = SystemState{SystemMagic, 0};
|
||||
*static_cast<SystemState*>(memory->p_cpu_mem) = SystemState{SystemMagic, options->frameSamples, options->maxSources};
|
||||
state.systems.insert(address);
|
||||
*outSystem = address;
|
||||
return 0;
|
||||
@@ -258,9 +286,11 @@ std::int32_t APS5_VABI sceAudioPropagationSourceGetAudioPathCount(AudioPropagati
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceGetAudioPath(AudioPropagationHandle source, std::uint32_t index, AudioPropagationHandle* outPath) {
|
||||
auto& state = registry();
|
||||
std::lock_guard lock(state.mutex);
|
||||
requireObject(state, source, Kind::Source, __func__);
|
||||
static_cast<void>(outPath);
|
||||
throw std::invalid_argument(std::string(__func__) + ": index " + std::to_string(index) + " out of range (0 audio paths)");
|
||||
const auto found = state.objects.find(source);
|
||||
if (found == state.objects.end() || found->second.kind != Kind::Source || !liveSystem(state, found->second.system)) return ErrorInvalidHandle;
|
||||
if (!outPath) return ErrorNullPointer;
|
||||
static_cast<void>(index);
|
||||
return ErrorInvalidArgument;
|
||||
}
|
||||
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceGetRays(AudioPropagationHandle source, void* rays, std::uint32_t* count) {
|
||||
@@ -272,15 +302,14 @@ std::int32_t APS5_VABI sceAudioPropagationSourceGetRays(AudioPropagationHandle s
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceCalculateAudioPaths(AudioPropagationHandle source, const void* rays, std::uint32_t rayCount, std::uint32_t flags, void* paths, std::uint32_t pathCount) {
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceCalculateAudioPaths(AudioPropagationHandle source, const void* rays, std::uint32_t rayCount, std::uint32_t reflectionOrder, void* paths, std::uint32_t pathCount) {
|
||||
auto& state = registry();
|
||||
std::lock_guard lock(state.mutex);
|
||||
requireObject(state, source, Kind::Source, __func__);
|
||||
static_cast<void>(rays);
|
||||
static_cast<void>(flags);
|
||||
static_cast<void>(paths);
|
||||
requireNoPropagation(rayCount == 0 && pathCount == 0, __func__);
|
||||
return 0;
|
||||
const auto found = state.objects.find(source);
|
||||
if (found == state.objects.end() || found->second.kind != Kind::Source || !liveSystem(state, found->second.system)) return ErrorInvalidHandle;
|
||||
if (!rays || !paths) return ErrorNullPointer;
|
||||
if (rayCount != 6 || reflectionOrder == 0 || pathCount == 0) return ErrorInvalidArgument;
|
||||
throw std::runtime_error(__func__);
|
||||
}
|
||||
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPaths(AudioPropagationHandle source, const void* entries, std::uint32_t count) {
|
||||
@@ -292,7 +321,7 @@ std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPaths(AudioPropagationHa
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHandle path, const void* data, float gain) {
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHandle path, const AudioPath* data, float gain) {
|
||||
static_cast<void>(path);
|
||||
static_cast<void>(data);
|
||||
static_cast<void>(gain);
|
||||
@@ -300,47 +329,53 @@ std::int32_t APS5_VABI sceAudioPropagationSourceSetAudioPath(AudioPropagationHan
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle system, const RenderInfo* infos, std::uint32_t count) {
|
||||
std::int32_t APS5_VABI sceAudioPropagationSourceRender(AudioPropagationHandle system, RenderInfo* infos, std::uint32_t count) {
|
||||
require(infos != nullptr && count != 0, __func__);
|
||||
auto& state = registry();
|
||||
std::lock_guard lock(state.mutex);
|
||||
requireSystem(state, system, __func__);
|
||||
const auto& configuration = *reinterpret_cast<const SystemState*>(system);
|
||||
require(count <= configuration.maxSources, __func__);
|
||||
const auto requiredBytes = static_cast<std::size_t>(configuration.frameSamples) * 16 * sizeof(float);
|
||||
for (std::uint32_t index = 0; index < count; ++index) {
|
||||
const auto& info = infos[index];
|
||||
requireDescriptor(&info.desc, RenderInfoId, RenderInfoSize, __func__);
|
||||
require(requireObject(state, info.source, Kind::Source, __func__).system == system, __func__);
|
||||
require(info.output != nullptr && info.outputSize != 0, __func__);
|
||||
require(info.output != nullptr && info.outputSize >= requiredBytes, __func__);
|
||||
if (info.format != RenderFormat) throw std::runtime_error(std::string(__func__) + ": render format " + std::to_string(info.format) + " is unknown");
|
||||
}
|
||||
for (std::uint32_t index = 0; index < count; ++index) {
|
||||
infos[index].channelCount = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationPathGetNumPoints() {
|
||||
int APS5_VABI sceAudioPropagationPathGetNumPoints(AudioPropagationHandle path, std::uint32_t* count) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationReportApi() {
|
||||
int APS5_VABI sceAudioPropagationReportApi(std::uint32_t category, AudioPropagationHandle handle, std::int32_t result, std::int32_t operation, const char* function, const char* format, ...) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationResetAttributes() {
|
||||
int APS5_VABI sceAudioPropagationResetAttributes(Attribute* attributes, std::uint32_t count) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationSourceQueryInfo() {
|
||||
int APS5_VABI sceAudioPropagationSourceQueryInfo(AudioPropagationHandle source, SourceInfo* info) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationSystemLock() {
|
||||
int APS5_VABI sceAudioPropagationSystemLock(AudioPropagationHandle system) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationSystemQueryInfo() {
|
||||
int APS5_VABI sceAudioPropagationSystemQueryInfo(AudioPropagationHandle system, SystemInfo* info, std::size_t size) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
@@ -395,22 +430,22 @@ int APS5_VABI sceAudioPropagationSystemOptionInit() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationReportRegisterHandler() {
|
||||
int APS5_VABI sceAudioPropagationReportRegisterHandler(std::uint32_t category, ReportHandler handler, void* userData, ReportRegistration** registration) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationReportUnregisterHandler() {
|
||||
int APS5_VABI sceAudioPropagationReportUnregisterHandler(ReportRegistration* registration) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationSystemEnumHandles() {
|
||||
int APS5_VABI sceAudioPropagationSystemEnumHandles(AudioPropagationHandle* handles, std::uint32_t capacity) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioPropagationSystemUnlock() {
|
||||
int APS5_VABI sceAudioPropagationSystemUnlock(AudioPropagationHandle system) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
|
||||
extern "C" {
|
||||
|
||||
APS5_DUMMY_FUN
|
||||
int APS5_VABI sceAudioSystemSystemResume() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioSystemSystemSuspend() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include "SceTypes.hpp"
|
||||
@@ -88,7 +90,7 @@ std::int32_t validateCtrl(const AudiodecCtrl* ctrl, std::uint32_t codecType) {
|
||||
if (infoSize != sizeof(AudiodecMp3Info)) return ERROR_INVALID_BSI_INFO_SIZE;
|
||||
break;
|
||||
default:
|
||||
if (paramSize < sizeof(AudiodecParamM4aac)) return ERROR_INVALID_PARAM_SIZE;
|
||||
if (paramSize != sizeof(AudiodecParamM4aac) && paramSize != 28 && paramSize != 32) return ERROR_INVALID_PARAM_SIZE;
|
||||
if (infoSize != sizeof(AudiodecM4aacInfo)) return ERROR_INVALID_BSI_INFO_SIZE;
|
||||
break;
|
||||
}
|
||||
@@ -133,12 +135,20 @@ void fillMp3Info(AudiodecMp3Info* info, const Audiodec::Mp3Header& header) {
|
||||
}
|
||||
|
||||
std::int32_t createAac(const AudiodecParamM4aac* param, AudiodecM4aacInfo* info, Instance& instance) {
|
||||
std::uint32_t extensionMode = 0;
|
||||
std::uint32_t outputMode = 0;
|
||||
const auto* bytes = reinterpret_cast<const std::byte*>(param);
|
||||
if (param->ui_size >= 28) std::memcpy(&extensionMode, bytes + 24, sizeof(extensionMode));
|
||||
if (extensionMode > 1) return static_cast<std::int32_t>(0x807F0305);
|
||||
if (param->ui_size == 32) std::memcpy(&outputMode, bytes + 28, sizeof(outputMode));
|
||||
if (outputMode > 2) return static_cast<std::int32_t>(0x807F0306);
|
||||
if (extensionMode != 0 || outputMode != 0) throw std::runtime_error("M4AAC extended decoding modes are not implemented");
|
||||
if (param->ui_config_number != M4AAC_CONFIG_ADTS && param->ui_config_number != M4AAC_CONFIG_RAW) return ERROR_M4AAC_INVALID_CONFIG_NUMBER;
|
||||
const std::uint32_t sampleRate = Audiodec::AacSampleRate(param->ui_sampling_freq_index);
|
||||
if (param->ui_config_number == M4AAC_CONFIG_RAW && sampleRate == 0) return ERROR_M4AAC_INVALID_SAMPLING_FREQ;
|
||||
if (param->ui_max_channels > 8) return ERROR_M4AAC_INVALID_MAX_CHANNELS;
|
||||
if (param->ui_enable_heaac > 1) return ERROR_M4AAC_INVALID_ENABLE_HEAAC;
|
||||
const std::uint32_t channels = param->ui_max_channels == 0 ? 2 : param->ui_max_channels;
|
||||
if (param->ui_max_channels != 1 && param->ui_max_channels != 2 && param->ui_max_channels != 6 && param->ui_max_channels != 8) return ERROR_M4AAC_INVALID_MAX_CHANNELS;
|
||||
if (param->ui_enable_heaac > 1 || (param->ui_enable_heaac == 1 && param->ui_max_channels > 2)) return ERROR_M4AAC_INVALID_ENABLE_HEAAC;
|
||||
const std::uint32_t channels = param->ui_max_channels;
|
||||
instance.decoder = Audiodec::CreateAac(param->i_bw_pcm, param->ui_config_number == M4AAC_CONFIG_ADTS, param->ui_sampling_freq_index, channels, param->ui_enable_heaac == 1);
|
||||
instance.channels = channels;
|
||||
instance.sampleRate = sampleRate;
|
||||
|
||||
@@ -3,17 +3,37 @@
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
struct AutoMounterUsbDeviceInfo {
|
||||
char deviceName[128];
|
||||
std::byte metadata[22];
|
||||
std::byte data0[256];
|
||||
std::byte padding[2];
|
||||
std::uint64_t data0Size;
|
||||
std::byte data1[256];
|
||||
std::uint64_t data1Size;
|
||||
std::byte data2[256];
|
||||
std::uint64_t data2Size;
|
||||
};
|
||||
|
||||
static_assert(sizeof(AutoMounterUsbDeviceInfo) == 944);
|
||||
static_assert(offsetof(AutoMounterUsbDeviceInfo, data0) == 150);
|
||||
static_assert(offsetof(AutoMounterUsbDeviceInfo, data0Size) == 408);
|
||||
static_assert(offsetof(AutoMounterUsbDeviceInfo, data1) == 416);
|
||||
static_assert(offsetof(AutoMounterUsbDeviceInfo, data1Size) == 672);
|
||||
static_assert(offsetof(AutoMounterUsbDeviceInfo, data2) == 680);
|
||||
static_assert(offsetof(AutoMounterUsbDeviceInfo, data2Size) == 936);
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo() {
|
||||
int APS5_VABI sceAutoMounterClientGetUsbDeviceInfo(const char* deviceName, AutoMounterUsbDeviceInfo* info) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAutoMounterClientGetUsbDeviceList() {
|
||||
int APS5_VABI sceAutoMounterClientGetUsbDeviceList(char (*deviceNames)[64], std::int32_t* count) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
int APS5_VABI sceAutoMounterClientInit() {
|
||||
int APS5_VABI sceAutoMounterClientInit(PthreadAttr* threadAttr) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -175,10 +175,9 @@ int APS5_VABI sceAvPlayerStart(AvPlayerInternal* h) {
|
||||
return ToPlayer(h)->Start();
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvPlayerStartEx(AvPlayerInternal* h, const void* start_info_ex) {
|
||||
(void)start_info_ex;
|
||||
if (!h) return SCE_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
return ToPlayer(h)->Start();
|
||||
int APS5_VABI sceAvPlayerStartEx(AvPlayerInternal* handle, const StartInfo* startInfo) {
|
||||
if (handle == nullptr || startInfo == nullptr || startInfo->size != sizeof(*startInfo)) return SCE_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
return ToPlayer(handle)->Start(startInfo->startTime);
|
||||
}
|
||||
|
||||
int APS5_VABI sceAvPlayerStop(AvPlayerInternal* h) {
|
||||
|
||||
@@ -51,6 +51,13 @@ public:
|
||||
virtual void OnError() = 0;
|
||||
};
|
||||
|
||||
struct StartInfo {
|
||||
std::uint64_t size;
|
||||
std::uint64_t startTime;
|
||||
};
|
||||
|
||||
static_assert(sizeof(StartInfo) == 16);
|
||||
|
||||
struct SourceSettings {
|
||||
AvPlayerMemAllocator memory;
|
||||
AvPlayerFileReplacement file;
|
||||
@@ -66,7 +73,7 @@ public:
|
||||
virtual bool EnableStream(std::uint32_t index) = 0;
|
||||
virtual bool DisableStream(std::uint32_t index) = 0;
|
||||
virtual bool ChangeStream(std::uint32_t from, std::uint32_t to) = 0;
|
||||
virtual int Start() = 0;
|
||||
virtual int Start(std::uint64_t startTime) = 0;
|
||||
virtual void Stop() = 0;
|
||||
virtual void Pause() = 0;
|
||||
virtual void Resume() = 0;
|
||||
@@ -103,7 +110,7 @@ public:
|
||||
int EnableStream(std::uint32_t index);
|
||||
int DisableStream(std::uint32_t index);
|
||||
int ChangeStream(std::uint32_t from, std::uint32_t to);
|
||||
int Start();
|
||||
int Start(std::uint64_t startTime = 0);
|
||||
int Stop();
|
||||
int Pause();
|
||||
int Resume();
|
||||
@@ -136,7 +143,7 @@ private:
|
||||
void autoStart();
|
||||
void checkEndOfFile();
|
||||
bool readyLocked() const;
|
||||
int startLocked();
|
||||
int startLocked(std::uint64_t startTime = 0);
|
||||
int stopLocked();
|
||||
|
||||
AvPlayerFileReplacement file{};
|
||||
|
||||
Loaded 100 of 638 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user