mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-20 01:00:54 +02:00
Emulated images keep a running total of their decoded and ETC2/EAC sizes over every mip and layer. --profile writes both as counters, and summarize_profile_trace.py prints their peaks.
1327 lines
57 KiB
C++
1327 lines
57 KiB
C++
#include "mocktail/graphics/vulkan_etc2_emulation.h"
|
|
|
|
#include "mocktail/graphics/etc2_decoder.h"
|
|
#include "mocktail/graphics/texture_override.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <vector>
|
|
|
|
namespace mocktail::graphics {
|
|
namespace {
|
|
|
|
void ExpectMapping(VkFormat format, VkFormat host, EtcFormat etc) {
|
|
Etc2EmulatedFormat mapped;
|
|
ASSERT_TRUE(LookupEmulatedEtc2Format(format, &mapped)) << format;
|
|
EXPECT_EQ(mapped.host_format, host) << format;
|
|
EXPECT_EQ(mapped.etc_format, etc) << format;
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, MapsEveryEtc2AndEacFormat) {
|
|
ExpectMapping(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, VK_FORMAT_R8G8B8A8_UNORM,
|
|
EtcFormat::kEtc2Rgb8);
|
|
ExpectMapping(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, VK_FORMAT_R8G8B8A8_SRGB,
|
|
EtcFormat::kEtc2Rgb8);
|
|
ExpectMapping(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, VK_FORMAT_R8G8B8A8_UNORM,
|
|
EtcFormat::kEtc2Rgb8A1);
|
|
ExpectMapping(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, VK_FORMAT_R8G8B8A8_SRGB,
|
|
EtcFormat::kEtc2Rgb8A1);
|
|
ExpectMapping(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, VK_FORMAT_R8G8B8A8_UNORM,
|
|
EtcFormat::kEtc2Rgba8);
|
|
ExpectMapping(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, VK_FORMAT_R8G8B8A8_SRGB,
|
|
EtcFormat::kEtc2Rgba8);
|
|
ExpectMapping(VK_FORMAT_EAC_R11_UNORM_BLOCK, VK_FORMAT_R16_UNORM,
|
|
EtcFormat::kEacR11);
|
|
ExpectMapping(VK_FORMAT_EAC_R11_SNORM_BLOCK, VK_FORMAT_R16_SNORM,
|
|
EtcFormat::kEacR11Signed);
|
|
ExpectMapping(VK_FORMAT_EAC_R11G11_UNORM_BLOCK, VK_FORMAT_R16G16_UNORM,
|
|
EtcFormat::kEacRg11);
|
|
ExpectMapping(VK_FORMAT_EAC_R11G11_SNORM_BLOCK, VK_FORMAT_R16G16_SNORM,
|
|
EtcFormat::kEacRg11Signed);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, AdvertisesEtc2UnlessSwitchedOff) {
|
|
EXPECT_TRUE(Etc2SupportAdvertised(nullptr));
|
|
EXPECT_TRUE(Etc2SupportAdvertised(""));
|
|
EXPECT_TRUE(Etc2SupportAdvertised("1"));
|
|
EXPECT_FALSE(Etc2SupportAdvertised("0"));
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, ParsesSmallTextureUpscale) {
|
|
EXPECT_EQ(SmallTextureUpscale(nullptr), 4u);
|
|
EXPECT_EQ(SmallTextureUpscale(""), 4u);
|
|
EXPECT_EQ(SmallTextureUpscale("abc"), 4u);
|
|
EXPECT_EQ(SmallTextureUpscale("0"), 1u);
|
|
EXPECT_EQ(SmallTextureUpscale("1"), 1u);
|
|
EXPECT_EQ(SmallTextureUpscale("2"), 2u);
|
|
EXPECT_EQ(SmallTextureUpscale("99"), 8u);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, IgnoresOtherFormats) {
|
|
Etc2EmulatedFormat mapped;
|
|
EXPECT_FALSE(LookupEmulatedEtc2Format(VK_FORMAT_R8G8B8A8_UNORM, &mapped));
|
|
EXPECT_FALSE(LookupEmulatedEtc2Format(VK_FORMAT_BC7_UNORM_BLOCK, &mapped));
|
|
EXPECT_FALSE(
|
|
LookupEmulatedEtc2Format(VK_FORMAT_ASTC_4x4_UNORM_BLOCK, &mapped));
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, KeepsOnlySamplingAndTransferFeatures) {
|
|
const VkFormatFeatureFlags host =
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |
|
|
VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT |
|
|
VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT |
|
|
VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
|
|
VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
|
|
EXPECT_EQ(EmulatedEtc2FormatFeatures(host),
|
|
static_cast<VkFormatFeatureFlags>(
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |
|
|
VK_FORMAT_FEATURE_BLIT_SRC_BIT |
|
|
VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
|
|
VK_FORMAT_FEATURE_TRANSFER_DST_BIT));
|
|
EXPECT_EQ(EmulatedEtc2FormatFeatures(VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT),
|
|
0U);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, CountsUploadBytesPerRegion) {
|
|
VkDeviceSize compressed = 0;
|
|
VkDeviceSize decoded = 0;
|
|
ASSERT_TRUE(Etc2UploadByteCounts(EtcFormat::kEtc2Rgba8, {5, 3, 1}, 2,
|
|
&compressed, &decoded));
|
|
EXPECT_EQ(compressed, 64U);
|
|
EXPECT_EQ(decoded, 120U);
|
|
|
|
ASSERT_TRUE(Etc2UploadByteCounts(EtcFormat::kEacR11, {1, 1, 1}, 1,
|
|
&compressed, &decoded));
|
|
EXPECT_EQ(compressed, 8U);
|
|
EXPECT_EQ(decoded, 2U);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, CountsWholeImageBytesAcrossMipsAndLayers) {
|
|
VkDeviceSize compressed = 0;
|
|
VkDeviceSize decoded = 0;
|
|
// 64x64 with a full chain: 64, 32, 16, 8, 4, 2, 1.
|
|
ASSERT_TRUE(Etc2ImageByteCounts(EtcFormat::kEtc2Rgb8, {64, 64, 1}, 7, 1, 1,
|
|
&compressed, &decoded));
|
|
EXPECT_EQ(compressed, 343U * 8U);
|
|
EXPECT_EQ(decoded, 5461U * 4U);
|
|
|
|
// Two layers of EAC R11, 2 bytes per decoded texel.
|
|
ASSERT_TRUE(Etc2ImageByteCounts(EtcFormat::kEacR11, {8, 4, 1}, 1, 2, 1,
|
|
&compressed, &decoded));
|
|
EXPECT_EQ(compressed, 2U * 2U * 8U);
|
|
EXPECT_EQ(decoded, 2U * 32U * 2U);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, CountsDecodedBytesAtTheUpscaledExtent) {
|
|
VkDeviceSize compressed = 0;
|
|
VkDeviceSize decoded = 0;
|
|
// The host image is 256x256 with the same 7 levels: 256 down to 4.
|
|
ASSERT_TRUE(Etc2ImageByteCounts(EtcFormat::kEtc2Rgba8, {64, 64, 1}, 7, 1, 4,
|
|
&compressed, &decoded));
|
|
EXPECT_EQ(compressed, 343U * 16U);
|
|
EXPECT_EQ(decoded, 87376U * 4U);
|
|
|
|
EXPECT_FALSE(Etc2ImageByteCounts(EtcFormat::kEtc2Rgb8, {0, 4, 1}, 1, 1, 1,
|
|
&compressed, &decoded));
|
|
EXPECT_FALSE(Etc2ImageByteCounts(EtcFormat::kEtc2Rgb8, {4, 4, 1}, 0, 1, 1,
|
|
&compressed, &decoded));
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, RejectsUnsupportedRegions) {
|
|
VkDeviceSize compressed = 0;
|
|
VkDeviceSize decoded = 0;
|
|
EXPECT_FALSE(Etc2UploadByteCounts(EtcFormat::kEtc2Rgb8, {4, 4, 2}, 1,
|
|
&compressed, &decoded));
|
|
EXPECT_FALSE(Etc2UploadByteCounts(EtcFormat::kEtc2Rgb8, {0, 4, 1}, 1,
|
|
&compressed, &decoded));
|
|
EXPECT_FALSE(Etc2UploadByteCounts(EtcFormat::kEtc2Rgb8, {4, 4, 1}, 0,
|
|
&compressed, &decoded));
|
|
}
|
|
|
|
template <typename Handle>
|
|
Handle FakeHandle(std::uintptr_t value) {
|
|
return reinterpret_cast<Handle>(value);
|
|
}
|
|
|
|
constexpr std::uintptr_t kSourceMemory = 0x300;
|
|
constexpr std::uintptr_t kStagingBuffer = 0x500;
|
|
constexpr std::uintptr_t kStagingMemory = 0x600;
|
|
|
|
std::array<std::uint8_t, 64> g_source{};
|
|
// Every staging allocation maps here. Staging blocks are larger, but these
|
|
// tests write only the first few kilobytes of any block.
|
|
std::array<std::uint8_t, 64 * 1024> g_staging{};
|
|
VkExtent3D g_created_extent{};
|
|
VkExtent3D g_copied_extent{};
|
|
VkDeviceSize g_created_buffer_size = 0;
|
|
std::uintptr_t g_next_image = 0x100;
|
|
std::uint32_t g_copy_image_calls = 0;
|
|
std::uint32_t g_create_buffer_calls = 0;
|
|
std::uint32_t g_allocate_memory_calls = 0;
|
|
std::uint32_t g_destroy_buffer_calls = 0;
|
|
std::uint32_t g_free_memory_calls = 0;
|
|
VkImageBlit g_blit{};
|
|
VkFilter g_blit_filter = VK_FILTER_NEAREST;
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL FakeCreateImage(VkDevice,
|
|
const VkImageCreateInfo* info,
|
|
const VkAllocationCallbacks*,
|
|
VkImage* image) {
|
|
g_created_extent = info->extent;
|
|
*image = FakeHandle<VkImage>(g_next_image++);
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL FakeDestroyImage(VkDevice, VkImage,
|
|
const VkAllocationCallbacks*) {}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL FakeCopyImage(VkCommandBuffer, VkImage,
|
|
VkImageLayout, VkImage, VkImageLayout,
|
|
std::uint32_t, const VkImageCopy*) {
|
|
++g_copy_image_calls;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL FakeBlitImage(VkCommandBuffer, VkImage,
|
|
VkImageLayout, VkImage, VkImageLayout,
|
|
std::uint32_t count,
|
|
const VkImageBlit* blits,
|
|
VkFilter filter) {
|
|
g_blit = count > 0 ? blits[0] : VkImageBlit{};
|
|
g_blit_filter = filter;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL FakeCreateBuffer(VkDevice,
|
|
const VkBufferCreateInfo* info,
|
|
const VkAllocationCallbacks*,
|
|
VkBuffer* buffer) {
|
|
g_created_buffer_size = info->size;
|
|
*buffer = FakeHandle<VkBuffer>(kStagingBuffer + g_create_buffer_calls);
|
|
++g_create_buffer_calls;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL FakeBufferRequirements(
|
|
VkDevice, VkBuffer, VkMemoryRequirements* requirements) {
|
|
requirements->size = g_created_buffer_size;
|
|
requirements->memoryTypeBits = 1;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL FakeAllocateMemory(
|
|
VkDevice, const VkMemoryAllocateInfo*, const VkAllocationCallbacks*,
|
|
VkDeviceMemory* memory) {
|
|
*memory = FakeHandle<VkDeviceMemory>(kStagingMemory);
|
|
++g_allocate_memory_calls;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL FakeBindBufferMemory(VkDevice, VkBuffer,
|
|
VkDeviceMemory,
|
|
VkDeviceSize) {
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL FakeMapMemory(VkDevice, VkDeviceMemory memory,
|
|
VkDeviceSize offset, VkDeviceSize,
|
|
VkMemoryMapFlags, void** data) {
|
|
*data = memory == FakeHandle<VkDeviceMemory>(kSourceMemory)
|
|
? g_source.data() + offset
|
|
: g_staging.data() + offset;
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL FakeUnmapMemory(VkDevice, VkDeviceMemory) {}
|
|
VKAPI_ATTR void VKAPI_CALL FakeDestroyBuffer(VkDevice, VkBuffer,
|
|
const VkAllocationCallbacks*) {
|
|
++g_destroy_buffer_calls;
|
|
}
|
|
VKAPI_ATTR void VKAPI_CALL FakeFreeMemory(VkDevice, VkDeviceMemory,
|
|
const VkAllocationCallbacks*) {
|
|
++g_free_memory_calls;
|
|
}
|
|
// Records the host-write barrier that must precede each staged copy, so the
|
|
// decode's writes are visible to the device even though they land after the
|
|
// submit that reads them.
|
|
std::uint32_t g_barriers_before_copy = 0;
|
|
VkPipelineStageFlags g_barrier_src_stage = 0;
|
|
VkPipelineStageFlags g_barrier_dst_stage = 0;
|
|
VkAccessFlags g_barrier_src_access = 0;
|
|
VkAccessFlags g_barrier_dst_access = 0;
|
|
std::uint32_t g_pending_barriers = 0;
|
|
|
|
VKAPI_ATTR void VKAPI_CALL FakePipelineBarrier(
|
|
VkCommandBuffer, VkPipelineStageFlags src_stage,
|
|
VkPipelineStageFlags dst_stage, VkDependencyFlags, std::uint32_t count,
|
|
const VkMemoryBarrier* barriers, std::uint32_t, const VkBufferMemoryBarrier*,
|
|
std::uint32_t, const VkImageMemoryBarrier*) {
|
|
g_barrier_src_stage = src_stage;
|
|
g_barrier_dst_stage = dst_stage;
|
|
if (count > 0) {
|
|
g_barrier_src_access = barriers[0].srcAccessMask;
|
|
g_barrier_dst_access = barriers[0].dstAccessMask;
|
|
}
|
|
++g_pending_barriers;
|
|
}
|
|
|
|
// Staging buffer and offset of each recorded copy's first region.
|
|
struct StagedCopy {
|
|
VkBuffer buffer = VK_NULL_HANDLE;
|
|
VkDeviceSize offset = 0;
|
|
};
|
|
std::vector<StagedCopy> g_staged_copies;
|
|
|
|
VKAPI_ATTR void VKAPI_CALL FakeCopyBufferToImage(
|
|
VkCommandBuffer, VkBuffer buffer, VkImage, VkImageLayout,
|
|
std::uint32_t count, const VkBufferImageCopy* regions) {
|
|
g_copied_extent = count > 0 ? regions[0].imageExtent : VkExtent3D{};
|
|
g_barriers_before_copy = g_pending_barriers;
|
|
if (count > 0) {
|
|
g_staged_copies.push_back({buffer, regions[0].bufferOffset});
|
|
}
|
|
}
|
|
|
|
constexpr std::uintptr_t kTimelineSemaphore = 0x900;
|
|
|
|
std::mutex g_signal_mutex;
|
|
std::vector<std::uint64_t> g_signalled;
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL FakeCreateSemaphore(
|
|
VkDevice, const VkSemaphoreCreateInfo*, const VkAllocationCallbacks*,
|
|
VkSemaphore* semaphore) {
|
|
*semaphore = FakeHandle<VkSemaphore>(kTimelineSemaphore);
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR VkResult VKAPI_CALL
|
|
FakeSignalSemaphore(VkDevice, const VkSemaphoreSignalInfo* info) {
|
|
std::lock_guard<std::mutex> lock(g_signal_mutex);
|
|
g_signalled.push_back(info->value);
|
|
return VK_SUCCESS;
|
|
}
|
|
|
|
VKAPI_ATTR void VKAPI_CALL FakeDestroySemaphore(VkDevice, VkSemaphore,
|
|
const VkAllocationCallbacks*) {}
|
|
|
|
std::vector<std::uint64_t> SignalledValues() {
|
|
std::lock_guard<std::mutex> lock(g_signal_mutex);
|
|
return g_signalled;
|
|
}
|
|
|
|
void ResetSignalledValues() {
|
|
std::lock_guard<std::mutex> lock(g_signal_mutex);
|
|
g_signalled.clear();
|
|
}
|
|
|
|
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL FakeGetDeviceProcAddr(
|
|
VkDevice, const char* name) {
|
|
const auto is = [name](const char* candidate) {
|
|
return std::strcmp(name, candidate) == 0;
|
|
};
|
|
if (is("vkCreateSemaphore"))
|
|
return reinterpret_cast<PFN_vkVoidFunction>(FakeCreateSemaphore);
|
|
if (is("vkSignalSemaphore"))
|
|
return reinterpret_cast<PFN_vkVoidFunction>(FakeSignalSemaphore);
|
|
if (is("vkDestroySemaphore"))
|
|
return reinterpret_cast<PFN_vkVoidFunction>(FakeDestroySemaphore);
|
|
if (is("vkCreateImage")) return reinterpret_cast<PFN_vkVoidFunction>(FakeCreateImage);
|
|
if (is("vkDestroyImage")) return reinterpret_cast<PFN_vkVoidFunction>(FakeDestroyImage);
|
|
if (is("vkCreateBuffer")) return reinterpret_cast<PFN_vkVoidFunction>(FakeCreateBuffer);
|
|
if (is("vkGetBufferMemoryRequirements"))
|
|
return reinterpret_cast<PFN_vkVoidFunction>(FakeBufferRequirements);
|
|
if (is("vkAllocateMemory")) return reinterpret_cast<PFN_vkVoidFunction>(FakeAllocateMemory);
|
|
if (is("vkBindBufferMemory")) return reinterpret_cast<PFN_vkVoidFunction>(FakeBindBufferMemory);
|
|
if (is("vkMapMemory")) return reinterpret_cast<PFN_vkVoidFunction>(FakeMapMemory);
|
|
if (is("vkUnmapMemory")) return reinterpret_cast<PFN_vkVoidFunction>(FakeUnmapMemory);
|
|
if (is("vkDestroyBuffer")) return reinterpret_cast<PFN_vkVoidFunction>(FakeDestroyBuffer);
|
|
if (is("vkFreeMemory")) return reinterpret_cast<PFN_vkVoidFunction>(FakeFreeMemory);
|
|
if (is("vkCmdCopyBufferToImage"))
|
|
return reinterpret_cast<PFN_vkVoidFunction>(FakeCopyBufferToImage);
|
|
if (is("vkCmdPipelineBarrier"))
|
|
return reinterpret_cast<PFN_vkVoidFunction>(FakePipelineBarrier);
|
|
if (is("vkCmdCopyImage")) return reinterpret_cast<PFN_vkVoidFunction>(FakeCopyImage);
|
|
if (is("vkCmdBlitImage")) return reinterpret_cast<PFN_vkVoidFunction>(FakeBlitImage);
|
|
return nullptr;
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, TotalsTheBytesOfLiveEmulatedImages) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x12);
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x20), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.imageType = VK_IMAGE_TYPE_3D;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.extent = {8, 8, 1};
|
|
image_info.mipLevels = 1;
|
|
image_info.arrayLayers = 1;
|
|
VkImage first = VK_NULL_HANDLE;
|
|
VkImage second = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &first),
|
|
VK_SUCCESS);
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &second),
|
|
VK_SUCCESS);
|
|
image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
VkImage plain = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &plain),
|
|
VK_SUCCESS);
|
|
|
|
EmulatedTextureBytes totals = emulation.TextureBytes();
|
|
EXPECT_EQ(totals.compressed, 2U * 4U * 8U);
|
|
EXPECT_EQ(totals.decoded, 2U * 64U * 4U);
|
|
|
|
emulation.DestroyImage(device, first, nullptr);
|
|
emulation.DestroyImage(device, plain, nullptr);
|
|
totals = emulation.TextureBytes();
|
|
EXPECT_EQ(totals.compressed, 4U * 8U);
|
|
EXPECT_EQ(totals.decoded, 64U * 4U);
|
|
|
|
emulation.DestroyDevice(device);
|
|
totals = emulation.TextureBytes();
|
|
EXPECT_EQ(totals.compressed, 0U);
|
|
EXPECT_EQ(totals.decoded, 0U);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, SkipsUploadOutsideTheMappedWindow) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x10);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x200);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x700);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x20), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.extent = {4, 4, 1};
|
|
VkImage image = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
|
|
// The 8-byte block sits at memory offset 16, but only [0, 8) is mapped.
|
|
ASSERT_EQ(emulation.BindBufferMemory(device, source, source_memory, 16),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
ASSERT_EQ(emulation.MapMemory(device, source_memory, 0, 8, 0, &mapped),
|
|
VK_SUCCESS);
|
|
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = {4, 4, 1};
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
|
®ion);
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
|
|
EXPECT_EQ(g_staging[0], 0xAA);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, DecodesAgainOnEveryResubmit) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x11);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x201);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x701);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x21), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.extent = {4, 4, 1};
|
|
VkImage image = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
ASSERT_EQ(emulation.BindBufferMemory(device, source, source_memory, 0),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
ASSERT_EQ(emulation.MapMemory(device, source_memory, 0, g_source.size(), 0,
|
|
&mapped),
|
|
VK_SUCCESS);
|
|
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = {4, 4, 1};
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
|
®ion);
|
|
|
|
// Differential block: every texel decodes to 134.
|
|
const std::array<std::uint8_t, 8> first = {0x81, 0x81, 0x81, 0x02,
|
|
0, 0, 0, 0};
|
|
std::memcpy(g_source.data(), first.data(), first.size());
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
EXPECT_EQ(g_staging[0], 134);
|
|
|
|
// Individual block: every texel decodes to 138.
|
|
const std::array<std::uint8_t, 8> second = {0x88, 0x88, 0x88, 0x00,
|
|
0, 0, 0, 0};
|
|
std::memcpy(g_source.data(), second.data(), second.size());
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
EXPECT_EQ(g_staging[0], 138);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, UpscalesSmallTexturesAndTheirUploads) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x12);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x202);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x702);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ASSERT_EQ(setenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE", "2", 1), 0);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
ASSERT_EQ(unsetenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE"), 0);
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x22), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.extent = {4, 4, 1};
|
|
image_info.mipLevels = 1;
|
|
image_info.arrayLayers = 1;
|
|
VkImage image = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
EXPECT_EQ(g_created_extent.width, 8u);
|
|
EXPECT_EQ(g_created_extent.height, 8u);
|
|
ASSERT_EQ(emulation.BindBufferMemory(device, source, source_memory, 0),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
ASSERT_EQ(emulation.MapMemory(device, source_memory, 0, g_source.size(), 0,
|
|
&mapped),
|
|
VK_SUCCESS);
|
|
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = {4, 4, 1};
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
|
®ion);
|
|
EXPECT_EQ(g_copied_extent.width, 8u);
|
|
EXPECT_EQ(g_copied_extent.height, 8u);
|
|
|
|
const std::array<std::uint8_t, 8> block = {0x81, 0x81, 0x81, 0x02,
|
|
0, 0, 0, 0};
|
|
std::memcpy(g_source.data(), block.data(), block.size());
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
// The host region is 8x8: every 4x4 texel repeated 2x2.
|
|
std::array<std::uint8_t, 4 * 4 * 4> expected{};
|
|
ASSERT_TRUE(DecodeEtcImage(EtcFormat::kEtc2Rgb8, block.data(), block.size(),
|
|
4, 4, expected.data(), expected.size()));
|
|
for (std::size_t y = 0; y < 8; ++y) {
|
|
for (std::size_t x = 0; x < 8; ++x) {
|
|
for (std::size_t channel = 0; channel < 4; ++channel) {
|
|
ASSERT_EQ(g_staging[(y * 8 + x) * 4 + channel],
|
|
expected[((y / 2) * 4 + x / 2) * 4 + channel])
|
|
<< x << "," << y;
|
|
}
|
|
}
|
|
}
|
|
EXPECT_EQ(g_staging[8 * 8 * 4], 0xAA);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
// Block-rounded uploads (a 1x1 mip sent as 4x4) must not scale past the
|
|
// host mip; the host region snaps to the host mip's edge instead.
|
|
TEST(VulkanEtc2EmulationTest, ClampsScaledUploadsToTheHostMip) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x14);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x204);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x704);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ASSERT_EQ(setenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE", "2", 1), 0);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
ASSERT_EQ(unsetenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE"), 0);
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x24), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.extent = {4, 4, 1};
|
|
image_info.mipLevels = 3;
|
|
image_info.arrayLayers = 1;
|
|
VkImage image = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
ASSERT_EQ(emulation.BindBufferMemory(device, source, source_memory, 0),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
ASSERT_EQ(emulation.MapMemory(device, source_memory, 0, g_source.size(), 0,
|
|
&mapped),
|
|
VK_SUCCESS);
|
|
|
|
// Mip 2 is 1x1 for the application and 2x2 on the host.
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.mipLevel = 2;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = {4, 4, 1};
|
|
g_copied_extent = {};
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
|
®ion);
|
|
EXPECT_EQ(g_copied_extent.width, 2u);
|
|
EXPECT_EQ(g_copied_extent.height, 2u);
|
|
|
|
// Differential block: every texel decodes to 134.
|
|
const std::array<std::uint8_t, 8> block = {0x81, 0x81, 0x81, 0x02,
|
|
0, 0, 0, 0};
|
|
std::memcpy(g_source.data(), block.data(), block.size());
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
for (std::size_t index = 0; index < 2 * 2 * 4; ++index) {
|
|
ASSERT_EQ(g_staging[index], index % 4 == 3 ? 255 : 134) << index;
|
|
}
|
|
EXPECT_EQ(g_staging[2 * 2 * 4], 0xAA);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
// Drawn at its own size, a scaled texture samples a smaller host mip, so
|
|
// every host mip carries level-0 detail rather than the application's mip.
|
|
TEST(VulkanEtc2EmulationTest, FillsScaledMipsFromLevelZero) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x15);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x205);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x705);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ASSERT_EQ(setenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE", "2", 1), 0);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
ASSERT_EQ(unsetenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE"), 0);
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x25), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.extent = {4, 4, 1};
|
|
image_info.mipLevels = 3;
|
|
image_info.arrayLayers = 1;
|
|
VkImage image = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
ASSERT_EQ(emulation.BindBufferMemory(device, source, source_memory, 0),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
ASSERT_EQ(emulation.MapMemory(device, source_memory, 0, g_source.size(), 0,
|
|
&mapped),
|
|
VK_SUCCESS);
|
|
|
|
// Level 0 decodes to 134 everywhere, level 1 (at byte 8) to 138.
|
|
const std::array<std::uint8_t, 8> level0 = {0x81, 0x81, 0x81, 0x02,
|
|
0, 0, 0, 0};
|
|
const std::array<std::uint8_t, 8> level1 = {0x88, 0x88, 0x88, 0x00,
|
|
0, 0, 0, 0};
|
|
std::memcpy(g_source.data(), level0.data(), level0.size());
|
|
std::memcpy(g_source.data() + 8, level1.data(), level1.size());
|
|
|
|
std::array<VkBufferImageCopy, 2> regions{};
|
|
regions[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
regions[0].imageSubresource.layerCount = 1;
|
|
regions[0].imageExtent = {4, 4, 1};
|
|
regions[1] = regions[0];
|
|
regions[1].bufferOffset = 8;
|
|
regions[1].imageSubresource.mipLevel = 1;
|
|
regions[1].imageExtent = {2, 2, 1};
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2,
|
|
regions.data());
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
|
|
// Host level 0 is 8x8 (256 bytes); host level 1 is 4x4 and follows it,
|
|
// holding level 0 itself rather than the 138 texels of level 1.
|
|
RgbaImage level0_texels;
|
|
level0_texels.width = 4;
|
|
level0_texels.height = 4;
|
|
level0_texels.pixels.resize(4 * 4 * 4);
|
|
ASSERT_TRUE(DecodeEtcImage(EtcFormat::kEtc2Rgb8, level0.data(),
|
|
level0.size(), 4, 4, level0_texels.pixels.data(),
|
|
level0_texels.pixels.size()));
|
|
std::array<std::uint8_t, 4 * 4 * 4> expected{};
|
|
ResampleRgba(level0_texels, 4, 4, expected.data());
|
|
for (std::size_t index = 0; index < expected.size(); ++index) {
|
|
ASSERT_EQ(g_staging[256 + index], expected[index]) << index;
|
|
}
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
// Level 0 carries the texels every scaled mip is built from, so a mip
|
|
// recorded ahead of it must still resolve against it.
|
|
TEST(VulkanEtc2EmulationTest, FillsScaledMipsFromLevelZeroRecordedLast) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x16);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x206);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x706);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ASSERT_EQ(setenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE", "2", 1), 0);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
ASSERT_EQ(unsetenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE"), 0);
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x26), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.extent = {4, 4, 1};
|
|
image_info.mipLevels = 3;
|
|
image_info.arrayLayers = 1;
|
|
VkImage image = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
ASSERT_EQ(emulation.BindBufferMemory(device, source, source_memory, 0),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
ASSERT_EQ(emulation.MapMemory(device, source_memory, 0, g_source.size(), 0,
|
|
&mapped),
|
|
VK_SUCCESS);
|
|
|
|
const std::array<std::uint8_t, 8> level0 = {0x81, 0x81, 0x81, 0x02,
|
|
0, 0, 0, 0};
|
|
const std::array<std::uint8_t, 8> level1 = {0x88, 0x88, 0x88, 0x00,
|
|
0, 0, 0, 0};
|
|
std::memcpy(g_source.data(), level0.data(), level0.size());
|
|
std::memcpy(g_source.data() + 8, level1.data(), level1.size());
|
|
|
|
// Level 1 is recorded first, so its staging bytes lead the batch.
|
|
std::array<VkBufferImageCopy, 2> regions{};
|
|
regions[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
regions[0].imageSubresource.layerCount = 1;
|
|
regions[0].bufferOffset = 8;
|
|
regions[0].imageSubresource.mipLevel = 1;
|
|
regions[0].imageExtent = {2, 2, 1};
|
|
regions[1] = regions[0];
|
|
regions[1].bufferOffset = 0;
|
|
regions[1].imageSubresource.mipLevel = 0;
|
|
regions[1].imageExtent = {4, 4, 1};
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2,
|
|
regions.data());
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
|
|
RgbaImage level0_texels;
|
|
level0_texels.width = 4;
|
|
level0_texels.height = 4;
|
|
level0_texels.pixels.resize(4 * 4 * 4);
|
|
ASSERT_TRUE(DecodeEtcImage(EtcFormat::kEtc2Rgb8, level0.data(),
|
|
level0.size(), 4, 4, level0_texels.pixels.data(),
|
|
level0_texels.pixels.size()));
|
|
std::array<std::uint8_t, 4 * 4 * 4> expected{};
|
|
ResampleRgba(level0_texels, 4, 4, expected.data());
|
|
for (std::size_t index = 0; index < expected.size(); ++index) {
|
|
ASSERT_EQ(g_staging[index], expected[index]) << index;
|
|
}
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, ReusesStagingBuffersAcrossCommandBuffers) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x17);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x207);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
g_source.fill(0);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x27), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.extent = {4, 4, 1};
|
|
VkImage image = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
ASSERT_EQ(emulation.BindBufferMemory(device, source, source_memory, 0),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
ASSERT_EQ(emulation.MapMemory(device, source_memory, 0, g_source.size(), 0,
|
|
&mapped),
|
|
VK_SUCCESS);
|
|
|
|
g_create_buffer_calls = 0;
|
|
g_allocate_memory_calls = 0;
|
|
g_destroy_buffer_calls = 0;
|
|
g_free_memory_calls = 0;
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = {4, 4, 1};
|
|
for (std::uintptr_t frame = 0; frame < 8; ++frame) {
|
|
const VkCommandBuffer command_buffer =
|
|
FakeHandle<VkCommandBuffer>(0x780 + frame);
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
|
®ion);
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
EXPECT_EQ(g_create_buffer_calls, 1u);
|
|
EXPECT_EQ(g_allocate_memory_calls, 1u);
|
|
EXPECT_EQ(g_destroy_buffer_calls, 0u);
|
|
|
|
emulation.DestroyDevice(device);
|
|
EXPECT_EQ(g_destroy_buffer_calls, 1u);
|
|
EXPECT_EQ(g_free_memory_calls, 1u);
|
|
}
|
|
|
|
// Registers `device` and creates an EAC R11 image of `extent` whose uploads
|
|
// read from `source`. EAC is never upscaled, so staging bytes are the
|
|
// decoded size: two bytes per texel.
|
|
VkImage SetUpStagingTest(VulkanEtc2Emulation& emulation, VkDevice device,
|
|
VkBuffer source, VkExtent3D extent) {
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x28), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_EAC_R11_UNORM_BLOCK;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.extent = extent;
|
|
image_info.mipLevels = 1;
|
|
image_info.arrayLayers = 1;
|
|
VkImage image = VK_NULL_HANDLE;
|
|
EXPECT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
EXPECT_EQ(emulation.BindBufferMemory(
|
|
device, source, FakeHandle<VkDeviceMemory>(kSourceMemory), 0),
|
|
VK_SUCCESS);
|
|
g_create_buffer_calls = 0;
|
|
g_destroy_buffer_calls = 0;
|
|
g_staged_copies.clear();
|
|
return image;
|
|
}
|
|
|
|
void RecordUpload(VulkanEtc2Emulation& emulation, VkDevice device,
|
|
VkCommandBuffer command_buffer, VkBuffer source,
|
|
VkImage image, VkExtent3D extent) {
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = extent;
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
|
®ion);
|
|
}
|
|
|
|
// One buffer and allocation per upload costs the render thread a driver call
|
|
// on each side of the upload's life, so uploads share large blocks.
|
|
TEST(VulkanEtc2EmulationTest, SubAllocatesUploadsFromOneStagingBlock) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x18);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x208);
|
|
VulkanEtc2Emulation emulation;
|
|
const VkImage image = SetUpStagingTest(emulation, device, source, {4, 4, 1});
|
|
|
|
std::vector<VkCommandBuffer> command_buffers;
|
|
for (std::uintptr_t index = 0; index < 8; ++index) {
|
|
command_buffers.push_back(FakeHandle<VkCommandBuffer>(0x790 + index));
|
|
RecordUpload(emulation, device, command_buffers.back(), source, image,
|
|
{4, 4, 1});
|
|
}
|
|
EXPECT_EQ(g_create_buffer_calls, 1u);
|
|
ASSERT_EQ(g_staged_copies.size(), 8u);
|
|
for (std::size_t index = 0; index < g_staged_copies.size(); ++index) {
|
|
EXPECT_EQ(g_staged_copies[index].buffer, g_staged_copies[0].buffer);
|
|
EXPECT_EQ(g_staged_copies[index].offset % 4, 0u) << index;
|
|
if (index > 0) {
|
|
// Each upload takes 4 x 4 texels of 2 bytes, clear of the one before.
|
|
EXPECT_GE(g_staged_copies[index].offset,
|
|
g_staged_copies[index - 1].offset + 4 * 4 * 2)
|
|
<< index;
|
|
}
|
|
}
|
|
for (VkCommandBuffer command_buffer : command_buffers) {
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
EXPECT_EQ(g_destroy_buffer_calls, 0u);
|
|
emulation.DestroyDevice(device);
|
|
EXPECT_EQ(g_destroy_buffer_calls, 1u);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, StartsANewStagingBlockWhenTheCurrentIsFull) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x19);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x209);
|
|
VulkanEtc2Emulation emulation(1024);
|
|
// 16 x 16 texels of 2 bytes: two uploads fill a block.
|
|
const VkImage image =
|
|
SetUpStagingTest(emulation, device, source, {16, 16, 1});
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x7a0);
|
|
for (int upload = 0; upload < 3; ++upload) {
|
|
RecordUpload(emulation, device, command_buffer, source, image,
|
|
{16, 16, 1});
|
|
}
|
|
EXPECT_EQ(g_create_buffer_calls, 2u);
|
|
ASSERT_EQ(g_staged_copies.size(), 3u);
|
|
EXPECT_EQ(g_staged_copies[1].buffer, g_staged_copies[0].buffer);
|
|
EXPECT_NE(g_staged_copies[2].buffer, g_staged_copies[0].buffer);
|
|
EXPECT_EQ(g_staged_copies[2].offset, 0u);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, GivesAnUploadLargerThanABlockItsOwnBlock) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x1a);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x20a);
|
|
VulkanEtc2Emulation emulation(1024);
|
|
const VkImage image =
|
|
SetUpStagingTest(emulation, device, source, {32, 32, 1});
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x7a1);
|
|
RecordUpload(emulation, device, command_buffer, source, image, {32, 32, 1});
|
|
EXPECT_EQ(g_create_buffer_calls, 1u);
|
|
EXPECT_GE(g_created_buffer_size, 32u * 32u * 2u);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, RefillsAStagingBlockOnceItsCopiesAreReleased) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x1b);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x20b);
|
|
VulkanEtc2Emulation emulation(1024);
|
|
const VkImage image =
|
|
SetUpStagingTest(emulation, device, source, {16, 16, 1});
|
|
const VkCommandBuffer first = FakeHandle<VkCommandBuffer>(0x7a2);
|
|
const VkCommandBuffer second = FakeHandle<VkCommandBuffer>(0x7a3);
|
|
RecordUpload(emulation, device, first, source, image, {16, 16, 1});
|
|
RecordUpload(emulation, device, first, source, image, {16, 16, 1});
|
|
emulation.ReleaseCommandBuffer(first);
|
|
RecordUpload(emulation, device, second, source, image, {16, 16, 1});
|
|
EXPECT_EQ(g_create_buffer_calls, 1u);
|
|
ASSERT_EQ(g_staged_copies.size(), 3u);
|
|
EXPECT_EQ(g_staged_copies[2].buffer, g_staged_copies[0].buffer);
|
|
EXPECT_EQ(g_staged_copies[2].offset, 0u);
|
|
emulation.ReleaseCommandBuffer(second);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, KeepsFourIdleStagingBlocksAndDestroysTheRest) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x1c);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x20c);
|
|
VulkanEtc2Emulation emulation(1024);
|
|
const VkImage image =
|
|
SetUpStagingTest(emulation, device, source, {16, 16, 1});
|
|
// Six command buffers each fill a block of their own.
|
|
std::vector<VkCommandBuffer> command_buffers;
|
|
for (std::uintptr_t index = 0; index < 6; ++index) {
|
|
command_buffers.push_back(FakeHandle<VkCommandBuffer>(0x7b0 + index));
|
|
RecordUpload(emulation, device, command_buffers.back(), source, image,
|
|
{16, 16, 1});
|
|
RecordUpload(emulation, device, command_buffers.back(), source, image,
|
|
{16, 16, 1});
|
|
}
|
|
ASSERT_EQ(g_create_buffer_calls, 6u);
|
|
for (VkCommandBuffer command_buffer : command_buffers) {
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
EXPECT_EQ(g_destroy_buffer_calls, 2u);
|
|
|
|
const VkCommandBuffer again = FakeHandle<VkCommandBuffer>(0x7c0);
|
|
RecordUpload(emulation, device, again, source, image, {16, 16, 1});
|
|
EXPECT_EQ(g_create_buffer_calls, 6u);
|
|
emulation.ReleaseCommandBuffer(again);
|
|
}
|
|
|
|
// Copies out of a scaled image cover its host bounds and shrink back onto
|
|
// an unscaled destination, which a plain copy cannot do.
|
|
TEST(VulkanEtc2EmulationTest, BlitsCopiesThatTouchScaledImages) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x16);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x706);
|
|
ASSERT_EQ(setenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE", "2", 1), 0);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
ASSERT_EQ(unsetenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE"), 0);
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x26), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.extent = {4, 4, 1};
|
|
image_info.mipLevels = 3;
|
|
image_info.arrayLayers = 1;
|
|
VkImage small = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &small),
|
|
VK_SUCCESS);
|
|
image_info.extent = {128, 128, 1};
|
|
image_info.mipLevels = 8;
|
|
VkImage large = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &large),
|
|
VK_SUCCESS);
|
|
|
|
// Mip 1 of the small image (2x2, block-rounded to 4x4) lands in mip 6 of
|
|
// the large one, 2x2 at offset (1, 1).
|
|
VkImageCopy region{};
|
|
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.srcSubresource.mipLevel = 1;
|
|
region.srcSubresource.layerCount = 1;
|
|
region.dstSubresource = region.srcSubresource;
|
|
region.dstSubresource.mipLevel = 6;
|
|
region.dstOffset = {1, 1, 0};
|
|
region.extent = {4, 4, 1};
|
|
g_copy_image_calls = 0;
|
|
g_blit = {};
|
|
emulation.CmdCopyImage(device, command_buffer, small,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, large,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
|
EXPECT_EQ(g_copy_image_calls, 0u);
|
|
EXPECT_EQ(g_blit.srcSubresource.mipLevel, 1u);
|
|
EXPECT_EQ(g_blit.srcOffsets[1].x, 4);
|
|
EXPECT_EQ(g_blit.srcOffsets[1].y, 4);
|
|
EXPECT_EQ(g_blit.dstSubresource.mipLevel, 6u);
|
|
EXPECT_EQ(g_blit.dstOffsets[0].x, 1);
|
|
EXPECT_EQ(g_blit.dstOffsets[1].x, 5);
|
|
EXPECT_EQ(g_blit_filter, VK_FILTER_LINEAR);
|
|
|
|
// Copies between unscaled images pass through untouched.
|
|
emulation.CmdCopyImage(device, command_buffer, large,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, large,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
|
EXPECT_EQ(g_copy_image_calls, 1u);
|
|
}
|
|
|
|
// bufferRowLength counts texels of source pitch, not of the copied region, so
|
|
// a padded row holds blocks the region does not cover.
|
|
TEST(VulkanEtc2EmulationTest, ReadsCompressedRowsAtTheRequestedStride) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x18);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x208);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x708);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ASSERT_EQ(setenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE", "1", 1), 0);
|
|
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
VulkanEtc2Emulation emulation;
|
|
ASSERT_EQ(unsetenv("MOCKTAIL_SMALL_TEXTURE_UPSCALE"), 0);
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x28), true,
|
|
memory, FakeGetDeviceProcAddr);
|
|
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.extent = {8, 8, 1};
|
|
image_info.mipLevels = 1;
|
|
image_info.arrayLayers = 1;
|
|
VkImage image = VK_NULL_HANDLE;
|
|
ASSERT_EQ(emulation.CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
ASSERT_EQ(g_created_extent.width, 8u);
|
|
ASSERT_EQ(emulation.BindBufferMemory(device, source, source_memory, 0),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
ASSERT_EQ(emulation.MapMemory(device, source_memory, 0, g_source.size(), 0,
|
|
&mapped),
|
|
VK_SUCCESS);
|
|
|
|
// Differential block: every texel decodes to 134.
|
|
const std::array<std::uint8_t, 8> covered = {0x81, 0x81, 0x81, 0x02,
|
|
0, 0, 0, 0};
|
|
// Individual block: every texel decodes to 138.
|
|
const std::array<std::uint8_t, 8> padding = {0x88, 0x88, 0x88, 0x00,
|
|
0, 0, 0, 0};
|
|
// 16 texels of pitch is 4 blocks per row; the 8x8 region covers the first 2.
|
|
const std::array<const std::uint8_t*, 6> layout = {
|
|
covered.data(), covered.data(), padding.data(),
|
|
padding.data(), covered.data(), covered.data()};
|
|
for (std::size_t block = 0; block < layout.size(); ++block) {
|
|
std::memcpy(g_source.data() + block * 8, layout[block], 8);
|
|
}
|
|
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = {8, 8, 1};
|
|
region.bufferRowLength = 16;
|
|
emulation.CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
|
®ion);
|
|
emulation.PrepareSubmit(&command_buffer, 1);
|
|
|
|
std::array<std::uint8_t, 32> packed{};
|
|
for (std::size_t block = 0; block < 4; ++block) {
|
|
std::memcpy(packed.data() + block * 8, covered.data(), covered.size());
|
|
}
|
|
std::array<std::uint8_t, 8 * 8 * 4> expected{};
|
|
ASSERT_TRUE(DecodeEtcImage(EtcFormat::kEtc2Rgb8, packed.data(), packed.size(),
|
|
8, 8, expected.data(), expected.size()));
|
|
for (std::size_t index = 0; index < expected.size(); ++index) {
|
|
ASSERT_EQ(g_staging[index], expected[index]) << "byte " << index;
|
|
}
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
// Records one 4x4 ETC2 upload whose every texel decodes to 134, and returns
|
|
// the image it targets.
|
|
VkImage RecordDecodeOf134(VulkanEtc2Emulation* emulation, VkDevice device,
|
|
VkBuffer source, VkDeviceMemory source_memory,
|
|
VkCommandBuffer command_buffer) {
|
|
VkImageCreateInfo image_info{};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
|
|
image_info.extent = {4, 4, 1};
|
|
VkImage image = VK_NULL_HANDLE;
|
|
EXPECT_EQ(emulation->CreateImage(device, &image_info, nullptr, &image),
|
|
VK_SUCCESS);
|
|
EXPECT_EQ(emulation->BindBufferMemory(device, source, source_memory, 0),
|
|
VK_SUCCESS);
|
|
void* mapped = nullptr;
|
|
EXPECT_EQ(emulation->MapMemory(device, source_memory, 0, g_source.size(), 0,
|
|
&mapped),
|
|
VK_SUCCESS);
|
|
|
|
const std::array<std::uint8_t, 8> block = {0x81, 0x81, 0x81, 0x02,
|
|
0, 0, 0, 0};
|
|
std::memcpy(g_source.data(), block.data(), block.size());
|
|
|
|
VkBufferImageCopy region{};
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageExtent = {4, 4, 1};
|
|
emulation->CmdCopyBufferToImage(device, command_buffer, source, image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
|
|
®ion);
|
|
return image;
|
|
}
|
|
|
|
VkPhysicalDeviceMemoryProperties HostVisibleMemory() {
|
|
VkPhysicalDeviceMemoryProperties memory{};
|
|
memory.memoryTypeCount = 1;
|
|
memory.memoryTypes[0].propertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
return memory;
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, DecodesOffTheCallingThreadWithATimeline) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x18);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x208);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x708);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ResetSignalledValues();
|
|
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x28), true,
|
|
HostVisibleMemory(), FakeGetDeviceProcAddr, true);
|
|
RecordDecodeOf134(&emulation, device, source, source_memory, command_buffer);
|
|
|
|
const Etc2SubmitWait wait =
|
|
emulation.PrepareSubmit(&command_buffer, 1, true);
|
|
EXPECT_EQ(wait.semaphore, FakeHandle<VkSemaphore>(kTimelineSemaphore));
|
|
EXPECT_GT(wait.value, 0U);
|
|
|
|
// Releasing the command buffer waits for the decode that writes its staging.
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
EXPECT_EQ(g_staging[0], 134);
|
|
EXPECT_EQ(SignalledValues(), std::vector<std::uint64_t>{wait.value});
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, DecodesOnTheCallerWithoutATimeline) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x19);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x209);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x709);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ResetSignalledValues();
|
|
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x29), true,
|
|
HostVisibleMemory(), FakeGetDeviceProcAddr, false);
|
|
RecordDecodeOf134(&emulation, device, source, source_memory, command_buffer);
|
|
|
|
const Etc2SubmitWait wait =
|
|
emulation.PrepareSubmit(&command_buffer, 1, true);
|
|
EXPECT_EQ(wait.semaphore, VK_NULL_HANDLE);
|
|
EXPECT_EQ(wait.value, 0U);
|
|
// Decoded before the call returned, so no wait is owed.
|
|
EXPECT_EQ(g_staging[0], 134);
|
|
EXPECT_TRUE(SignalledValues().empty());
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, UnmapWaitsForDecodesReadingThatMemory) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x1C);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x20C);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x70D);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ResetSignalledValues();
|
|
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x2C), true,
|
|
HostVisibleMemory(), FakeGetDeviceProcAddr, true);
|
|
RecordDecodeOf134(&emulation, device, source, source_memory, command_buffer);
|
|
|
|
const Etc2SubmitWait wait =
|
|
emulation.PrepareSubmit(&command_buffer, 1, true);
|
|
ASSERT_NE(wait.semaphore, VK_NULL_HANDLE);
|
|
|
|
// A deferred gather reads the application's mapping on a worker, so the
|
|
// unmap must not return while a decode can still dereference it.
|
|
emulation.UnmapMemory(device, source_memory);
|
|
const std::vector<std::uint64_t> signalled = SignalledValues();
|
|
ASSERT_FALSE(signalled.empty()) << "unmap returned with a decode in flight";
|
|
EXPECT_GE(signalled.back(), wait.value);
|
|
EXPECT_EQ(g_staging[0], 134);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, FreeMemoryWaitsForDecodesReadingThatMemory) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x1D);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x20D);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x70E);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ResetSignalledValues();
|
|
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x2D), true,
|
|
HostVisibleMemory(), FakeGetDeviceProcAddr, true);
|
|
RecordDecodeOf134(&emulation, device, source, source_memory, command_buffer);
|
|
|
|
const Etc2SubmitWait wait =
|
|
emulation.PrepareSubmit(&command_buffer, 1, true);
|
|
ASSERT_NE(wait.semaphore, VK_NULL_HANDLE);
|
|
|
|
emulation.FreeMemory(device, source_memory, nullptr);
|
|
const std::vector<std::uint64_t> signalled = SignalledValues();
|
|
ASSERT_FALSE(signalled.empty()) << "free returned with a decode in flight";
|
|
EXPECT_GE(signalled.back(), wait.value);
|
|
EXPECT_EQ(g_staging[0], 134);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, GuardsStagedCopiesWithAHostWriteBarrier) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x1B);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x20B);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer command_buffer = FakeHandle<VkCommandBuffer>(0x70C);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
g_pending_barriers = 0;
|
|
g_barriers_before_copy = 0;
|
|
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x2B), true,
|
|
HostVisibleMemory(), FakeGetDeviceProcAddr, true);
|
|
RecordDecodeOf134(&emulation, device, source, source_memory, command_buffer);
|
|
|
|
// An asynchronous decode writes the staging after the submit that reads it,
|
|
// so the queue submission's host-to-device domain operation does not cover
|
|
// those writes; the copy must be preceded by an explicit barrier.
|
|
EXPECT_EQ(g_barriers_before_copy, 1U);
|
|
EXPECT_EQ(g_barrier_src_stage,
|
|
static_cast<VkPipelineStageFlags>(VK_PIPELINE_STAGE_HOST_BIT));
|
|
EXPECT_NE(g_barrier_dst_stage &
|
|
static_cast<VkPipelineStageFlags>(VK_PIPELINE_STAGE_TRANSFER_BIT),
|
|
0U);
|
|
EXPECT_EQ(g_barrier_src_access,
|
|
static_cast<VkAccessFlags>(VK_ACCESS_HOST_WRITE_BIT));
|
|
EXPECT_NE(g_barrier_dst_access &
|
|
static_cast<VkAccessFlags>(VK_ACCESS_TRANSFER_READ_BIT),
|
|
0U);
|
|
emulation.PrepareSubmit(&command_buffer, 1, true);
|
|
emulation.ReleaseCommandBuffer(command_buffer);
|
|
}
|
|
|
|
TEST(VulkanEtc2EmulationTest, SignalsDecodeValuesInHandOutOrder) {
|
|
const VkDevice device = FakeHandle<VkDevice>(0x1A);
|
|
const VkBuffer source = FakeHandle<VkBuffer>(0x20A);
|
|
const VkDeviceMemory source_memory = FakeHandle<VkDeviceMemory>(kSourceMemory);
|
|
const VkCommandBuffer first = FakeHandle<VkCommandBuffer>(0x70A);
|
|
const VkCommandBuffer second = FakeHandle<VkCommandBuffer>(0x70B);
|
|
g_source.fill(0);
|
|
g_staging.fill(0xAA);
|
|
ResetSignalledValues();
|
|
|
|
VulkanEtc2Emulation emulation;
|
|
emulation.RegisterDevice(device, FakeHandle<VkPhysicalDevice>(0x2A), true,
|
|
HostVisibleMemory(), FakeGetDeviceProcAddr, true);
|
|
RecordDecodeOf134(&emulation, device, source, source_memory, first);
|
|
RecordDecodeOf134(&emulation, device, source, source_memory, second);
|
|
|
|
const Etc2SubmitWait one = emulation.PrepareSubmit(&first, 1, true);
|
|
const Etc2SubmitWait two = emulation.PrepareSubmit(&second, 1, true);
|
|
EXPECT_GT(two.value, one.value);
|
|
|
|
emulation.ReleaseCommandBuffer(first);
|
|
emulation.ReleaseCommandBuffer(second);
|
|
|
|
const std::vector<std::uint64_t> signalled = SignalledValues();
|
|
ASSERT_FALSE(signalled.empty());
|
|
EXPECT_EQ(signalled.back(), two.value);
|
|
for (std::size_t index = 1; index < signalled.size(); ++index) {
|
|
EXPECT_GT(signalled[index], signalled[index - 1]) << "at " << index;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace mocktail::graphics
|