test(agc): add texture resource and detiling coverage
This commit is contained in:
parent
fa09114296
commit
b521e1f811
|
|
@ -184,6 +184,11 @@ add_executable(agc_driver_graphics_tests EXCLUDE_FROM_ALL
|
|||
tests/Graphics.cpp
|
||||
tests/BdaResources.cpp
|
||||
tests/GuestAllocations.cpp
|
||||
tests/TextureFormat.cpp
|
||||
tests/TextureTiling.cpp
|
||||
tests/GuestTextureResource.cpp
|
||||
tests/GuestSamplerResource.cpp
|
||||
tests/TextureDetiler.cpp
|
||||
Graphics/src/State.cpp
|
||||
Graphics/src/Resources.cpp
|
||||
Graphics/src/ShaderResources.cpp
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "BdaTests.hpp"
|
||||
#include "GraphicsTests.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
|
|
@ -269,6 +271,12 @@ struct MockVulkan {
|
|||
std::uint32_t boundSets = 0;
|
||||
std::uint32_t boundFirst = 0;
|
||||
VkPipelineBindPoint boundPoint = VK_PIPELINE_BIND_POINT_MAX_ENUM;
|
||||
std::map<VkPipelineLayout, VkDeviceSize> pipelineLayoutPushConstantSize;
|
||||
std::uint32_t pipelineCreateCount = 0;
|
||||
std::vector<std::array<std::uint32_t, 3>> pipelineSpecializations;
|
||||
VkPipeline boundPipeline = VK_NULL_HANDLE;
|
||||
std::vector<std::byte> lastPushConstants;
|
||||
struct { std::uint32_t x = 0, y = 0, z = 0; } lastDispatchGroups;
|
||||
};
|
||||
|
||||
MockVulkan mock;
|
||||
|
|
@ -377,6 +385,57 @@ VKAPI_ATTR VkDeviceAddress VKAPI_CALL mockGetBufferDeviceAddress(VkDevice, const
|
|||
return 0x100000000000ULL + reinterpret_cast<std::uintptr_t>(info->buffer) * 0x10000;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL mockCreatePipelineLayout(VkDevice, const VkPipelineLayoutCreateInfo* info, const VkAllocationCallbacks*, VkPipelineLayout* layout) {
|
||||
*layout = makeHandle<VkPipelineLayout>();
|
||||
mock.pipelineLayoutPushConstantSize[*layout] = info->pushConstantRangeCount > 0 ? info->pPushConstantRanges[0].size : 0;
|
||||
++mock.live;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL mockDestroyPipelineLayout(VkDevice, VkPipelineLayout, const VkAllocationCallbacks*) {
|
||||
--mock.live;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL mockCreateShaderModule(VkDevice, const VkShaderModuleCreateInfo*, const VkAllocationCallbacks*, VkShaderModule* module) {
|
||||
*module = makeHandle<VkShaderModule>();
|
||||
++mock.live;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL mockDestroyShaderModule(VkDevice, VkShaderModule, const VkAllocationCallbacks*) {
|
||||
--mock.live;
|
||||
}
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL mockCreateComputePipelines(VkDevice, VkPipelineCache, std::uint32_t count, const VkComputePipelineCreateInfo* infos, const VkAllocationCallbacks*, VkPipeline* pipelines) {
|
||||
Require(count == 1, "mock expects exactly one compute pipeline per call");
|
||||
Require(infos[0].stage.pSpecializationInfo != nullptr, "compute pipeline must provide specialization data");
|
||||
std::array<std::uint32_t, 3> values{};
|
||||
Require(infos[0].stage.pSpecializationInfo->dataSize == sizeof(values), "compute pipeline specialization data has an unexpected size");
|
||||
std::memcpy(values.data(), infos[0].stage.pSpecializationInfo->pData, sizeof(values));
|
||||
*pipelines = makeHandle<VkPipeline>();
|
||||
mock.pipelineSpecializations.push_back(values);
|
||||
++mock.pipelineCreateCount;
|
||||
++mock.live;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL mockDestroyPipeline(VkDevice, VkPipeline, const VkAllocationCallbacks*) {
|
||||
--mock.live;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL mockCmdBindPipeline(VkCommandBuffer, VkPipelineBindPoint, VkPipeline pipeline) {
|
||||
mock.boundPipeline = pipeline;
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL mockCmdPushConstants(VkCommandBuffer, VkPipelineLayout, VkShaderStageFlags, std::uint32_t, std::uint32_t size, const void* values) {
|
||||
const auto* bytes = static_cast<const std::byte*>(values);
|
||||
mock.lastPushConstants.assign(bytes, bytes + size);
|
||||
}
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL mockCmdDispatch(VkCommandBuffer, std::uint32_t x, std::uint32_t y, std::uint32_t z) {
|
||||
mock.lastDispatchGroups = {x, y, z};
|
||||
}
|
||||
|
||||
PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) {
|
||||
static const std::map<std::string_view, PFN_vkVoidFunction> table{
|
||||
{"vkGetBufferDeviceAddressKHR", reinterpret_cast<PFN_vkVoidFunction>(mockGetBufferDeviceAddress)},
|
||||
|
|
@ -394,7 +453,16 @@ PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) {
|
|||
{"vkDestroyDescriptorPool", reinterpret_cast<PFN_vkVoidFunction>(mockDestroyDescriptorPool)},
|
||||
{"vkAllocateDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(mockAllocateDescriptorSets)},
|
||||
{"vkUpdateDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(mockUpdateDescriptorSets)},
|
||||
{"vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(mockCmdBindDescriptorSets)}
|
||||
{"vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(mockCmdBindDescriptorSets)},
|
||||
{"vkCreatePipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(mockCreatePipelineLayout)},
|
||||
{"vkDestroyPipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(mockDestroyPipelineLayout)},
|
||||
{"vkCreateShaderModule", reinterpret_cast<PFN_vkVoidFunction>(mockCreateShaderModule)},
|
||||
{"vkDestroyShaderModule", reinterpret_cast<PFN_vkVoidFunction>(mockDestroyShaderModule)},
|
||||
{"vkCreateComputePipelines", reinterpret_cast<PFN_vkVoidFunction>(mockCreateComputePipelines)},
|
||||
{"vkDestroyPipeline", reinterpret_cast<PFN_vkVoidFunction>(mockDestroyPipeline)},
|
||||
{"vkCmdBindPipeline", reinterpret_cast<PFN_vkVoidFunction>(mockCmdBindPipeline)},
|
||||
{"vkCmdPushConstants", reinterpret_cast<PFN_vkVoidFunction>(mockCmdPushConstants)},
|
||||
{"vkCmdDispatch", reinterpret_cast<PFN_vkVoidFunction>(mockCmdDispatch)}
|
||||
};
|
||||
const auto it = table.find(name);
|
||||
return it == table.end() ? nullptr : it->second;
|
||||
|
|
@ -983,6 +1051,50 @@ int main() {
|
|||
});
|
||||
Require(mock.live == 0, "BDA resources leaked Vulkan objects");
|
||||
RunGuestAllocationTests();
|
||||
RunTextureFormatTests();
|
||||
RunTextureTilingTests();
|
||||
RunGuestTextureResourceTests();
|
||||
RunGuestSamplerResourceTests();
|
||||
mock = MockVulkan{};
|
||||
auto textureDetilerContext = mockContext();
|
||||
textureDetilerContext.limits.minStorageBufferOffsetAlignment = 16;
|
||||
textureDetilerContext.limits.maxStorageBufferRange = 256;
|
||||
RunTextureDetilerTests(textureDetilerContext, {
|
||||
[](std::uint64_t size) {
|
||||
VkBuffer buffer{};
|
||||
VkBufferCreateInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||
bufferInfo.size = size;
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
mockCreateBuffer(VK_NULL_HANDLE, &bufferInfo, nullptr, &buffer);
|
||||
VkMemoryRequirements requirements{};
|
||||
mockGetBufferMemoryRequirements(VK_NULL_HANDLE, buffer, &requirements);
|
||||
VkMemoryAllocateInfo allocationInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
allocationInfo.allocationSize = requirements.size;
|
||||
VkDeviceMemory memory{};
|
||||
mockAllocateMemory(VK_NULL_HANDLE, &allocationInfo, nullptr, &memory);
|
||||
mockBindBufferMemory(VK_NULL_HANDLE, buffer, memory, 0);
|
||||
return buffer;
|
||||
},
|
||||
[](VkBuffer buffer) -> std::vector<std::byte>& { return mock.memories.at(mock.bufferMemory.at(buffer)); },
|
||||
[] {
|
||||
Require(mock.writes.size() >= 2, "expected texture detiling descriptor writes");
|
||||
const auto& destinationWrite = mock.writes.back();
|
||||
const auto& sourceWrite = mock.writes[mock.writes.size() - 2];
|
||||
DetilerCapture capture{};
|
||||
capture.groupsX = mock.lastDispatchGroups.x;
|
||||
capture.groupsY = mock.lastDispatchGroups.y;
|
||||
capture.groupsZ = mock.lastDispatchGroups.z;
|
||||
capture.pushConstants = mock.lastPushConstants;
|
||||
capture.sourceBuffer = sourceWrite.buffers.at(0).buffer;
|
||||
capture.sourceOffset = sourceWrite.buffers.at(0).offset;
|
||||
capture.sourceRange = sourceWrite.buffers.at(0).range;
|
||||
capture.destinationBuffer = destinationWrite.buffers.at(0).buffer;
|
||||
capture.destinationOffset = destinationWrite.buffers.at(0).offset;
|
||||
capture.destinationRange = destinationWrite.buffers.at(0).range;
|
||||
return capture;
|
||||
},
|
||||
[] { return mock.pipelineCreateCount; }
|
||||
});
|
||||
std::cout << "Graphics validation tests passed\n";
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_GRAPHICSTESTS_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_GRAPHICSTESTS_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
struct DetilerCapture {
|
||||
std::uint32_t groupsX;
|
||||
std::uint32_t groupsY;
|
||||
std::uint32_t groupsZ;
|
||||
std::vector<std::byte> pushConstants;
|
||||
VkBuffer sourceBuffer;
|
||||
VkDeviceSize sourceOffset;
|
||||
VkDeviceSize sourceRange;
|
||||
VkBuffer destinationBuffer;
|
||||
VkDeviceSize destinationOffset;
|
||||
VkDeviceSize destinationRange;
|
||||
};
|
||||
|
||||
struct TextureDetilerTestAccess {
|
||||
std::function<VkBuffer(std::uint64_t)> makeBuffer;
|
||||
std::function<std::vector<std::byte>&(VkBuffer)> bytes;
|
||||
std::function<DetilerCapture()> lastDispatch;
|
||||
std::function<std::uint32_t()> pipelineCount;
|
||||
};
|
||||
|
||||
void RunTextureFormatTests();
|
||||
void RunTextureTilingTests();
|
||||
void RunGuestTextureResourceTests();
|
||||
void RunGuestSamplerResourceTests();
|
||||
void RunTextureDetilerTests(const AgcDriver::Graphics::Context& context, const TextureDetilerTestAccess& access);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
#include "GraphicsTests.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
|
||||
struct Fields {
|
||||
std::uint32_t clampX = 2;
|
||||
std::uint32_t clampY = 2;
|
||||
std::uint32_t clampZ = 2;
|
||||
std::uint32_t maxAnisoRatio = 0;
|
||||
std::uint32_t depthCompareFunc = 0;
|
||||
bool forceUnormCoords = false;
|
||||
std::uint32_t anisoThreshold = 0;
|
||||
bool forceSrgb = false;
|
||||
std::uint32_t anisoBias = 0;
|
||||
bool truncCoord = false;
|
||||
bool disableCubeWrap = false;
|
||||
std::uint32_t filterMode = 0;
|
||||
bool disableDegamma = false;
|
||||
std::uint32_t minLodRaw = 0;
|
||||
std::uint32_t maxLodRaw = 0x3c00;
|
||||
std::uint32_t perfMip = 0;
|
||||
std::uint32_t perfZ = 0;
|
||||
std::uint32_t lodBiasRaw = 0;
|
||||
std::uint32_t lodBiasSec = 0;
|
||||
std::uint32_t xyMagFilter = 1;
|
||||
std::uint32_t xyMinFilter = 1;
|
||||
std::uint32_t zFilter = 1;
|
||||
std::uint32_t mipFilter = 2;
|
||||
bool pointPreclamp = false;
|
||||
bool anisoOverride = false;
|
||||
bool blendZeroPrt = false;
|
||||
std::uint32_t borderColorType = 0;
|
||||
};
|
||||
|
||||
std::array<std::uint32_t, 4> pack(const Fields& f) {
|
||||
std::array<std::uint32_t, 4> words{};
|
||||
words[0] = (f.clampX & 0x7u) | ((f.clampY & 0x7u) << 3u) | ((f.clampZ & 0x7u) << 6u) | ((f.maxAnisoRatio & 0x7u) << 9u)
|
||||
| ((f.depthCompareFunc & 0x7u) << 12u) | ((f.forceUnormCoords ? 1u : 0u) << 15u) | ((f.anisoThreshold & 0x7u) << 16u)
|
||||
| ((f.forceSrgb ? 1u : 0u) << 20u) | ((f.anisoBias & 0x3fu) << 21u) | ((f.truncCoord ? 1u : 0u) << 27u)
|
||||
| ((f.disableCubeWrap ? 1u : 0u) << 28u) | ((f.filterMode & 0x3u) << 29u) | ((f.disableDegamma ? 1u : 0u) << 31u);
|
||||
words[1] = (f.minLodRaw & 0xfffu) | ((f.maxLodRaw & 0xfffu) << 12u) | ((f.perfMip & 0xfu) << 24u) | ((f.perfZ & 0xfu) << 28u);
|
||||
words[2] = (f.lodBiasRaw & 0x3fffu) | ((f.lodBiasSec & 0x3fu) << 14u) | ((f.xyMagFilter & 0x3u) << 20u)
|
||||
| ((f.xyMinFilter & 0x3u) << 22u) | ((f.zFilter & 0x3u) << 24u) | ((f.mipFilter & 0x3u) << 26u)
|
||||
| ((f.pointPreclamp ? 1u : 0u) << 28u) | ((f.anisoOverride ? 1u : 0u) << 29u) | ((f.blendZeroPrt ? 1u : 0u) << 30u);
|
||||
words[3] = (f.borderColorType & 0x3u) << 30u;
|
||||
return words;
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
void reject(TAction action, std::string_view reason) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::runtime_error& error) {
|
||||
Require(std::string_view(error.what()).find(reason) != std::string_view::npos, std::string("unexpected guest sampler test error: ") + error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(std::string("expected guest sampler rejection: ") + std::string(reason));
|
||||
}
|
||||
|
||||
void rejectFields(const Fields& f, std::string_view reason) {
|
||||
const auto words = pack(f);
|
||||
reject([&] { DecodeSamplerResource(words); }, reason);
|
||||
}
|
||||
|
||||
bool nearlyEqual(float a, float b) {
|
||||
return std::fabs(a - b) < 0.001f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunGuestSamplerResourceTests() {
|
||||
Fields base;
|
||||
auto result = DecodeSamplerResource(pack(base));
|
||||
Require(result.magFilter == VK_FILTER_LINEAR && result.minFilter == VK_FILTER_LINEAR, "linear filter fields decoded incorrectly");
|
||||
Require(result.mipmapMode == VK_SAMPLER_MIPMAP_MODE_LINEAR, "linear mip filter must decode to linear mipmap mode");
|
||||
Require(result.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "clamp mode 2 must decode to clamp-to-edge");
|
||||
Require(!result.anisotropyEnable && result.maxAnisotropy == 1.0f, "non-anisotropic filter must leave anisotropy disabled");
|
||||
Require(nearlyEqual(result.maxLod, static_cast<float>(0x3c00) / 256.0f), "max LOD decoded incorrectly");
|
||||
Require(result.borderColor == VK_BORDER_COLOR_INT_TRANSPARENT_BLACK, "border color type 0 must decode to transparent black");
|
||||
|
||||
Fields nearest = base;
|
||||
nearest.xyMagFilter = 0;
|
||||
nearest.xyMinFilter = 0;
|
||||
nearest.zFilter = 0;
|
||||
nearest.mipFilter = 0;
|
||||
result = DecodeSamplerResource(pack(nearest));
|
||||
Require(result.magFilter == VK_FILTER_NEAREST && result.minFilter == VK_FILTER_NEAREST, "filter 0 must decode to nearest");
|
||||
Require(result.mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST, "mip filter 0 must decode to nearest mipmap mode");
|
||||
Require(result.minLod == 0.0f && result.maxLod == 0.0f, "disabled mip filtering must clear LOD range");
|
||||
|
||||
Fields addressModes = base;
|
||||
addressModes.clampX = 0;
|
||||
addressModes.clampY = 1;
|
||||
addressModes.clampZ = 4;
|
||||
result = DecodeSamplerResource(pack(addressModes));
|
||||
Require(result.addressModeU == VK_SAMPLER_ADDRESS_MODE_REPEAT, "clamp mode 0 must decode to repeat");
|
||||
Require(result.addressModeV == VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, "clamp mode 1 must decode to mirrored repeat");
|
||||
Require(result.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, "clamp mode 4 must decode to clamp-to-border");
|
||||
|
||||
Fields aniso = base;
|
||||
aniso.xyMagFilter = 2;
|
||||
aniso.xyMinFilter = 2;
|
||||
aniso.zFilter = 2;
|
||||
aniso.maxAnisoRatio = 3;
|
||||
result = DecodeSamplerResource(pack(aniso));
|
||||
Require(result.anisotropyEnable, "anisotropic filter must enable anisotropy");
|
||||
Require(nearlyEqual(result.maxAnisotropy, 8.0f), "anisotropy ratio 3 must decode to 8x");
|
||||
aniso.maxAnisoRatio = 5;
|
||||
rejectFields(aniso, "unknown anisotropy ratio");
|
||||
|
||||
Fields badLod = base;
|
||||
badLod.minLodRaw = 100;
|
||||
badLod.maxLodRaw = 50;
|
||||
rejectFields(badLod, "minimum LOD past its maximum LOD");
|
||||
|
||||
Fields badZFilter = base;
|
||||
badZFilter.zFilter = 0;
|
||||
rejectFields(badZFilter, "differs from the minification filter");
|
||||
|
||||
Fields badMipFilter = base;
|
||||
badMipFilter.mipFilter = 3;
|
||||
rejectFields(badMipFilter, "unknown mip filter");
|
||||
|
||||
Fields badDepthCompare = base;
|
||||
badDepthCompare.depthCompareFunc = 1;
|
||||
rejectFields(badDepthCompare, "depth comparison");
|
||||
|
||||
Fields badUnorm = base;
|
||||
badUnorm.forceUnormCoords = true;
|
||||
rejectFields(badUnorm, "unnormalized coordinates");
|
||||
|
||||
Fields badThreshold = base;
|
||||
badThreshold.anisoThreshold = 1;
|
||||
rejectFields(badThreshold, "anisotropy threshold override");
|
||||
|
||||
Fields badSrgb = base;
|
||||
badSrgb.forceSrgb = true;
|
||||
rejectFields(badSrgb, "forces sRGB decoding");
|
||||
|
||||
Fields badAnisoBias = base;
|
||||
badAnisoBias.anisoBias = 1;
|
||||
rejectFields(badAnisoBias, "anisotropy bias");
|
||||
|
||||
Fields badTrunc = base;
|
||||
badTrunc.truncCoord = true;
|
||||
rejectFields(badTrunc, "coordinate truncation");
|
||||
|
||||
Fields badCubeWrap = base;
|
||||
badCubeWrap.disableCubeWrap = true;
|
||||
rejectFields(badCubeWrap, "seamless cube filtering");
|
||||
|
||||
Fields badFilterMode = base;
|
||||
badFilterMode.filterMode = 1;
|
||||
rejectFields(badFilterMode, "reduction filter mode");
|
||||
|
||||
Fields badDegamma = base;
|
||||
badDegamma.disableDegamma = true;
|
||||
rejectFields(badDegamma, "disables degamma");
|
||||
|
||||
Fields badPerf = base;
|
||||
badPerf.perfMip = 1;
|
||||
rejectFields(badPerf, "performance counters");
|
||||
badPerf = base;
|
||||
badPerf.perfZ = 1;
|
||||
rejectFields(badPerf, "performance counters");
|
||||
|
||||
Fields badLodBiasSec = base;
|
||||
badLodBiasSec.lodBiasSec = 1;
|
||||
rejectFields(badLodBiasSec, "secondary LOD bias");
|
||||
|
||||
Fields badPreclamp = base;
|
||||
badPreclamp.pointPreclamp = true;
|
||||
rejectFields(badPreclamp, "point preclamping");
|
||||
|
||||
Fields badAnisoOverride = base;
|
||||
badAnisoOverride.anisoOverride = true;
|
||||
rejectFields(badAnisoOverride, "anisotropy override");
|
||||
|
||||
Fields badBlendZero = base;
|
||||
badBlendZero.blendZeroPrt = true;
|
||||
rejectFields(badBlendZero, "PRT blend-zero");
|
||||
|
||||
Fields badBorder = base;
|
||||
badBorder.borderColorType = 3;
|
||||
rejectFields(badBorder, "border color table");
|
||||
|
||||
Fields opaqueBlack = base;
|
||||
opaqueBlack.borderColorType = 1;
|
||||
Require(DecodeSamplerResource(pack(opaqueBlack)).borderColor == VK_BORDER_COLOR_INT_OPAQUE_BLACK, "border color type 1 must decode to opaque black");
|
||||
Fields opaqueWhite = base;
|
||||
opaqueWhite.borderColorType = 2;
|
||||
Require(DecodeSamplerResource(pack(opaqueWhite)).borderColor == VK_BORDER_COLOR_INT_OPAQUE_WHITE, "border color type 2 must decode to opaque white");
|
||||
|
||||
Fields positiveBias = base;
|
||||
positiveBias.lodBiasRaw = 256;
|
||||
Require(nearlyEqual(DecodeSamplerResource(pack(positiveBias)).lodBias, 1.0f), "positive LOD bias decoded incorrectly");
|
||||
Fields negativeBias = base;
|
||||
negativeBias.lodBiasRaw = static_cast<std::uint32_t>(-256) & 0x3fffu;
|
||||
Require(nearlyEqual(DecodeSamplerResource(pack(negativeBias)).lodBias, -1.0f), "negative LOD bias decoded incorrectly");
|
||||
|
||||
std::array<std::uint32_t, 3> shortWords{};
|
||||
reject([&] { DecodeSamplerResource(shortWords); }, "4 dwords");
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
#include "GraphicsTests.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
using Shape = ShaderRecompiler::DescriptorImageShape;
|
||||
|
||||
struct Fields {
|
||||
std::uint64_t base40 = 0x123456ull;
|
||||
std::uint32_t minLod = 0;
|
||||
std::uint32_t format = 56;
|
||||
std::uint32_t width = 16;
|
||||
std::uint32_t height = 16;
|
||||
std::uint32_t dstSelX = 4;
|
||||
std::uint32_t dstSelY = 5;
|
||||
std::uint32_t dstSelZ = 6;
|
||||
std::uint32_t dstSelW = 7;
|
||||
std::uint32_t baseLevel = 0;
|
||||
std::uint32_t lastLevel = 0;
|
||||
std::uint32_t tileModeRaw = 0x00;
|
||||
std::uint32_t bcSwizzle = 0;
|
||||
std::uint32_t typeRaw = 9;
|
||||
std::uint32_t depth = 0;
|
||||
std::uint32_t baseArray = 0;
|
||||
std::uint32_t arrayPitch = 0;
|
||||
std::uint32_t maxMip = 0;
|
||||
std::uint32_t minLodWarn = 0;
|
||||
std::uint32_t perfMod = 0;
|
||||
bool cornerSample = false;
|
||||
bool mipStatsCntEn = false;
|
||||
bool prtDefColor = false;
|
||||
std::uint32_t mipStatsCntId = 0;
|
||||
bool msaaDepth = false;
|
||||
std::uint32_t maxUncompBlkSize = 0;
|
||||
std::uint32_t maxCompBlkSize = 0;
|
||||
bool metaPipeAligned = false;
|
||||
bool writeCompress = false;
|
||||
bool metaCompress = false;
|
||||
bool dccAlphaPos = false;
|
||||
bool dccColorTransf = false;
|
||||
std::uint64_t metaAddr = 0;
|
||||
};
|
||||
|
||||
std::array<std::uint32_t, 8> pack(const Fields& f) {
|
||||
std::array<std::uint32_t, 8> words{};
|
||||
const auto widthMinus1 = f.width - 1u;
|
||||
const auto heightMinus1 = f.height - 1u;
|
||||
words[0] = static_cast<std::uint32_t>(f.base40 & 0xffffffffull);
|
||||
words[1] = static_cast<std::uint32_t>((f.base40 >> 32u) & 0xffull)
|
||||
| ((f.minLod & 0xfffu) << 8u)
|
||||
| ((f.format & 0x1ffu) << 20u)
|
||||
| ((widthMinus1 & 0x3u) << 30u);
|
||||
words[2] = ((widthMinus1 >> 2u) & 0xfffu) | ((heightMinus1 & 0x3fffu) << 14u);
|
||||
words[3] = (f.dstSelX & 0x7u) | ((f.dstSelY & 0x7u) << 3u) | ((f.dstSelZ & 0x7u) << 6u) | ((f.dstSelW & 0x7u) << 9u)
|
||||
| ((f.baseLevel & 0xfu) << 12u) | ((f.lastLevel & 0xfu) << 16u) | ((f.tileModeRaw & 0x1fu) << 20u)
|
||||
| ((f.bcSwizzle & 0x7u) << 25u) | ((f.typeRaw & 0xfu) << 28u);
|
||||
words[4] = (f.depth & 0x1fffu) | ((f.baseArray & 0x1fffu) << 16u);
|
||||
words[5] = (f.arrayPitch & 0xfu) | ((f.maxMip & 0xfu) << 4u) | ((f.minLodWarn & 0xfffu) << 8u)
|
||||
| ((f.perfMod & 0x7u) << 20u) | ((f.cornerSample ? 1u : 0u) << 23u) | ((f.mipStatsCntEn ? 1u : 0u) << 25u)
|
||||
| ((f.prtDefColor ? 1u : 0u) << 26u);
|
||||
words[6] = (f.mipStatsCntId & 0xffu) | ((f.msaaDepth ? 1u : 0u) << 10u) | ((f.maxUncompBlkSize & 0x3u) << 15u)
|
||||
| ((f.maxCompBlkSize & 0x3u) << 17u) | ((f.metaPipeAligned ? 1u : 0u) << 19u) | ((f.writeCompress ? 1u : 0u) << 20u)
|
||||
| ((f.metaCompress ? 1u : 0u) << 21u) | ((f.dccAlphaPos ? 1u : 0u) << 22u) | ((f.dccColorTransf ? 1u : 0u) << 23u)
|
||||
| static_cast<std::uint32_t>((f.metaAddr & 0xffull) << 24u);
|
||||
words[7] = static_cast<std::uint32_t>(f.metaAddr >> 8u);
|
||||
return words;
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
void reject(TAction action, std::string_view reason) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::runtime_error& error) {
|
||||
Require(std::string_view(error.what()).find(reason) != std::string_view::npos, std::string("unexpected guest texture test error: ") + error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(std::string("expected guest texture rejection: ") + std::string(reason));
|
||||
}
|
||||
|
||||
void rejectFields(const Fields& f, std::string_view reason) {
|
||||
const auto words = pack(f);
|
||||
reject([&] { DecodeTextureResource(words); }, reason);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunGuestTextureResourceTests() {
|
||||
Fields base;
|
||||
auto result = DecodeTextureResource(pack(base));
|
||||
Require(result.baseAddress == (base.base40 << 8u), "decoded base address changed");
|
||||
Require(result.width == 16 && result.height == 16, "decoded width or height changed");
|
||||
Require(result.depthOrLastArray == 0 && result.baseArray == 0, "decoded depth or base array changed");
|
||||
Require(result.mipCount == 1 && result.baseLevel == 0, "decoded mip count or base level changed");
|
||||
Require(result.tileMode == TextureTileMode::kLinear, "decoded tile mode changed");
|
||||
Require(result.dimension == TextureDimension::k2D, "decoded dimension changed");
|
||||
Require(result.format == 56, "decoded format changed");
|
||||
Require(result.dstSelX == 4 && result.dstSelY == 5 && result.dstSelZ == 6 && result.dstSelW == 7, "decoded destination selectors changed");
|
||||
|
||||
Fields wide = base;
|
||||
wide.width = 8192;
|
||||
wide.height = 4096;
|
||||
result = DecodeTextureResource(pack(wide));
|
||||
Require(result.width == 8192 && result.height == 4096, "wide texture dimensions were split across dwords incorrectly");
|
||||
|
||||
Fields tileModes = base;
|
||||
tileModes.tileModeRaw = 0x01;
|
||||
Require(DecodeTextureResource(pack(tileModes)).tileMode == TextureTileMode::kStandard256B, "tile mode 0x01 must decode to standard 256B");
|
||||
tileModes.tileModeRaw = 0x05;
|
||||
Require(DecodeTextureResource(pack(tileModes)).tileMode == TextureTileMode::kStandard4KB, "tile mode 0x05 must decode to standard 4KB");
|
||||
tileModes.tileModeRaw = 0x09;
|
||||
Require(DecodeTextureResource(pack(tileModes)).tileMode == TextureTileMode::kStandard64KB, "tile mode 0x09 must decode to standard 64KB");
|
||||
tileModes.tileModeRaw = 0x02;
|
||||
rejectFields(tileModes, "unsupported tile mode");
|
||||
|
||||
Fields oneD = base;
|
||||
oneD.typeRaw = 8;
|
||||
oneD.height = 1;
|
||||
result = DecodeTextureResource(pack(oneD));
|
||||
Require(result.dimension == TextureDimension::k1D, "1D descriptor did not decode to 1D dimension");
|
||||
oneD.height = 2;
|
||||
rejectFields(oneD, "nonzero height, depth or base array");
|
||||
|
||||
Fields twoD = base;
|
||||
twoD.depth = 1;
|
||||
rejectFields(twoD, "nonzero depth or base array");
|
||||
twoD = base;
|
||||
twoD.baseArray = 1;
|
||||
rejectFields(twoD, "nonzero depth or base array");
|
||||
|
||||
Fields array = base;
|
||||
array.typeRaw = 13;
|
||||
array.depth = 3;
|
||||
array.baseArray = 1;
|
||||
result = DecodeTextureResource(pack(array));
|
||||
Require(result.dimension == TextureDimension::k2DArray && result.depthOrLastArray == 3 && result.baseArray == 1, "2D array descriptor decoded incorrectly");
|
||||
array.baseArray = 5;
|
||||
rejectFields(array, "base array past its last array slice");
|
||||
|
||||
Fields cube = base;
|
||||
cube.typeRaw = 11;
|
||||
cube.width = 32;
|
||||
cube.height = 32;
|
||||
cube.depth = 5;
|
||||
cube.baseArray = 0;
|
||||
result = DecodeTextureResource(pack(cube));
|
||||
Require(result.dimension == TextureDimension::kCube, "cube descriptor did not decode to cube dimension");
|
||||
Fields cubeNotSquare = cube;
|
||||
cubeNotSquare.height = 16;
|
||||
rejectFields(cubeNotSquare, "not square");
|
||||
Fields cubeBadArray = cube;
|
||||
cubeBadArray.baseArray = 6;
|
||||
rejectFields(cubeBadArray, "base array past its last array slice");
|
||||
Fields cubeNotMultiple = cube;
|
||||
cubeNotMultiple.depth = 4;
|
||||
rejectFields(cubeNotMultiple, "multiple of 6");
|
||||
|
||||
Fields badType = base;
|
||||
badType.typeRaw = 0;
|
||||
rejectFields(badType, "unsupported image type");
|
||||
|
||||
Fields badSel = base;
|
||||
badSel.dstSelX = 2;
|
||||
rejectFields(badSel, "invalid destination channel selector");
|
||||
badSel = base;
|
||||
badSel.dstSelW = 3;
|
||||
rejectFields(badSel, "invalid destination channel selector");
|
||||
|
||||
Fields zeroAddress = base;
|
||||
zeroAddress.base40 = 0;
|
||||
rejectFields(zeroAddress, "null base address");
|
||||
|
||||
Fields badMinLod = base;
|
||||
badMinLod.minLod = 1;
|
||||
rejectFields(badMinLod, "nonzero minimum LOD clamp");
|
||||
|
||||
Fields badMinLodWarn = base;
|
||||
badMinLodWarn.minLodWarn = 1;
|
||||
rejectFields(badMinLodWarn, "minimum LOD warning threshold");
|
||||
|
||||
Fields badPerfMod = base;
|
||||
badPerfMod.perfMod = 1;
|
||||
rejectFields(badPerfMod, "performance modulation");
|
||||
|
||||
Fields badMipStats = base;
|
||||
badMipStats.mipStatsCntId = 1;
|
||||
rejectFields(badMipStats, "mip statistics counters");
|
||||
badMipStats = base;
|
||||
badMipStats.mipStatsCntEn = true;
|
||||
rejectFields(badMipStats, "mip statistics counters");
|
||||
|
||||
Fields badCorner = base;
|
||||
badCorner.cornerSample = true;
|
||||
rejectFields(badCorner, "corner sampling");
|
||||
|
||||
Fields badPrt = base;
|
||||
badPrt.prtDefColor = true;
|
||||
rejectFields(badPrt, "partially resident default color");
|
||||
|
||||
Fields badPitch = base;
|
||||
badPitch.arrayPitch = 1;
|
||||
rejectFields(badPitch, "nonzero array pitch");
|
||||
|
||||
Fields badMsaa = base;
|
||||
badMsaa.msaaDepth = true;
|
||||
rejectFields(badMsaa, "MSAA");
|
||||
|
||||
Fields badBlockSize = base;
|
||||
badBlockSize.maxUncompBlkSize = 1;
|
||||
rejectFields(badBlockSize, "DCC block size overrides");
|
||||
badBlockSize = base;
|
||||
badBlockSize.maxCompBlkSize = 1;
|
||||
rejectFields(badBlockSize, "DCC block size overrides");
|
||||
|
||||
Fields badMeta = base;
|
||||
badMeta.metaPipeAligned = true;
|
||||
rejectFields(badMeta, "metadata compression");
|
||||
badMeta = base;
|
||||
badMeta.writeCompress = true;
|
||||
rejectFields(badMeta, "metadata compression");
|
||||
badMeta = base;
|
||||
badMeta.metaCompress = true;
|
||||
rejectFields(badMeta, "metadata compression");
|
||||
badMeta = base;
|
||||
badMeta.dccAlphaPos = true;
|
||||
rejectFields(badMeta, "metadata compression");
|
||||
badMeta = base;
|
||||
badMeta.dccColorTransf = true;
|
||||
rejectFields(badMeta, "metadata compression");
|
||||
badMeta = base;
|
||||
badMeta.metaAddr = 1;
|
||||
rejectFields(badMeta, "metadata compression");
|
||||
|
||||
Fields badSwizzle = base;
|
||||
badSwizzle.bcSwizzle = 1;
|
||||
rejectFields(badSwizzle, "BC swizzle");
|
||||
|
||||
Fields badLevels = base;
|
||||
badLevels.baseLevel = 2;
|
||||
badLevels.lastLevel = 1;
|
||||
badLevels.maxMip = 1;
|
||||
rejectFields(badLevels, "base mip level past its last mip level");
|
||||
|
||||
Fields badMaxMip = base;
|
||||
badMaxMip.lastLevel = 1;
|
||||
badMaxMip.maxMip = 2;
|
||||
rejectFields(badMaxMip, "must expose every mip level");
|
||||
|
||||
std::array<std::uint32_t, 4> shortWords{};
|
||||
reject([&] { DecodeTextureResource(shortWords); }, "8 dwords");
|
||||
|
||||
Require(MatchesGuestDimension(Shape::Image1D, TextureDimension::k1D), "1D shape must match 1D dimension");
|
||||
Require(!MatchesGuestDimension(Shape::Image1D, TextureDimension::k2D), "1D shape must not match 2D dimension");
|
||||
Require(MatchesGuestDimension(Shape::Image2D, TextureDimension::k2D), "2D shape must match 2D dimension");
|
||||
Require(!MatchesGuestDimension(Shape::Image2D, TextureDimension::k2DArray), "2D shape must not match 2D array dimension");
|
||||
Require(MatchesGuestDimension(Shape::Image2DArray, TextureDimension::k2DArray), "2D array shape must match 2D array dimension");
|
||||
Require(!MatchesGuestDimension(Shape::Image2DArray, TextureDimension::kCube), "2D array shape must not match cube dimension");
|
||||
Require(MatchesGuestDimension(Shape::ImageCube, TextureDimension::kCube), "cube shape must match cube dimension");
|
||||
Require(!MatchesGuestDimension(Shape::ImageCube, TextureDimension::k1D), "cube shape must not match 1D dimension");
|
||||
Require(!MatchesGuestDimension(Shape::Image3D, TextureDimension::k1D), "3D shape must never match a guest dimension");
|
||||
Require(!MatchesGuestDimension(Shape::Image3D, TextureDimension::k2D), "3D shape must never match a guest dimension");
|
||||
Require(!MatchesGuestDimension(Shape::Image3D, TextureDimension::k2DArray), "3D shape must never match a guest dimension");
|
||||
Require(!MatchesGuestDimension(Shape::Image3D, TextureDimension::kCube), "3D shape must never match a guest dimension");
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
#include "GraphicsTests.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
|
||||
struct Push {
|
||||
std::uint32_t srcBase;
|
||||
std::uint32_t dstBase;
|
||||
std::uint32_t width;
|
||||
std::uint32_t height;
|
||||
std::uint32_t pitchBytes;
|
||||
std::uint32_t blocksPerRow;
|
||||
std::uint32_t tail;
|
||||
std::uint32_t tailX;
|
||||
std::uint32_t tailY;
|
||||
std::uint32_t elementBytes;
|
||||
};
|
||||
|
||||
Push decodePush(const std::vector<std::byte>& bytes) {
|
||||
Push push{};
|
||||
Require(bytes.size() == sizeof(Push), "unexpected texture detiling push constant size");
|
||||
std::memcpy(&push, bytes.data(), sizeof(Push));
|
||||
return push;
|
||||
}
|
||||
|
||||
TileMipLayout makeLayout(std::uint32_t width, std::uint32_t height, std::uint32_t pitchBytes, std::uint32_t blocksPerRow, std::uint64_t tiledSize, std::uint64_t linearSize) {
|
||||
TileMipLayout layout{};
|
||||
layout.width = width;
|
||||
layout.height = height;
|
||||
layout.pitchBytes = pitchBytes;
|
||||
layout.blocksPerRow = blocksPerRow;
|
||||
layout.tiledSize = tiledSize;
|
||||
layout.linearSize = linearSize;
|
||||
layout.tail = false;
|
||||
layout.tailX = 0;
|
||||
layout.tailY = 0;
|
||||
return layout;
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
void reject(TAction action, std::string_view reason) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::runtime_error& error) {
|
||||
Require(std::string_view(error.what()).find(reason) != std::string_view::npos, std::string("unexpected texture detiler test error: ") + error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(std::string("expected texture detiler rejection: ") + std::string(reason));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunTextureDetilerTests(const Context& context, const TextureDetilerTestAccess& access) {
|
||||
TextureDetiler detiler(context);
|
||||
const auto commands = reinterpret_cast<VkCommandBuffer>(static_cast<std::uintptr_t>(1));
|
||||
const auto source = access.makeBuffer(4096);
|
||||
const auto destination = access.makeBuffer(4096);
|
||||
|
||||
const auto layout = makeLayout(20, 12, 96, 5, 64, 48);
|
||||
detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 20, destination, 40, layout);
|
||||
auto capture = access.lastDispatch();
|
||||
Require(capture.groupsX == 3 && capture.groupsY == 2 && capture.groupsZ == 1, "dispatch group counts were computed incorrectly");
|
||||
const auto push = decodePush(capture.pushConstants);
|
||||
Require(push.srcBase == 4 && push.dstBase == 8, "push constant buffer bases must account for storage buffer offset alignment");
|
||||
Require(push.width == 20 && push.height == 12, "push constant dimensions changed");
|
||||
Require(push.pitchBytes == 96 && push.blocksPerRow == 5, "push constant row layout changed");
|
||||
Require(push.tail == 0 && push.tailX == 0 && push.tailY == 0, "push constant tail fields must reflect a non-tail mip");
|
||||
Require(push.elementBytes == 4, "push constant element size changed");
|
||||
Require(capture.sourceBuffer == source && capture.destinationBuffer == destination, "dispatch bound the wrong source or destination buffer");
|
||||
Require(capture.sourceOffset == 16 && capture.sourceRange == 68, "source descriptor offset or range computed incorrectly");
|
||||
Require(capture.destinationOffset == 32 && capture.destinationRange == 56, "destination descriptor offset or range computed incorrectly");
|
||||
|
||||
const auto pipelinesAfterFirst = access.pipelineCount();
|
||||
Require(pipelinesAfterFirst == 1, "the first dispatch must create exactly one compute pipeline");
|
||||
|
||||
detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, makeLayout(8, 8, 32, 2, 32, 32));
|
||||
Require(access.pipelineCount() == pipelinesAfterFirst, "dispatching with the same tile mode and element size must reuse the cached pipeline");
|
||||
|
||||
detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 8, source, 0, destination, 0, makeLayout(8, 8, 32, 2, 32, 32));
|
||||
Require(access.pipelineCount() == pipelinesAfterFirst + 1, "a different element size must create a new compute pipeline");
|
||||
|
||||
detiler.Dispatch(commands, TextureTileMode::kLinear, 4, source, 0, destination, 0, makeLayout(8, 8, 32, 2, 32, 32));
|
||||
Require(access.pipelineCount() == pipelinesAfterFirst + 2, "a different tile mode must create a new compute pipeline");
|
||||
|
||||
detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, makeLayout(8, 8, 32, 2, 32, 32));
|
||||
Require(access.pipelineCount() == pipelinesAfterFirst + 2, "reusing an earlier tile mode and element size must not create another pipeline");
|
||||
|
||||
reject([&] { detiler.Dispatch(VK_NULL_HANDLE, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, layout); }, "active command buffer");
|
||||
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, VK_NULL_HANDLE, 0, destination, 0, layout); }, "source and destination buffers");
|
||||
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, VK_NULL_HANDLE, 0, layout); }, "source and destination buffers");
|
||||
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, makeLayout(0, 12, 96, 5, 64, 48)); }, "non-empty mip layout");
|
||||
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, makeLayout(20, 0, 96, 5, 64, 48)); }, "non-empty mip layout");
|
||||
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, makeLayout(20, 12, 96, 5, 0, 48)); }, "non-empty mip layout");
|
||||
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, makeLayout(20, 12, 96, 5, 64, 0)); }, "non-empty mip layout");
|
||||
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 3, source, 0, destination, 0, layout); }, "unsupported element size");
|
||||
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 20, destination, 40, makeLayout(20, 12, 96, 5, 1024, 48)); }, "buffer range exceeds device limits");
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include "GraphicsTests.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
|
||||
template<typename TAction>
|
||||
void reject(TAction action, std::string_view reason) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::runtime_error& error) {
|
||||
Require(std::string_view(error.what()).find(reason) != std::string_view::npos, std::string("unexpected format test error: ") + error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(std::string("expected texture format rejection: ") + std::string(reason));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunTextureFormatTests() {
|
||||
Require(ResolveTextureFormat(1) == VK_FORMAT_R8_UNORM, "format 1 must resolve to R8_UNORM");
|
||||
Require(BytesPerElement(1) == 1u, "format 1 must be one byte wide");
|
||||
Require(!IsBlockCompressed(1), "format 1 must not be block compressed");
|
||||
Require(BlockWidth(1) == 1u && BlockHeight(1) == 1u, "format 1 must have a one-texel block");
|
||||
|
||||
Require(ResolveTextureFormat(56) == VK_FORMAT_R8G8B8A8_UNORM, "format 56 must resolve to R8G8B8A8_UNORM");
|
||||
Require(BytesPerElement(56) == 4u, "format 56 must be four bytes wide");
|
||||
|
||||
Require(ResolveTextureFormat(22) == VK_FORMAT_R32_SFLOAT, "format 22 must resolve to R32_SFLOAT");
|
||||
Require(BytesPerElement(22) == 4u, "format 22 must be four bytes wide");
|
||||
|
||||
Require(ResolveTextureFormat(77) == VK_FORMAT_R32G32B32A32_SFLOAT, "format 77 must resolve to R32G32B32A32_SFLOAT");
|
||||
Require(BytesPerElement(77) == 16u, "format 77 must be sixteen bytes wide");
|
||||
|
||||
Require(ResolveTextureFormat(169) == VK_FORMAT_BC1_RGBA_UNORM_BLOCK, "format 169 must resolve to BC1_RGBA_UNORM_BLOCK");
|
||||
Require(IsBlockCompressed(169), "format 169 must be block compressed");
|
||||
Require(BytesPerElement(169) == 8u, "BC1 blocks must be eight bytes");
|
||||
Require(BlockWidth(169) == 4u && BlockHeight(169) == 4u, "BC1 blocks must be four by four texels");
|
||||
|
||||
Require(ResolveTextureFormat(181) == VK_FORMAT_BC7_UNORM_BLOCK, "format 181 must resolve to BC7_UNORM_BLOCK");
|
||||
Require(BytesPerElement(181) == 16u, "BC7 blocks must be sixteen bytes");
|
||||
|
||||
Require(ResolveTextureFormat(34) == ResolveTextureFormat(20), "format 34 must remap to format 20");
|
||||
Require(BytesPerElement(34) == BytesPerElement(20), "remapped format 34 must share the width of format 20");
|
||||
|
||||
reject([] { ResolveTextureFormat(0); }, "unsupported guest texture format");
|
||||
reject([] { ResolveTextureFormat(183); }, "unsupported guest texture format");
|
||||
reject([] { ResolveTextureFormat(9999); }, "unsupported guest texture format");
|
||||
reject([] { BytesPerElement(2); }, "unsupported guest texture format");
|
||||
reject([] { IsBlockCompressed(200); }, "unsupported guest texture format");
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
#include "GraphicsTests.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace AgcDriver::Graphics;
|
||||
|
||||
template<typename TAction>
|
||||
void reject(TAction action, std::string_view reason) {
|
||||
try {
|
||||
action();
|
||||
} catch (const std::runtime_error& error) {
|
||||
Require(std::string_view(error.what()).find(reason) != std::string_view::npos, std::string("unexpected texture tiling test error: ") + error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(std::string("expected texture tiling rejection: ") + std::string(reason));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunTextureTilingTests() {
|
||||
{
|
||||
const auto mips = ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 2);
|
||||
Require(mips.size() == 2, "linear mip chain must contain the requested mip count");
|
||||
Require(mips[0].tiledOffset == 512 && mips[0].tiledSize == 1024, "linear mip 0 offset or size changed");
|
||||
Require(mips[0].width == 4 && mips[0].height == 4, "linear mip 0 dimensions changed");
|
||||
Require(mips[0].blocksPerRow == 256 && mips[0].pitchBytes == 256, "linear mip 0 row layout changed");
|
||||
Require(!mips[0].tail, "linear mips must never fall into a mip tail");
|
||||
Require(mips[1].tiledOffset == 0 && mips[1].tiledSize == 512, "linear mip 1 offset or size changed");
|
||||
Require(mips[1].width == 2 && mips[1].height == 2, "linear mip 1 dimensions changed");
|
||||
Require(mips[1].linearOffset == mips[1].tiledOffset && mips[1].linearSize == mips[1].tiledSize, "linear tiling must keep linear and tiled layout identical");
|
||||
}
|
||||
{
|
||||
const auto mips = ComputeMipLayout(TextureTileMode::kLinear, 169, 8, 8, 1);
|
||||
Require(mips.size() == 1, "compressed linear layout must contain one mip");
|
||||
Require(mips[0].width == 2 && mips[0].height == 2, "compressed linear mip block dimensions changed");
|
||||
Require(mips[0].blocksPerRow == 32 && mips[0].pitchBytes == 256, "compressed linear mip row layout changed");
|
||||
Require(mips[0].tiledSize == 512 && mips[0].linearSize == 512, "compressed linear mip size changed");
|
||||
}
|
||||
{
|
||||
const auto mips = ComputeMipLayout(TextureTileMode::kStandard256B, 1, 64, 64, 1);
|
||||
Require(mips.size() == 1, "standard 256B layout must contain one mip");
|
||||
Require(mips[0].tiledOffset == 0 && mips[0].tiledSize == 4096, "standard 256B mip 0 offset or size changed");
|
||||
Require(mips[0].width == 64 && mips[0].height == 64, "standard 256B mip 0 dimensions changed");
|
||||
Require(mips[0].blocksPerRow == 4 && mips[0].pitchBytes == 64, "standard 256B mip 0 row layout changed");
|
||||
Require(!mips[0].tail, "standard 256B textures must never use a mip tail");
|
||||
|
||||
const auto surfaceSize = ComputeSurfaceSize(mips, 3);
|
||||
Require(surfaceSize == 4096ull * 3ull, "surface size must multiply the slice size by the array layer count");
|
||||
}
|
||||
{
|
||||
const auto mips = ComputeMipLayout(TextureTileMode::kStandard256B, 1, 32, 32, 6);
|
||||
Require(mips.size() == 6, "standard 256B mip chain must contain the requested mip count");
|
||||
for (const auto& mip : mips) Require(!mip.tail, "standard 256B tile mode must never produce a mip tail");
|
||||
}
|
||||
{
|
||||
const auto mips = ComputeMipLayout(TextureTileMode::kStandard64KB, 1, 1024, 1024, 11);
|
||||
Require(mips.size() == 11, "standard 64KB mip chain must contain the requested mip count");
|
||||
auto tailSeen = false;
|
||||
for (const auto& mip : mips) {
|
||||
Require(mip.width != 0 && mip.height != 0, "every standard 64KB mip must have nonzero dimensions");
|
||||
Require(mip.tiledSize != 0 && mip.linearSize != 0, "every standard 64KB mip must have a nonzero size");
|
||||
if (mip.tail) {
|
||||
tailSeen = true;
|
||||
Require(mip.blocksPerRow == 1, "mip tail levels must report a single block per row");
|
||||
Require(mip.tiledOffset == 0 && mip.linearOffset == 0, "mip tail levels must share the tail block offset");
|
||||
}
|
||||
}
|
||||
Require(tailSeen, "a deep standard 64KB mip chain must fall into the mip tail");
|
||||
Require(!mips.front().tail, "the base level of a deep mip chain must not be in the mip tail");
|
||||
}
|
||||
{
|
||||
const auto mips = ComputeMipLayout(TextureTileMode::kStandard4KB, 1, 512, 512, 10);
|
||||
Require(mips.size() == 10, "standard 4KB mip chain must contain the requested mip count");
|
||||
auto tailSeen = false;
|
||||
for (const auto& mip : mips) {
|
||||
if (mip.tail) tailSeen = true;
|
||||
}
|
||||
Require(tailSeen, "a deep standard 4KB mip chain must fall into the mip tail");
|
||||
}
|
||||
|
||||
reject([] { ComputeMipLayout(TextureTileMode::kLinear, 1, 0, 4, 1); }, "zero-sized texture");
|
||||
reject([] { ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 0, 1); }, "zero-sized texture");
|
||||
reject([] { ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 0); }, "mip count is out of range");
|
||||
reject([] { ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 17); }, "mip count is out of range");
|
||||
|
||||
reject([] { ComputeSurfaceSize({}, 1); }, "empty mip chain");
|
||||
reject([] { ComputeSurfaceSize(ComputeMipLayout(TextureTileMode::kLinear, 1, 4, 4, 1), 0); }, "zero array layers");
|
||||
}
|
||||
Loading…
Reference in New Issue