Merge branch 'master' of https://github.com/xenia-project/xenia into canary_experimental

This commit is contained in:
Gliniak
2022-07-04 08:04:31 +02:00
149 changed files with 27259 additions and 42442 deletions

View File

@@ -1,850 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/vulkan/buffer_cache.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
#include "xenia/base/profiling.h"
#include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/vulkan/vulkan_gpu_flags.h"
#include "xenia/ui/vulkan/vulkan_mem_alloc.h"
#include "xenia/ui/vulkan/vulkan_util.h"
using namespace xe::gpu::xenos;
namespace xe {
namespace gpu {
namespace vulkan {
#if XE_ARCH_AMD64
void copy_cmp_swap_16_unaligned(void* dest_ptr, const void* src_ptr,
uint16_t cmp_value, size_t count) {
auto dest = reinterpret_cast<uint16_t*>(dest_ptr);
auto src = reinterpret_cast<const uint16_t*>(src_ptr);
__m128i shufmask =
_mm_set_epi8(0x0E, 0x0F, 0x0C, 0x0D, 0x0A, 0x0B, 0x08, 0x09, 0x06, 0x07,
0x04, 0x05, 0x02, 0x03, 0x00, 0x01);
__m128i cmpval = _mm_set1_epi16(cmp_value);
size_t i;
for (i = 0; i + 8 <= count; i += 8) {
__m128i input = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&src[i]));
__m128i output = _mm_shuffle_epi8(input, shufmask);
__m128i mask = _mm_cmpeq_epi16(output, cmpval);
output = _mm_or_si128(output, mask);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
for (; i < count; ++i) { // handle residual elements
dest[i] = byte_swap(src[i]);
}
}
void copy_cmp_swap_32_unaligned(void* dest_ptr, const void* src_ptr,
uint32_t cmp_value, size_t count) {
auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
auto src = reinterpret_cast<const uint32_t*>(src_ptr);
__m128i shufmask =
_mm_set_epi8(0x0C, 0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x04, 0x05,
0x06, 0x07, 0x00, 0x01, 0x02, 0x03);
__m128i cmpval = _mm_set1_epi32(cmp_value);
size_t i;
for (i = 0; i + 4 <= count; i += 4) {
__m128i input = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&src[i]));
__m128i output = _mm_shuffle_epi8(input, shufmask);
__m128i mask = _mm_cmpeq_epi32(output, cmpval);
output = _mm_or_si128(output, mask);
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
}
for (; i < count; ++i) { // handle residual elements
dest[i] = byte_swap(src[i]);
}
}
#else
void copy_cmp_swap_16_unaligned(void* dest_ptr, const void* src_ptr,
uint16_t cmp_value, size_t count) {
auto dest = reinterpret_cast<uint16_t*>(dest_ptr);
auto src = reinterpret_cast<const uint16_t*>(src_ptr);
for (size_t i = 0; i < count; ++i) {
uint16_t value = byte_swap(src[i]);
dest[i] = value == cmp_value ? 0xFFFF : value;
}
}
void copy_cmp_swap_32_unaligned(void* dest_ptr, const void* src_ptr,
uint32_t cmp_value, size_t count) {
auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
auto src = reinterpret_cast<const uint32_t*>(src_ptr);
for (size_t i = 0; i < count; ++i) {
uint32_t value = byte_swap(src[i]);
dest[i] = value == cmp_value ? 0xFFFFFFFF : value;
}
}
#endif
using xe::ui::vulkan::util::CheckResult;
constexpr VkDeviceSize kConstantRegisterUniformRange =
512 * 4 * 4 + 8 * 4 + 32 * 4;
BufferCache::BufferCache(RegisterFile* register_file, Memory* memory,
const ui::vulkan::VulkanProvider& provider,
size_t capacity)
: register_file_(register_file), memory_(memory), provider_(provider) {
transient_buffer_ = std::make_unique<ui::vulkan::CircularBuffer>(
provider_,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
capacity, 256);
}
BufferCache::~BufferCache() { Shutdown(); }
VkResult BufferCache::Initialize() {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult status = VK_SUCCESS;
VkMemoryRequirements pool_reqs;
transient_buffer_->GetBufferMemoryRequirements(&pool_reqs);
VkMemoryAllocateInfo pool_allocate_info;
pool_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
pool_allocate_info.pNext = nullptr;
pool_allocate_info.allocationSize = pool_reqs.size;
pool_allocate_info.memoryTypeIndex = ui::vulkan::util::ChooseHostMemoryType(
provider_, pool_reqs.memoryTypeBits, false);
if (pool_allocate_info.memoryTypeIndex == UINT32_MAX) {
return VK_ERROR_INITIALIZATION_FAILED;
}
status = dfn.vkAllocateMemory(device, &pool_allocate_info, nullptr,
&gpu_memory_pool_);
if (status != VK_SUCCESS) {
return status;
}
status = transient_buffer_->Initialize(gpu_memory_pool_, 0);
if (status != VK_SUCCESS) {
return status;
}
// Create a memory allocator for textures.
VmaVulkanFunctions vulkan_funcs = {};
ui::vulkan::FillVMAVulkanFunctions(&vulkan_funcs, provider_);
VmaAllocatorCreateInfo alloc_info = {};
alloc_info.physicalDevice = provider_.physical_device();
alloc_info.device = device;
alloc_info.pVulkanFunctions = &vulkan_funcs;
alloc_info.instance = provider_.instance();
status = vmaCreateAllocator(&alloc_info, &mem_allocator_);
if (status != VK_SUCCESS) {
return status;
}
status = CreateConstantDescriptorSet();
if (status != VK_SUCCESS) {
return status;
}
status = CreateVertexDescriptorPool();
if (status != VK_SUCCESS) {
return status;
}
return VK_SUCCESS;
}
VkResult BufferCache::CreateVertexDescriptorPool() {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult status;
std::vector<VkDescriptorPoolSize> pool_sizes;
pool_sizes.push_back({
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
32 * 16384,
});
vertex_descriptor_pool_ = std::make_unique<ui::vulkan::DescriptorPool>(
provider_, 32 * 16384, pool_sizes);
// 32 storage buffers available to vertex shader.
// TODO(DrChat): In the future, this could hold memexport staging data.
VkDescriptorSetLayoutBinding binding = {
0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
32, VK_SHADER_STAGE_VERTEX_BIT,
nullptr,
};
VkDescriptorSetLayoutCreateInfo layout_info = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
nullptr,
0,
1,
&binding,
};
status = dfn.vkCreateDescriptorSetLayout(device, &layout_info, nullptr,
&vertex_descriptor_set_layout_);
if (status != VK_SUCCESS) {
return status;
}
return VK_SUCCESS;
}
void BufferCache::FreeVertexDescriptorPool() {
vertex_descriptor_pool_.reset();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout,
device, vertex_descriptor_set_layout_);
}
VkResult BufferCache::CreateConstantDescriptorSet() {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
VkResult status = VK_SUCCESS;
// Descriptor pool used for all of our cached descriptors.
// In the steady state we don't allocate anything, so these are all manually
// managed.
VkDescriptorPoolCreateInfo transient_descriptor_pool_info;
transient_descriptor_pool_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
transient_descriptor_pool_info.pNext = nullptr;
transient_descriptor_pool_info.flags =
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
transient_descriptor_pool_info.maxSets = 1;
VkDescriptorPoolSize pool_sizes[1];
pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
pool_sizes[0].descriptorCount = 2;
transient_descriptor_pool_info.poolSizeCount = 1;
transient_descriptor_pool_info.pPoolSizes = pool_sizes;
status = dfn.vkCreateDescriptorPool(device, &transient_descriptor_pool_info,
nullptr, &constant_descriptor_pool_);
if (status != VK_SUCCESS) {
return status;
}
// Create the descriptor set layout used for our uniform buffer.
// As it is a static binding that uses dynamic offsets during draws we can
// create this once and reuse it forever.
VkDescriptorSetLayoutBinding bindings[2] = {};
// Vertex constants
bindings[0].binding = 0;
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
bindings[0].descriptorCount = 1;
bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[0].pImmutableSamplers = nullptr;
// Fragment constants
bindings[1].binding = 1;
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
bindings[1].descriptorCount = 1;
bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[1].pImmutableSamplers = nullptr;
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_info = {};
descriptor_set_layout_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptor_set_layout_info.pNext = nullptr;
descriptor_set_layout_info.flags = 0;
descriptor_set_layout_info.bindingCount =
static_cast<uint32_t>(xe::countof(bindings));
descriptor_set_layout_info.pBindings = bindings;
status = dfn.vkCreateDescriptorSetLayout(device, &descriptor_set_layout_info,
nullptr,
&constant_descriptor_set_layout_);
if (status != VK_SUCCESS) {
return status;
}
// Create the descriptor we'll use for the uniform buffer.
// This is what we hand out to everyone (who then also needs to use our
// offsets).
VkDescriptorSetAllocateInfo set_alloc_info;
set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
set_alloc_info.pNext = nullptr;
set_alloc_info.descriptorPool = constant_descriptor_pool_;
set_alloc_info.descriptorSetCount = 1;
set_alloc_info.pSetLayouts = &constant_descriptor_set_layout_;
status = dfn.vkAllocateDescriptorSets(device, &set_alloc_info,
&constant_descriptor_set_);
if (status != VK_SUCCESS) {
return status;
}
// Initialize descriptor set with our buffers.
VkDescriptorBufferInfo buffer_info;
buffer_info.buffer = transient_buffer_->gpu_buffer();
buffer_info.offset = 0;
buffer_info.range = kConstantRegisterUniformRange;
VkWriteDescriptorSet descriptor_writes[2];
auto& vertex_uniform_binding_write = descriptor_writes[0];
vertex_uniform_binding_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
vertex_uniform_binding_write.pNext = nullptr;
vertex_uniform_binding_write.dstSet = constant_descriptor_set_;
vertex_uniform_binding_write.dstBinding = 0;
vertex_uniform_binding_write.dstArrayElement = 0;
vertex_uniform_binding_write.descriptorCount = 1;
vertex_uniform_binding_write.descriptorType =
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
vertex_uniform_binding_write.pBufferInfo = &buffer_info;
auto& fragment_uniform_binding_write = descriptor_writes[1];
fragment_uniform_binding_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
fragment_uniform_binding_write.pNext = nullptr;
fragment_uniform_binding_write.dstSet = constant_descriptor_set_;
fragment_uniform_binding_write.dstBinding = 1;
fragment_uniform_binding_write.dstArrayElement = 0;
fragment_uniform_binding_write.descriptorCount = 1;
fragment_uniform_binding_write.descriptorType =
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
fragment_uniform_binding_write.pBufferInfo = &buffer_info;
dfn.vkUpdateDescriptorSets(device, 2, descriptor_writes, 0, nullptr);
return VK_SUCCESS;
}
void BufferCache::FreeConstantDescriptorSet() {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
if (constant_descriptor_set_) {
dfn.vkFreeDescriptorSets(device, constant_descriptor_pool_, 1,
&constant_descriptor_set_);
constant_descriptor_set_ = nullptr;
}
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout,
device,
constant_descriptor_set_layout_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, device,
constant_descriptor_pool_);
}
void BufferCache::Shutdown() {
if (mem_allocator_) {
vmaDestroyAllocator(mem_allocator_);
mem_allocator_ = nullptr;
}
FreeConstantDescriptorSet();
FreeVertexDescriptorPool();
transient_buffer_->Shutdown();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
gpu_memory_pool_);
}
std::pair<VkDeviceSize, VkDeviceSize> BufferCache::UploadConstantRegisters(
VkCommandBuffer command_buffer,
const Shader::ConstantRegisterMap& vertex_constant_register_map,
const Shader::ConstantRegisterMap& pixel_constant_register_map,
VkFence fence) {
// Fat struct, including all registers:
// struct {
// vec4 float[512];
// uint bool[8];
// uint loop[32];
// };
auto offset = AllocateTransientData(kConstantRegisterUniformRange, fence);
if (offset == VK_WHOLE_SIZE) {
// OOM.
return {VK_WHOLE_SIZE, VK_WHOLE_SIZE};
}
// Copy over all the registers.
const auto& values = register_file_->values;
uint8_t* dest_ptr = transient_buffer_->host_base() + offset;
std::memcpy(dest_ptr, &values[XE_GPU_REG_SHADER_CONSTANT_000_X].f32,
(512 * 4 * 4));
dest_ptr += 512 * 4 * 4;
std::memcpy(dest_ptr, &values[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031].u32,
8 * 4);
dest_ptr += 8 * 4;
std::memcpy(dest_ptr, &values[XE_GPU_REG_SHADER_CONSTANT_LOOP_00].u32,
32 * 4);
dest_ptr += 32 * 4;
transient_buffer_->Flush(offset, kConstantRegisterUniformRange);
// Append a barrier to the command buffer.
VkBufferMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
VK_ACCESS_HOST_WRITE_BIT,
VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
transient_buffer_->gpu_buffer(),
offset,
kConstantRegisterUniformRange,
};
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 1,
&barrier, 0, nullptr);
return {offset, offset};
// Packed upload code.
// This is not currently supported by the shaders, but would be awesome.
// We should be able to use this for any shader that does not do dynamic
// constant indexing.
#if 0
// Allocate space in the buffer for our data.
auto offset =
AllocateTransientData(constant_register_map.packed_byte_length, fence);
if (offset == VK_WHOLE_SIZE) {
// OOM.
return VK_WHOLE_SIZE;
}
// Run through registers and copy them into the buffer.
// TODO(benvanik): optimize this - it's hit twice every call.
const auto& values = register_file_->values;
uint8_t* dest_ptr =
reinterpret_cast<uint8_t*>(transient_buffer_data_) + offset;
for (int i = 0; i < 4; ++i) {
auto piece = constant_register_map.float_bitmap[i];
if (!piece) {
continue;
}
for (int j = 0, sh = 0; j < 64; ++j, sh << 1) {
if (piece & sh) {
xe::copy_128_aligned(
dest_ptr,
&values[XE_GPU_REG_SHADER_CONSTANT_000_X + i * 64 + j].f32, 1);
dest_ptr += 16;
}
}
}
for (int i = 0; i < 32; ++i) {
if (constant_register_map.loop_bitmap & (1 << i)) {
xe::store<uint32_t>(dest_ptr,
values[XE_GPU_REG_SHADER_CONSTANT_LOOP_00 + i].u32);
dest_ptr += 4;
}
}
for (int i = 0; i < 8; ++i) {
if (constant_register_map.bool_bitmap[i]) {
xe::store<uint32_t>(
dest_ptr, values[XE_GPU_REG_SHADER_CONSTANT_BOOL_000_031 + i].u32);
dest_ptr += 4;
}
}
return offset;
#endif // 0
}
std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadIndexBuffer(
VkCommandBuffer command_buffer, uint32_t source_addr,
uint32_t source_length, xenos::IndexFormat format, VkFence fence) {
// Allocate space in the buffer for our data.
auto offset = AllocateTransientData(source_length, fence);
if (offset == VK_WHOLE_SIZE) {
// OOM.
return {nullptr, VK_WHOLE_SIZE};
}
const void* source_ptr = memory_->TranslatePhysical(source_addr);
uint32_t prim_reset_index =
register_file_->values[XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX].u32;
bool prim_reset_enabled =
!!(register_file_->values[XE_GPU_REG_PA_SU_SC_MODE_CNTL].u32 & (1 << 21));
// Copy data into the buffer. If primitive reset is enabled, translate any
// primitive reset indices to something Vulkan understands.
// TODO(benvanik): memcpy then use compute shaders to swap?
if (prim_reset_enabled) {
if (format == xenos::IndexFormat::kInt16) {
// Endian::k8in16, swap half-words.
copy_cmp_swap_16_unaligned(
transient_buffer_->host_base() + offset, source_ptr,
static_cast<uint16_t>(prim_reset_index), source_length / 2);
} else if (format == xenos::IndexFormat::kInt32) {
// Endian::k8in32, swap words.
copy_cmp_swap_32_unaligned(transient_buffer_->host_base() + offset,
source_ptr, prim_reset_index,
source_length / 4);
}
} else {
if (format == xenos::IndexFormat::kInt16) {
// Endian::k8in16, swap half-words.
xe::copy_and_swap_16_unaligned(transient_buffer_->host_base() + offset,
source_ptr, source_length / 2);
} else if (format == xenos::IndexFormat::kInt32) {
// Endian::k8in32, swap words.
xe::copy_and_swap_32_unaligned(transient_buffer_->host_base() + offset,
source_ptr, source_length / 4);
}
}
transient_buffer_->Flush(offset, source_length);
// Append a barrier to the command buffer.
VkBufferMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
VK_ACCESS_HOST_WRITE_BIT,
VK_ACCESS_INDEX_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
transient_buffer_->gpu_buffer(),
offset,
source_length,
};
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 0, nullptr, 1,
&barrier, 0, nullptr);
return {transient_buffer_->gpu_buffer(), offset};
}
std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadVertexBuffer(
VkCommandBuffer command_buffer, uint32_t source_addr,
uint32_t source_length, xenos::Endian endian, VkFence fence) {
auto offset = FindCachedTransientData(source_addr, source_length);
if (offset != VK_WHOLE_SIZE) {
return {transient_buffer_->gpu_buffer(), offset};
}
// Slow path :)
// Expand the region up to the allocation boundary
auto physical_heap = memory_->GetPhysicalHeap();
uint32_t upload_base = source_addr;
uint32_t upload_size = source_length;
// Ping the memory subsystem for allocation size.
// TODO(DrChat): Artifacting occurring in 5841089E with this enabled.
// physical_heap->QueryBaseAndSize(&upload_base, &upload_size);
assert(upload_base <= source_addr);
uint32_t source_offset = source_addr - upload_base;
// Allocate space in the buffer for our data.
offset = AllocateTransientData(upload_size, fence);
if (offset == VK_WHOLE_SIZE) {
// OOM.
XELOGW(
"Failed to allocate transient data for vertex buffer! Wanted to "
"allocate {} bytes.",
upload_size);
return {nullptr, VK_WHOLE_SIZE};
}
const void* upload_ptr = memory_->TranslatePhysical(upload_base);
// Copy data into the buffer.
// TODO(benvanik): memcpy then use compute shaders to swap?
if (endian == xenos::Endian::k8in32) {
// Endian::k8in32, swap words.
xe::copy_and_swap_32_unaligned(transient_buffer_->host_base() + offset,
upload_ptr, source_length / 4);
} else if (endian == xenos::Endian::k16in32) {
xe::copy_and_swap_16_in_32_unaligned(
transient_buffer_->host_base() + offset, upload_ptr, source_length / 4);
} else {
assert_always();
}
transient_buffer_->Flush(offset, upload_size);
// Append a barrier to the command buffer.
VkBufferMemoryBarrier barrier = {
VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
nullptr,
VK_ACCESS_HOST_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
transient_buffer_->gpu_buffer(),
offset,
upload_size,
};
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, 0, 0, nullptr,
1, &barrier, 0, nullptr);
CacheTransientData(upload_base, upload_size, offset);
return {transient_buffer_->gpu_buffer(), offset + source_offset};
}
void BufferCache::HashVertexBindings(
XXH3_state_t* hash_state,
const std::vector<Shader::VertexBinding>& vertex_bindings) {
auto& regs = *register_file_;
for (const auto& vertex_binding : vertex_bindings) {
#if 0
XXH3_64bits_update(hash_state, &vertex_binding.binding_index, sizeof(vertex_binding.binding_index));
XXH3_64bits_update(hash_state, &vertex_binding.fetch_constant, sizeof(vertex_binding.fetch_constant));
XXH3_64bits_update(hash_state, &vertex_binding.stride_words, sizeof(vertex_binding.stride_words));
#endif
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 +
(vertex_binding.fetch_constant / 3) * 6;
const auto group = reinterpret_cast<xe_gpu_fetch_group_t*>(&regs.values[r]);
switch (vertex_binding.fetch_constant % 3) {
case 0: {
auto& fetch = group->vertex_fetch_0;
XXH3_64bits_update(hash_state, &fetch, sizeof(fetch));
} break;
case 1: {
auto& fetch = group->vertex_fetch_1;
XXH3_64bits_update(hash_state, &fetch, sizeof(fetch));
} break;
case 2: {
auto& fetch = group->vertex_fetch_2;
XXH3_64bits_update(hash_state, &fetch, sizeof(fetch));
} break;
}
}
}
VkDescriptorSet BufferCache::PrepareVertexSet(
VkCommandBuffer command_buffer, VkFence fence,
const std::vector<Shader::VertexBinding>& vertex_bindings) {
// (quickly) Generate a hash.
XXH3_state_t hash_state;
XXH3_64bits_reset(&hash_state);
// (quickly) Generate a hash.
HashVertexBindings(&hash_state, vertex_bindings);
uint64_t hash = XXH3_64bits_digest(&hash_state);
for (auto it = vertex_sets_.find(hash); it != vertex_sets_.end(); ++it) {
// TODO(DrChat): We need to compare the bindings and ensure they're equal.
return it->second;
}
if (!vertex_descriptor_pool_->has_open_batch()) {
vertex_descriptor_pool_->BeginBatch(fence);
}
VkDescriptorSet set =
vertex_descriptor_pool_->AcquireEntry(vertex_descriptor_set_layout_);
if (!set) {
return nullptr;
}
// TODO(DrChat): Define magic number 32 as a constant somewhere.
VkDescriptorBufferInfo buffer_infos[32] = {};
VkWriteDescriptorSet descriptor_write = {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
set,
0,
0,
0,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
nullptr,
buffer_infos,
nullptr,
};
auto& regs = *register_file_;
for (const auto& vertex_binding : vertex_bindings) {
int r = XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 +
(vertex_binding.fetch_constant / 3) * 6;
const auto group = reinterpret_cast<xe_gpu_fetch_group_t*>(&regs.values[r]);
const xe_gpu_vertex_fetch_t* fetch = nullptr;
switch (vertex_binding.fetch_constant % 3) {
case 0:
fetch = &group->vertex_fetch_0;
break;
case 1:
fetch = &group->vertex_fetch_1;
break;
case 2:
fetch = &group->vertex_fetch_2;
break;
}
// TODO(DrChat): Some games use type kInvalidTexture (with no data).
switch (fetch->type) {
case xenos::FetchConstantType::kVertex:
break;
case xenos::FetchConstantType::kInvalidVertex:
if (cvars::gpu_allow_invalid_fetch_constants) {
break;
}
XELOGW(
"Vertex fetch constant {} ({:08X} {:08X}) has \"invalid\" type! "
"This "
"is incorrect behavior, but you can try bypassing this by "
"launching Xenia with --gpu_allow_invalid_fetch_constants=true.",
vertex_binding.fetch_constant, fetch->dword_0, fetch->dword_1);
return nullptr;
default:
XELOGW(
"Vertex fetch constant {} ({:08X} {:08X}) is completely invalid!",
vertex_binding.fetch_constant, fetch->dword_0, fetch->dword_1);
return nullptr;
}
// TODO(benvanik): compute based on indices or vertex count.
// THIS CAN BE MASSIVELY INCORRECT (too large).
// This may not be possible (with indexed vfetch).
uint32_t source_length = fetch->size * 4;
uint32_t physical_address = fetch->address << 2;
// TODO(DrChat): This needs to be put in gpu::CommandProcessor
// trace_writer_.WriteMemoryRead(physical_address, source_length);
// Upload (or get a cached copy of) the buffer.
auto buffer_ref = UploadVertexBuffer(command_buffer, physical_address,
source_length, fetch->endian, fence);
if (buffer_ref.second == VK_WHOLE_SIZE) {
// Failed to upload buffer.
XELOGW("Failed to upload vertex buffer!");
return nullptr;
}
// Stash the buffer reference for our bulk bind at the end.
buffer_infos[descriptor_write.descriptorCount++] = {
buffer_ref.first,
buffer_ref.second,
source_length,
};
}
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
dfn.vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, nullptr);
vertex_sets_[hash] = set;
return set;
}
VkDeviceSize BufferCache::AllocateTransientData(VkDeviceSize length,
VkFence fence) {
// Try fast path (if we have space).
VkDeviceSize offset = TryAllocateTransientData(length, fence);
if (offset != VK_WHOLE_SIZE) {
return offset;
}
// Ran out of easy allocations.
// Try consuming fences before we panic.
transient_buffer_->Scavenge();
// Try again. It may still fail if we didn't get enough space back.
offset = TryAllocateTransientData(length, fence);
return offset;
}
VkDeviceSize BufferCache::TryAllocateTransientData(VkDeviceSize length,
VkFence fence) {
auto alloc = transient_buffer_->Acquire(length, fence);
if (alloc) {
return alloc->offset;
}
// No more space.
return VK_WHOLE_SIZE;
}
VkDeviceSize BufferCache::FindCachedTransientData(uint32_t guest_address,
uint32_t guest_length) {
if (transient_cache_.empty()) {
// Short-circuit exit.
return VK_WHOLE_SIZE;
}
// Find the first element > guest_address
auto it = transient_cache_.upper_bound(guest_address);
if (it != transient_cache_.begin()) {
// it = first element <= guest_address
--it;
if ((it->first + it->second.first) >= (guest_address + guest_length)) {
// This data is contained within some existing transient data.
auto source_offset = static_cast<VkDeviceSize>(guest_address - it->first);
return it->second.second + source_offset;
}
}
return VK_WHOLE_SIZE;
}
void BufferCache::CacheTransientData(uint32_t guest_address,
uint32_t guest_length,
VkDeviceSize offset) {
transient_cache_[guest_address] = {guest_length, offset};
// Erase any entries contained within
auto it = transient_cache_.upper_bound(guest_address);
while (it != transient_cache_.end()) {
if ((guest_address + guest_length) >= (it->first + it->second.first)) {
it = transient_cache_.erase(it);
} else {
break;
}
}
}
void BufferCache::Flush(VkCommandBuffer command_buffer) {
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
VkDevice device = provider_.device();
// If we are flushing a big enough chunk queue up an event.
// We don't want to do this for everything but often enough so that we won't
// run out of space.
if (true) {
// VkEvent finish_event;
// dfn.vkCmdSetEvent(cmd_buffer, finish_event,
// VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
}
// Flush memory.
// TODO(benvanik): subrange.
VkMappedMemoryRange dirty_range;
dirty_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
dirty_range.pNext = nullptr;
dirty_range.memory = transient_buffer_->gpu_memory();
dirty_range.offset = 0;
dirty_range.size = transient_buffer_->capacity();
dfn.vkFlushMappedMemoryRanges(device, 1, &dirty_range);
}
void BufferCache::InvalidateCache() {
// Called by VulkanCommandProcessor::MakeCoherent()
// Discard everything?
transient_cache_.clear();
}
void BufferCache::ClearCache() { transient_cache_.clear(); }
void BufferCache::Scavenge() {
SCOPE_profile_cpu_f("gpu");
transient_cache_.clear();
transient_buffer_->Scavenge();
// TODO(DrChat): These could persist across frames, we just need a smart way
// to delete unused ones.
vertex_sets_.clear();
if (vertex_descriptor_pool_->has_open_batch()) {
vertex_descriptor_pool_->EndBatch();
}
vertex_descriptor_pool_->Scavenge();
}
} // namespace vulkan
} // namespace gpu
} // namespace xe

View File

@@ -1,175 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_BUFFER_CACHE_H_
#define XENIA_GPU_VULKAN_BUFFER_CACHE_H_
#include "xenia/base/xxhash.h"
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/shader.h"
#include "xenia/gpu/xenos.h"
#include "xenia/memory.h"
#include "xenia/ui/vulkan/circular_buffer.h"
#include "xenia/ui/vulkan/fenced_pools.h"
#include "xenia/ui/vulkan/vulkan_mem_alloc.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include <map>
#include <unordered_map>
namespace xe {
namespace gpu {
namespace vulkan {
// Efficiently manages buffers of various kinds.
// Used primarily for uploading index and vertex data from guest memory and
// transient data like shader constants.
class BufferCache {
public:
BufferCache(RegisterFile* register_file, Memory* memory,
const ui::vulkan::VulkanProvider& provider, size_t capacity);
~BufferCache();
VkResult Initialize();
void Shutdown();
// Descriptor set containing the dynamic uniform buffer used for constant
// uploads. Used in conjunction with a dynamic offset returned by
// UploadConstantRegisters.
// The set contains two bindings:
// binding = 0: for use in vertex shaders
// binding = 1: for use in fragment shaders
VkDescriptorSet constant_descriptor_set() const {
return constant_descriptor_set_;
}
VkDescriptorSetLayout constant_descriptor_set_layout() const {
return constant_descriptor_set_layout_;
}
// Descriptor set containing vertex buffers stored in storage buffers.
// This set contains one binding with an array of 32 storage buffers.
VkDescriptorSetLayout vertex_descriptor_set_layout() const {
return vertex_descriptor_set_layout_;
}
// Uploads the constants specified in the register maps to the transient
// uniform storage buffer.
// The registers are tightly packed in order as [floats, ints, bools].
// Returns an offset that can be used with the transient_descriptor_set or
// VK_WHOLE_SIZE if the constants could not be uploaded (OOM).
// The returned offsets may alias.
std::pair<VkDeviceSize, VkDeviceSize> UploadConstantRegisters(
VkCommandBuffer command_buffer,
const Shader::ConstantRegisterMap& vertex_constant_register_map,
const Shader::ConstantRegisterMap& pixel_constant_register_map,
VkFence fence);
// Uploads index buffer data from guest memory, possibly eliding with
// recently uploaded data or cached copies.
// Returns a buffer and offset that can be used with vkCmdBindIndexBuffer.
// Size will be VK_WHOLE_SIZE if the data could not be uploaded (OOM).
std::pair<VkBuffer, VkDeviceSize> UploadIndexBuffer(
VkCommandBuffer command_buffer, uint32_t source_addr,
uint32_t source_length, xenos::IndexFormat format, VkFence fence);
// Uploads vertex buffer data from guest memory, possibly eliding with
// recently uploaded data or cached copies.
// Returns a buffer and offset that can be used with vkCmdBindVertexBuffers.
// Size will be VK_WHOLE_SIZE if the data could not be uploaded (OOM).
std::pair<VkBuffer, VkDeviceSize> UploadVertexBuffer(
VkCommandBuffer command_buffer, uint32_t source_addr,
uint32_t source_length, xenos::Endian endian, VkFence fence);
// Prepares and returns a vertex descriptor set.
VkDescriptorSet PrepareVertexSet(
VkCommandBuffer setup_buffer, VkFence fence,
const std::vector<Shader::VertexBinding>& vertex_bindings);
// Flushes all pending data to the GPU.
// Until this is called the GPU is not guaranteed to see any data.
// The given command buffer will be used to queue up events so that the
// cache can determine when data has been consumed.
void Flush(VkCommandBuffer command_buffer);
// Marks the cache as potentially invalid.
// This is not as strong as ClearCache and is a hint that any and all data
// should be verified before being reused.
void InvalidateCache();
// Clears all cached content and prevents future elision with pending data.
void ClearCache();
// Wipes all data no longer needed.
void Scavenge();
private:
// This represents an uploaded vertex buffer.
struct VertexBuffer {
uint32_t guest_address;
uint32_t size;
VmaAllocation alloc;
VmaAllocationInfo alloc_info;
};
VkResult CreateVertexDescriptorPool();
void FreeVertexDescriptorPool();
VkResult CreateConstantDescriptorSet();
void FreeConstantDescriptorSet();
void HashVertexBindings(
XXH3_state_t* hash_state,
const std::vector<Shader::VertexBinding>& vertex_bindings);
// Allocates a block of memory in the transient buffer.
// When memory is not available fences are checked and space is reclaimed.
// Returns VK_WHOLE_SIZE if requested amount of memory is not available.
VkDeviceSize AllocateTransientData(VkDeviceSize length, VkFence fence);
// Tries to allocate a block of memory in the transient buffer.
// Returns VK_WHOLE_SIZE if requested amount of memory is not available.
VkDeviceSize TryAllocateTransientData(VkDeviceSize length, VkFence fence);
// Finds a block of data in the transient buffer sourced from the specified
// guest address and length.
VkDeviceSize FindCachedTransientData(uint32_t guest_address,
uint32_t guest_length);
// Adds a block of data to the frame cache.
void CacheTransientData(uint32_t guest_address, uint32_t guest_length,
VkDeviceSize offset);
RegisterFile* register_file_ = nullptr;
Memory* memory_ = nullptr;
const ui::vulkan::VulkanProvider& provider_;
VkDeviceMemory gpu_memory_pool_ = nullptr;
VmaAllocator mem_allocator_ = nullptr;
// Staging ringbuffer we cycle through fast. Used for data we don't
// plan on keeping past the current frame.
std::unique_ptr<ui::vulkan::CircularBuffer> transient_buffer_ = nullptr;
std::map<uint32_t, std::pair<uint32_t, VkDeviceSize>> transient_cache_;
// Vertex buffer descriptors
std::unique_ptr<ui::vulkan::DescriptorPool> vertex_descriptor_pool_ = nullptr;
VkDescriptorSetLayout vertex_descriptor_set_layout_ = nullptr;
// Current frame vertex sets.
std::unordered_map<uint64_t, VkDescriptorSet> vertex_sets_;
// Descriptor set used to hold vertex/pixel shader float constants
VkDescriptorPool constant_descriptor_pool_ = nullptr;
VkDescriptorSetLayout constant_descriptor_set_layout_ = nullptr;
VkDescriptorSet constant_descriptor_set_ = nullptr;
};
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_BUFFER_CACHE_H_

View File

@@ -0,0 +1,367 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/vulkan/deferred_command_buffer.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/base/profiling.h"
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
namespace xe {
namespace gpu {
namespace vulkan {
DeferredCommandBuffer::DeferredCommandBuffer(
const VulkanCommandProcessor& command_processor, size_t initial_size)
: command_processor_(command_processor) {
command_stream_.reserve(initial_size / sizeof(uintmax_t));
}
void DeferredCommandBuffer::Reset() { command_stream_.clear(); }
void DeferredCommandBuffer::Execute(VkCommandBuffer command_buffer) {
#if XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES
SCOPE_profile_cpu_f("gpu");
#endif // XE_UI_VULKAN_FINE_GRAINED_DRAW_SCOPES
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn =
command_processor_.GetVulkanProvider().dfn();
const uintmax_t* stream = command_stream_.data();
size_t stream_remaining = command_stream_.size();
while (stream_remaining) {
const CommandHeader& header =
*reinterpret_cast<const CommandHeader*>(stream);
stream += kCommandHeaderSizeElements;
stream_remaining -= kCommandHeaderSizeElements;
switch (header.command) {
case Command::kVkBeginRenderPass: {
auto& args = *reinterpret_cast<const ArgsVkBeginRenderPass*>(stream);
size_t offset_bytes = sizeof(ArgsVkBeginRenderPass);
VkRenderPassBeginInfo render_pass_begin_info;
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin_info.pNext = nullptr;
render_pass_begin_info.renderPass = args.render_pass;
render_pass_begin_info.framebuffer = args.framebuffer;
render_pass_begin_info.renderArea = args.render_area;
render_pass_begin_info.clearValueCount = args.clear_value_count;
if (render_pass_begin_info.clearValueCount) {
offset_bytes = xe::align(offset_bytes, alignof(VkClearValue));
render_pass_begin_info.pClearValues =
reinterpret_cast<const VkClearValue*>(
reinterpret_cast<const uint8_t*>(stream) + offset_bytes);
offset_bytes +=
sizeof(VkClearValue) * render_pass_begin_info.clearValueCount;
} else {
render_pass_begin_info.pClearValues = nullptr;
}
dfn.vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info,
args.contents);
} break;
case Command::kVkBindDescriptorSets: {
auto& args = *reinterpret_cast<const ArgsVkBindDescriptorSets*>(stream);
size_t offset_bytes = xe::align(sizeof(ArgsVkBindDescriptorSets),
alignof(VkDescriptorSet));
const VkDescriptorSet* descriptor_sets =
reinterpret_cast<const VkDescriptorSet*>(
reinterpret_cast<const uint8_t*>(stream) + offset_bytes);
offset_bytes += sizeof(VkDescriptorSet) * args.descriptor_set_count;
const uint32_t* dynamic_offsets = nullptr;
if (args.dynamic_offset_count) {
offset_bytes = xe::align(offset_bytes, alignof(uint32_t));
dynamic_offsets = reinterpret_cast<const uint32_t*>(
reinterpret_cast<const uint8_t*>(stream) + offset_bytes);
offset_bytes += sizeof(uint32_t) * args.dynamic_offset_count;
}
dfn.vkCmdBindDescriptorSets(command_buffer, args.pipeline_bind_point,
args.layout, args.first_set,
args.descriptor_set_count, descriptor_sets,
args.dynamic_offset_count, dynamic_offsets);
} break;
case Command::kVkBindIndexBuffer: {
auto& args = *reinterpret_cast<const ArgsVkBindIndexBuffer*>(stream);
dfn.vkCmdBindIndexBuffer(command_buffer, args.buffer, args.offset,
args.index_type);
} break;
case Command::kVkBindPipeline: {
auto& args = *reinterpret_cast<const ArgsVkBindPipeline*>(stream);
dfn.vkCmdBindPipeline(command_buffer, args.pipeline_bind_point,
args.pipeline);
} break;
case Command::kVkBindVertexBuffers: {
auto& args = *reinterpret_cast<const ArgsVkBindVertexBuffers*>(stream);
size_t offset_bytes =
xe::align(sizeof(ArgsVkBindVertexBuffers), alignof(VkBuffer));
const VkBuffer* buffers = reinterpret_cast<const VkBuffer*>(
reinterpret_cast<const uint8_t*>(stream) + offset_bytes);
offset_bytes =
xe::align(offset_bytes + sizeof(VkBuffer) * args.binding_count,
alignof(VkDeviceSize));
const VkDeviceSize* offsets = reinterpret_cast<const VkDeviceSize*>(
reinterpret_cast<const uint8_t*>(stream) + offset_bytes);
dfn.vkCmdBindVertexBuffers(command_buffer, args.first_binding,
args.binding_count, buffers, offsets);
} break;
case Command::kVkClearAttachments: {
auto& args = *reinterpret_cast<const ArgsVkClearAttachments*>(stream);
size_t offset_bytes = xe::align(sizeof(ArgsVkClearAttachments),
alignof(VkClearAttachment));
const VkClearAttachment* attachments =
reinterpret_cast<const VkClearAttachment*>(
reinterpret_cast<const uint8_t*>(stream) + offset_bytes);
offset_bytes = xe::align(
offset_bytes + sizeof(VkClearAttachment) * args.attachment_count,
alignof(VkClearRect));
const VkClearRect* rects = reinterpret_cast<const VkClearRect*>(
reinterpret_cast<const uint8_t*>(stream) + offset_bytes);
dfn.vkCmdClearAttachments(command_buffer, args.attachment_count,
attachments, args.rect_count, rects);
} break;
case Command::kVkClearColorImage: {
auto& args = *reinterpret_cast<const ArgsVkClearColorImage*>(stream);
dfn.vkCmdClearColorImage(
command_buffer, args.image, args.image_layout, &args.color,
args.range_count,
reinterpret_cast<const VkImageSubresourceRange*>(
reinterpret_cast<const uint8_t*>(stream) +
xe::align(sizeof(ArgsVkClearColorImage),
alignof(VkImageSubresourceRange))));
} break;
case Command::kVkCopyBuffer: {
auto& args = *reinterpret_cast<const ArgsVkCopyBuffer*>(stream);
dfn.vkCmdCopyBuffer(
command_buffer, args.src_buffer, args.dst_buffer, args.region_count,
reinterpret_cast<const VkBufferCopy*>(
reinterpret_cast<const uint8_t*>(stream) +
xe::align(sizeof(ArgsVkCopyBuffer), alignof(VkBufferCopy))));
} break;
case Command::kVkCopyBufferToImage: {
auto& args = *reinterpret_cast<const ArgsVkCopyBufferToImage*>(stream);
dfn.vkCmdCopyBufferToImage(
command_buffer, args.src_buffer, args.dst_image,
args.dst_image_layout, args.region_count,
reinterpret_cast<const VkBufferImageCopy*>(
reinterpret_cast<const uint8_t*>(stream) +
xe::align(sizeof(ArgsVkCopyBufferToImage),
alignof(VkBufferImageCopy))));
} break;
case Command::kVkDispatch: {
auto& args = *reinterpret_cast<const ArgsVkDispatch*>(stream);
dfn.vkCmdDispatch(command_buffer, args.group_count_x,
args.group_count_y, args.group_count_z);
} break;
case Command::kVkDraw: {
auto& args = *reinterpret_cast<const ArgsVkDraw*>(stream);
dfn.vkCmdDraw(command_buffer, args.vertex_count, args.instance_count,
args.first_vertex, args.first_instance);
} break;
case Command::kVkDrawIndexed: {
auto& args = *reinterpret_cast<const ArgsVkDrawIndexed*>(stream);
dfn.vkCmdDrawIndexed(command_buffer, args.index_count,
args.instance_count, args.first_index,
args.vertex_offset, args.first_instance);
} break;
case Command::kVkEndRenderPass:
dfn.vkCmdEndRenderPass(command_buffer);
break;
case Command::kVkPipelineBarrier: {
auto& args = *reinterpret_cast<const ArgsVkPipelineBarrier*>(stream);
size_t barrier_offset_bytes = sizeof(ArgsVkPipelineBarrier);
const VkMemoryBarrier* memory_barriers = nullptr;
if (args.memory_barrier_count) {
barrier_offset_bytes =
xe::align(barrier_offset_bytes, alignof(VkMemoryBarrier));
memory_barriers = reinterpret_cast<const VkMemoryBarrier*>(
reinterpret_cast<const uint8_t*>(stream) + barrier_offset_bytes);
barrier_offset_bytes +=
sizeof(VkMemoryBarrier) * args.memory_barrier_count;
}
const VkBufferMemoryBarrier* buffer_memory_barriers = nullptr;
if (args.buffer_memory_barrier_count) {
barrier_offset_bytes =
xe::align(barrier_offset_bytes, alignof(VkBufferMemoryBarrier));
buffer_memory_barriers =
reinterpret_cast<const VkBufferMemoryBarrier*>(
reinterpret_cast<const uint8_t*>(stream) +
barrier_offset_bytes);
barrier_offset_bytes +=
sizeof(VkBufferMemoryBarrier) * args.buffer_memory_barrier_count;
}
const VkImageMemoryBarrier* image_memory_barriers = nullptr;
if (args.image_memory_barrier_count) {
barrier_offset_bytes =
xe::align(barrier_offset_bytes, alignof(VkImageMemoryBarrier));
image_memory_barriers = reinterpret_cast<const VkImageMemoryBarrier*>(
reinterpret_cast<const uint8_t*>(stream) + barrier_offset_bytes);
barrier_offset_bytes +=
sizeof(VkImageMemoryBarrier) * args.image_memory_barrier_count;
}
dfn.vkCmdPipelineBarrier(
command_buffer, args.src_stage_mask, args.dst_stage_mask,
args.dependency_flags, args.memory_barrier_count, memory_barriers,
args.buffer_memory_barrier_count, buffer_memory_barriers,
args.image_memory_barrier_count, image_memory_barriers);
} break;
case Command::kVkPushConstants: {
auto& args = *reinterpret_cast<const ArgsVkPushConstants*>(stream);
dfn.vkCmdPushConstants(command_buffer, args.layout, args.stage_flags,
args.offset, args.size,
reinterpret_cast<const uint8_t*>(stream) +
sizeof(ArgsVkPushConstants));
} break;
case Command::kVkSetBlendConstants: {
auto& args = *reinterpret_cast<const ArgsVkSetBlendConstants*>(stream);
dfn.vkCmdSetBlendConstants(command_buffer, args.blend_constants);
} break;
case Command::kVkSetDepthBias: {
auto& args = *reinterpret_cast<const ArgsVkSetDepthBias*>(stream);
dfn.vkCmdSetDepthBias(command_buffer, args.depth_bias_constant_factor,
args.depth_bias_clamp,
args.depth_bias_slope_factor);
} break;
case Command::kVkSetScissor: {
auto& args = *reinterpret_cast<const ArgsVkSetScissor*>(stream);
dfn.vkCmdSetScissor(
command_buffer, args.first_scissor, args.scissor_count,
reinterpret_cast<const VkRect2D*>(
reinterpret_cast<const uint8_t*>(stream) +
xe::align(sizeof(ArgsVkSetScissor), alignof(VkRect2D))));
} break;
case Command::kVkSetStencilCompareMask: {
auto& args =
*reinterpret_cast<const ArgsSetStencilMaskReference*>(stream);
dfn.vkCmdSetStencilCompareMask(command_buffer, args.face_mask,
args.mask_reference);
} break;
case Command::kVkSetStencilReference: {
auto& args =
*reinterpret_cast<const ArgsSetStencilMaskReference*>(stream);
dfn.vkCmdSetStencilReference(command_buffer, args.face_mask,
args.mask_reference);
} break;
case Command::kVkSetStencilWriteMask: {
auto& args =
*reinterpret_cast<const ArgsSetStencilMaskReference*>(stream);
dfn.vkCmdSetStencilWriteMask(command_buffer, args.face_mask,
args.mask_reference);
} break;
case Command::kVkSetViewport: {
auto& args = *reinterpret_cast<const ArgsVkSetViewport*>(stream);
dfn.vkCmdSetViewport(
command_buffer, args.first_viewport, args.viewport_count,
reinterpret_cast<const VkViewport*>(
reinterpret_cast<const uint8_t*>(stream) +
xe::align(sizeof(ArgsVkSetViewport), alignof(VkViewport))));
} break;
default:
assert_unhandled_case(header.command);
break;
}
stream += header.arguments_size_elements;
stream_remaining -= header.arguments_size_elements;
}
}
void DeferredCommandBuffer::CmdVkPipelineBarrier(
VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
VkDependencyFlags dependency_flags, uint32_t memory_barrier_count,
const VkMemoryBarrier* memory_barriers,
uint32_t buffer_memory_barrier_count,
const VkBufferMemoryBarrier* buffer_memory_barriers,
uint32_t image_memory_barrier_count,
const VkImageMemoryBarrier* image_memory_barriers) {
size_t arguments_size = sizeof(ArgsVkPipelineBarrier);
size_t memory_barriers_offset = 0;
if (memory_barrier_count) {
arguments_size = xe::align(arguments_size, alignof(VkMemoryBarrier));
memory_barriers_offset = arguments_size;
arguments_size += sizeof(VkMemoryBarrier) * memory_barrier_count;
}
size_t buffer_memory_barriers_offset = 0;
if (buffer_memory_barrier_count) {
arguments_size = xe::align(arguments_size, alignof(VkBufferMemoryBarrier));
buffer_memory_barriers_offset = arguments_size;
arguments_size +=
sizeof(VkBufferMemoryBarrier) * buffer_memory_barrier_count;
}
size_t image_memory_barriers_offset = 0;
if (image_memory_barrier_count) {
arguments_size = xe::align(arguments_size, alignof(VkImageMemoryBarrier));
image_memory_barriers_offset = arguments_size;
arguments_size += sizeof(VkImageMemoryBarrier) * image_memory_barrier_count;
}
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkPipelineBarrier, arguments_size));
auto& args = *reinterpret_cast<ArgsVkPipelineBarrier*>(args_ptr);
args.src_stage_mask = src_stage_mask;
args.dst_stage_mask = dst_stage_mask;
args.dependency_flags = dependency_flags;
args.memory_barrier_count = memory_barrier_count;
args.buffer_memory_barrier_count = buffer_memory_barrier_count;
args.image_memory_barrier_count = image_memory_barrier_count;
if (memory_barrier_count) {
std::memcpy(args_ptr + memory_barriers_offset, memory_barriers,
sizeof(VkMemoryBarrier) * memory_barrier_count);
}
if (buffer_memory_barrier_count) {
std::memcpy(args_ptr + buffer_memory_barriers_offset,
buffer_memory_barriers,
sizeof(VkBufferMemoryBarrier) * buffer_memory_barrier_count);
}
if (image_memory_barrier_count) {
std::memcpy(args_ptr + image_memory_barriers_offset, image_memory_barriers,
sizeof(VkImageMemoryBarrier) * image_memory_barrier_count);
}
}
void* DeferredCommandBuffer::WriteCommand(Command command,
size_t arguments_size_bytes) {
size_t arguments_size_elements =
(arguments_size_bytes + sizeof(uintmax_t) - 1) / sizeof(uintmax_t);
size_t offset = command_stream_.size();
command_stream_.resize(offset + kCommandHeaderSizeElements +
arguments_size_elements);
CommandHeader& header =
*reinterpret_cast<CommandHeader*>(command_stream_.data() + offset);
header.command = command;
header.arguments_size_elements = uint32_t(arguments_size_elements);
return command_stream_.data() + (offset + kCommandHeaderSizeElements);
}
} // namespace vulkan
} // namespace gpu
} // namespace xe

View File

@@ -0,0 +1,550 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_DEFERRED_COMMAND_BUFFER_H_
#define XENIA_GPU_VULKAN_DEFERRED_COMMAND_BUFFER_H_
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace gpu {
namespace vulkan {
class VulkanCommandProcessor;
class DeferredCommandBuffer {
public:
DeferredCommandBuffer(const VulkanCommandProcessor& command_processor,
size_t initial_size_bytes = 1024 * 1024);
void Reset();
void Execute(VkCommandBuffer command_buffer);
// render_pass_begin->pNext of all barriers must be null.
void CmdVkBeginRenderPass(const VkRenderPassBeginInfo* render_pass_begin,
VkSubpassContents contents) {
assert_null(render_pass_begin->pNext);
size_t arguments_size = sizeof(ArgsVkBeginRenderPass);
uint32_t clear_value_count = render_pass_begin->clearValueCount;
size_t clear_values_offset = 0;
if (clear_value_count) {
arguments_size = xe::align(arguments_size, alignof(VkClearValue));
clear_values_offset = arguments_size;
arguments_size += sizeof(VkClearValue) * clear_value_count;
}
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkBeginRenderPass, arguments_size));
auto& args = *reinterpret_cast<ArgsVkBeginRenderPass*>(args_ptr);
args.render_pass = render_pass_begin->renderPass;
args.framebuffer = render_pass_begin->framebuffer;
args.render_area = render_pass_begin->renderArea;
args.clear_value_count = clear_value_count;
args.contents = contents;
if (clear_value_count) {
std::memcpy(args_ptr + clear_values_offset,
render_pass_begin->pClearValues,
sizeof(VkClearValue) * clear_value_count);
}
}
void CmdVkBindDescriptorSets(VkPipelineBindPoint pipeline_bind_point,
VkPipelineLayout layout, uint32_t first_set,
uint32_t descriptor_set_count,
const VkDescriptorSet* descriptor_sets,
uint32_t dynamic_offset_count,
const uint32_t* dynamic_offsets) {
size_t arguments_size =
xe::align(sizeof(ArgsVkBindDescriptorSets), alignof(VkDescriptorSet));
size_t descriptor_sets_offset = arguments_size;
arguments_size += sizeof(VkDescriptorSet) * descriptor_set_count;
size_t dynamic_offsets_offset = 0;
if (dynamic_offset_count) {
arguments_size = xe::align(arguments_size, alignof(uint32_t));
dynamic_offsets_offset = arguments_size;
arguments_size += sizeof(uint32_t) * dynamic_offset_count;
}
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkBindDescriptorSets, arguments_size));
auto& args = *reinterpret_cast<ArgsVkBindDescriptorSets*>(args_ptr);
args.pipeline_bind_point = pipeline_bind_point;
args.layout = layout;
args.first_set = first_set;
args.descriptor_set_count = descriptor_set_count;
args.dynamic_offset_count = dynamic_offset_count;
std::memcpy(args_ptr + descriptor_sets_offset, descriptor_sets,
sizeof(VkDescriptorSet) * descriptor_set_count);
if (dynamic_offset_count) {
std::memcpy(args_ptr + dynamic_offsets_offset, dynamic_offsets,
sizeof(uint32_t) * dynamic_offset_count);
}
}
void CmdVkBindIndexBuffer(VkBuffer buffer, VkDeviceSize offset,
VkIndexType index_type) {
auto& args = *reinterpret_cast<ArgsVkBindIndexBuffer*>(WriteCommand(
Command::kVkBindIndexBuffer, sizeof(ArgsVkBindIndexBuffer)));
args.buffer = buffer;
args.offset = offset;
args.index_type = index_type;
}
void CmdVkBindPipeline(VkPipelineBindPoint pipeline_bind_point,
VkPipeline pipeline) {
auto& args = *reinterpret_cast<ArgsVkBindPipeline*>(
WriteCommand(Command::kVkBindPipeline, sizeof(ArgsVkBindPipeline)));
args.pipeline_bind_point = pipeline_bind_point;
args.pipeline = pipeline;
}
void CmdVkBindVertexBuffers(uint32_t first_binding, uint32_t binding_count,
const VkBuffer* buffers,
const VkDeviceSize* offsets) {
size_t arguments_size =
xe::align(sizeof(ArgsVkBindVertexBuffers), alignof(VkBuffer));
size_t buffers_offset = arguments_size;
arguments_size =
xe::align(arguments_size + sizeof(VkBuffer) * binding_count,
alignof(VkDeviceSize));
size_t offsets_offset = arguments_size;
arguments_size += sizeof(VkDeviceSize) * binding_count;
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkBindVertexBuffers, arguments_size));
auto& args = *reinterpret_cast<ArgsVkBindVertexBuffers*>(args_ptr);
args.first_binding = first_binding;
args.binding_count = binding_count;
std::memcpy(args_ptr + buffers_offset, buffers,
sizeof(VkBuffer) * binding_count);
std::memcpy(args_ptr + offsets_offset, offsets,
sizeof(VkDeviceSize) * binding_count);
}
void CmdClearAttachmentsEmplace(uint32_t attachment_count,
VkClearAttachment*& attachments_out,
uint32_t rect_count,
VkClearRect*& rects_out) {
size_t arguments_size =
xe::align(sizeof(ArgsVkClearAttachments), alignof(VkClearAttachment));
size_t attachments_offset = arguments_size;
arguments_size =
xe::align(arguments_size + sizeof(VkClearAttachment) * attachment_count,
alignof(VkClearRect));
size_t rects_offset = arguments_size;
arguments_size += sizeof(VkClearRect) * rect_count;
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkClearAttachments, arguments_size));
auto& args = *reinterpret_cast<ArgsVkClearAttachments*>(args_ptr);
args.attachment_count = attachment_count;
args.rect_count = rect_count;
attachments_out =
reinterpret_cast<VkClearAttachment*>(args_ptr + attachments_offset);
rects_out = reinterpret_cast<VkClearRect*>(args_ptr + rects_offset);
}
void CmdVkClearAttachments(uint32_t attachment_count,
const VkClearAttachment* attachments,
uint32_t rect_count, const VkClearRect* rects) {
VkClearAttachment* attachments_arg;
VkClearRect* rects_arg;
CmdClearAttachmentsEmplace(attachment_count, attachments_arg, rect_count,
rects_arg);
std::memcpy(attachments_arg, attachments,
sizeof(VkClearAttachment) * attachment_count);
std::memcpy(rects_arg, rects, sizeof(VkClearRect) * rect_count);
}
VkImageSubresourceRange* CmdClearColorImageEmplace(
VkImage image, VkImageLayout image_layout, const VkClearColorValue* color,
uint32_t range_count) {
const size_t header_size = xe::align(sizeof(ArgsVkClearColorImage),
alignof(VkImageSubresourceRange));
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(WriteCommand(
Command::kVkClearColorImage,
header_size + sizeof(VkImageSubresourceRange) * range_count));
auto& args = *reinterpret_cast<ArgsVkClearColorImage*>(args_ptr);
args.image = image;
args.image_layout = image_layout;
args.color = *color;
args.range_count = range_count;
return reinterpret_cast<VkImageSubresourceRange*>(args_ptr + header_size);
}
void CmdVkClearColorImage(VkImage image, VkImageLayout image_layout,
const VkClearColorValue* color,
uint32_t range_count,
const VkImageSubresourceRange* ranges) {
std::memcpy(
CmdClearColorImageEmplace(image, image_layout, color, range_count),
ranges, sizeof(VkImageSubresourceRange) * range_count);
}
VkBufferCopy* CmdCopyBufferEmplace(VkBuffer src_buffer, VkBuffer dst_buffer,
uint32_t region_count) {
const size_t header_size =
xe::align(sizeof(ArgsVkCopyBuffer), alignof(VkBufferCopy));
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkCopyBuffer,
header_size + sizeof(VkBufferCopy) * region_count));
auto& args = *reinterpret_cast<ArgsVkCopyBuffer*>(args_ptr);
args.src_buffer = src_buffer;
args.dst_buffer = dst_buffer;
args.region_count = region_count;
return reinterpret_cast<VkBufferCopy*>(args_ptr + header_size);
}
void CmdVkCopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
uint32_t region_count, const VkBufferCopy* regions) {
std::memcpy(CmdCopyBufferEmplace(src_buffer, dst_buffer, region_count),
regions, sizeof(VkBufferCopy) * region_count);
}
VkBufferImageCopy* CmdCopyBufferToImageEmplace(VkBuffer src_buffer,
VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count) {
const size_t header_size =
xe::align(sizeof(ArgsVkCopyBufferToImage), alignof(VkBufferImageCopy));
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkCopyBufferToImage,
header_size + sizeof(VkBufferImageCopy) * region_count));
auto& args = *reinterpret_cast<ArgsVkCopyBufferToImage*>(args_ptr);
args.src_buffer = src_buffer;
args.dst_image = dst_image;
args.dst_image_layout = dst_image_layout;
args.region_count = region_count;
return reinterpret_cast<VkBufferImageCopy*>(args_ptr + header_size);
}
void CmdVkCopyBufferToImage(VkBuffer src_buffer, VkImage dst_image,
VkImageLayout dst_image_layout,
uint32_t region_count,
const VkBufferImageCopy* regions) {
std::memcpy(CmdCopyBufferToImageEmplace(src_buffer, dst_image,
dst_image_layout, region_count),
regions, sizeof(VkBufferImageCopy) * region_count);
}
void CmdVkDispatch(uint32_t group_count_x, uint32_t group_count_y,
uint32_t group_count_z) {
auto& args = *reinterpret_cast<ArgsVkDispatch*>(
WriteCommand(Command::kVkDispatch, sizeof(ArgsVkDispatch)));
args.group_count_x = group_count_x;
args.group_count_y = group_count_y;
args.group_count_z = group_count_z;
}
void CmdVkDraw(uint32_t vertex_count, uint32_t instance_count,
uint32_t first_vertex, uint32_t first_instance) {
auto& args = *reinterpret_cast<ArgsVkDraw*>(
WriteCommand(Command::kVkDraw, sizeof(ArgsVkDraw)));
args.vertex_count = vertex_count;
args.instance_count = instance_count;
args.first_vertex = first_vertex;
args.first_instance = first_instance;
}
void CmdVkDrawIndexed(uint32_t index_count, uint32_t instance_count,
uint32_t first_index, int32_t vertex_offset,
uint32_t first_instance) {
auto& args = *reinterpret_cast<ArgsVkDrawIndexed*>(
WriteCommand(Command::kVkDrawIndexed, sizeof(ArgsVkDrawIndexed)));
args.index_count = index_count;
args.instance_count = instance_count;
args.first_index = first_index;
args.vertex_offset = vertex_offset;
args.first_instance = first_instance;
}
void CmdVkEndRenderPass() { WriteCommand(Command::kVkEndRenderPass, 0); }
// pNext of all barriers must be null.
void CmdVkPipelineBarrier(VkPipelineStageFlags src_stage_mask,
VkPipelineStageFlags dst_stage_mask,
VkDependencyFlags dependency_flags,
uint32_t memory_barrier_count,
const VkMemoryBarrier* memory_barriers,
uint32_t buffer_memory_barrier_count,
const VkBufferMemoryBarrier* buffer_memory_barriers,
uint32_t image_memory_barrier_count,
const VkImageMemoryBarrier* image_memory_barriers);
void CmdVkPushConstants(VkPipelineLayout layout,
VkShaderStageFlags stage_flags, uint32_t offset,
uint32_t size, const void* values) {
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(WriteCommand(
Command::kVkPushConstants, sizeof(ArgsVkPushConstants) + size));
auto& args = *reinterpret_cast<ArgsVkPushConstants*>(args_ptr);
args.layout = layout;
args.stage_flags = stage_flags;
args.offset = offset;
args.size = size;
std::memcpy(args_ptr + sizeof(ArgsVkPushConstants), values, size);
}
void CmdVkSetBlendConstants(const float* blend_constants) {
auto& args = *reinterpret_cast<ArgsVkSetBlendConstants*>(WriteCommand(
Command::kVkSetBlendConstants, sizeof(ArgsVkSetBlendConstants)));
std::memcpy(args.blend_constants, blend_constants, sizeof(float) * 4);
}
void CmdVkSetDepthBias(float depth_bias_constant_factor,
float depth_bias_clamp,
float depth_bias_slope_factor) {
auto& args = *reinterpret_cast<ArgsVkSetDepthBias*>(
WriteCommand(Command::kVkSetDepthBias, sizeof(ArgsVkSetDepthBias)));
args.depth_bias_constant_factor = depth_bias_constant_factor;
args.depth_bias_clamp = depth_bias_clamp;
args.depth_bias_slope_factor = depth_bias_slope_factor;
}
void CmdVkSetScissor(uint32_t first_scissor, uint32_t scissor_count,
const VkRect2D* scissors) {
const size_t header_size =
xe::align(sizeof(ArgsVkSetScissor), alignof(VkRect2D));
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkSetScissor,
header_size + sizeof(VkRect2D) * scissor_count));
auto& args = *reinterpret_cast<ArgsVkSetScissor*>(args_ptr);
args.first_scissor = first_scissor;
args.scissor_count = scissor_count;
std::memcpy(args_ptr + header_size, scissors,
sizeof(VkRect2D) * scissor_count);
}
void CmdVkSetStencilCompareMask(VkStencilFaceFlags face_mask,
uint32_t compare_mask) {
auto& args = *reinterpret_cast<ArgsSetStencilMaskReference*>(
WriteCommand(Command::kVkSetStencilCompareMask,
sizeof(ArgsSetStencilMaskReference)));
args.face_mask = face_mask;
args.mask_reference = compare_mask;
}
void CmdVkSetStencilReference(VkStencilFaceFlags face_mask,
uint32_t reference) {
auto& args = *reinterpret_cast<ArgsSetStencilMaskReference*>(WriteCommand(
Command::kVkSetStencilReference, sizeof(ArgsSetStencilMaskReference)));
args.face_mask = face_mask;
args.mask_reference = reference;
}
void CmdVkSetStencilWriteMask(VkStencilFaceFlags face_mask,
uint32_t write_mask) {
auto& args = *reinterpret_cast<ArgsSetStencilMaskReference*>(WriteCommand(
Command::kVkSetStencilWriteMask, sizeof(ArgsSetStencilMaskReference)));
args.face_mask = face_mask;
args.mask_reference = write_mask;
}
void CmdVkSetViewport(uint32_t first_viewport, uint32_t viewport_count,
const VkViewport* viewports) {
const size_t header_size =
xe::align(sizeof(ArgsVkSetViewport), alignof(VkViewport));
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
WriteCommand(Command::kVkSetViewport,
header_size + sizeof(VkViewport) * viewport_count));
auto& args = *reinterpret_cast<ArgsVkSetViewport*>(args_ptr);
args.first_viewport = first_viewport;
args.viewport_count = viewport_count;
std::memcpy(args_ptr + header_size, viewports,
sizeof(VkViewport) * viewport_count);
}
private:
enum class Command {
kVkBeginRenderPass,
kVkBindDescriptorSets,
kVkBindIndexBuffer,
kVkBindPipeline,
kVkBindVertexBuffers,
kVkClearAttachments,
kVkClearColorImage,
kVkCopyBuffer,
kVkCopyBufferToImage,
kVkDispatch,
kVkDraw,
kVkDrawIndexed,
kVkEndRenderPass,
kVkPipelineBarrier,
kVkPushConstants,
kVkSetBlendConstants,
kVkSetDepthBias,
kVkSetScissor,
kVkSetStencilCompareMask,
kVkSetStencilReference,
kVkSetStencilWriteMask,
kVkSetViewport,
};
struct CommandHeader {
Command command;
uint32_t arguments_size_elements;
};
static constexpr size_t kCommandHeaderSizeElements =
(sizeof(CommandHeader) + sizeof(uintmax_t) - 1) / sizeof(uintmax_t);
struct ArgsVkBeginRenderPass {
VkRenderPass render_pass;
VkFramebuffer framebuffer;
VkRect2D render_area;
uint32_t clear_value_count;
VkSubpassContents contents;
// Followed by aligned optional VkClearValue[].
static_assert(alignof(VkClearValue) <= alignof(uintmax_t));
};
struct ArgsVkBindDescriptorSets {
VkPipelineBindPoint pipeline_bind_point;
VkPipelineLayout layout;
uint32_t first_set;
uint32_t descriptor_set_count;
uint32_t dynamic_offset_count;
// Followed by aligned VkDescriptorSet[], optional uint32_t[].
static_assert(alignof(VkDescriptorSet) <= alignof(uintmax_t));
};
struct ArgsVkBindIndexBuffer {
VkBuffer buffer;
VkDeviceSize offset;
VkIndexType index_type;
};
struct ArgsVkBindPipeline {
VkPipelineBindPoint pipeline_bind_point;
VkPipeline pipeline;
};
struct ArgsVkBindVertexBuffers {
uint32_t first_binding;
uint32_t binding_count;
// Followed by aligned VkBuffer[], VkDeviceSize[].
static_assert(alignof(VkBuffer) <= alignof(uintmax_t));
static_assert(alignof(VkDeviceSize) <= alignof(uintmax_t));
};
struct ArgsVkClearAttachments {
uint32_t attachment_count;
uint32_t rect_count;
// Followed by aligned VkClearAttachment[], VkClearRect[].
static_assert(alignof(VkClearAttachment) <= alignof(uintmax_t));
static_assert(alignof(VkClearRect) <= alignof(uintmax_t));
};
struct ArgsVkClearColorImage {
VkImage image;
VkImageLayout image_layout;
VkClearColorValue color;
uint32_t range_count;
// Followed by aligned VkImageSubresourceRange[].
static_assert(alignof(VkImageSubresourceRange) <= alignof(uintmax_t));
};
struct ArgsVkCopyBuffer {
VkBuffer src_buffer;
VkBuffer dst_buffer;
uint32_t region_count;
// Followed by aligned VkBufferCopy[].
static_assert(alignof(VkBufferCopy) <= alignof(uintmax_t));
};
struct ArgsVkCopyBufferToImage {
VkBuffer src_buffer;
VkImage dst_image;
VkImageLayout dst_image_layout;
uint32_t region_count;
// Followed by aligned VkBufferImageCopy[].
static_assert(alignof(VkBufferImageCopy) <= alignof(uintmax_t));
};
struct ArgsVkDispatch {
uint32_t group_count_x;
uint32_t group_count_y;
uint32_t group_count_z;
};
struct ArgsVkDraw {
uint32_t vertex_count;
uint32_t instance_count;
uint32_t first_vertex;
uint32_t first_instance;
};
struct ArgsVkDrawIndexed {
uint32_t index_count;
uint32_t instance_count;
uint32_t first_index;
int32_t vertex_offset;
uint32_t first_instance;
};
struct ArgsVkPipelineBarrier {
VkPipelineStageFlags src_stage_mask;
VkPipelineStageFlags dst_stage_mask;
VkDependencyFlags dependency_flags;
uint32_t memory_barrier_count;
uint32_t buffer_memory_barrier_count;
uint32_t image_memory_barrier_count;
// Followed by aligned optional VkMemoryBarrier[],
// optional VkBufferMemoryBarrier[], optional VkImageMemoryBarrier[].
static_assert(alignof(VkMemoryBarrier) <= alignof(uintmax_t));
static_assert(alignof(VkBufferMemoryBarrier) <= alignof(uintmax_t));
static_assert(alignof(VkImageMemoryBarrier) <= alignof(uintmax_t));
};
struct ArgsVkPushConstants {
VkPipelineLayout layout;
VkShaderStageFlags stage_flags;
uint32_t offset;
uint32_t size;
// Followed by `size` bytes of values.
};
struct ArgsVkSetBlendConstants {
float blend_constants[4];
};
struct ArgsVkSetDepthBias {
float depth_bias_constant_factor;
float depth_bias_clamp;
float depth_bias_slope_factor;
};
struct ArgsVkSetScissor {
uint32_t first_scissor;
uint32_t scissor_count;
// Followed by aligned VkRect2D[].
static_assert(alignof(VkRect2D) <= alignof(uintmax_t));
};
struct ArgsSetStencilMaskReference {
VkStencilFaceFlags face_mask;
uint32_t mask_reference;
};
struct ArgsVkSetViewport {
uint32_t first_viewport;
uint32_t viewport_count;
// Followed by aligned VkViewport[].
static_assert(alignof(VkViewport) <= alignof(uintmax_t));
};
void* WriteCommand(Command command, size_t arguments_size_bytes);
const VulkanCommandProcessor& command_processor_;
// uintmax_t to ensure uint64_t and pointer alignment of all structures.
std::vector<uintmax_t> command_stream_;
};
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_DEFERRED_COMMAND_BUFFER_H_

View File

@@ -8,10 +8,10 @@ project("xenia-gpu-vulkan")
language("C++")
links({
"fmt",
"glslang-spirv",
"xenia-base",
"xenia-gpu",
"xenia-ui",
"xenia-ui-spirv",
"xenia-ui-vulkan",
"xxhash",
})
@@ -20,10 +20,9 @@ project("xenia-gpu-vulkan")
})
local_platform_files()
files({
"shaders/bytecode/vulkan_spirv/*.h",
"../shaders/bytecode/vulkan_spirv/*.h",
})
-- TODO(benvanik): kill this and move to the debugger UI.
group("src")
project("xenia-gpu-vulkan-trace-viewer")
uuid("86a1dddc-a26a-4885-8c55-cf745225d93e")
@@ -43,7 +42,6 @@ project("xenia-gpu-vulkan-trace-viewer")
"xenia-kernel",
"xenia-patcher",
"xenia-ui",
"xenia-ui-spirv",
"xenia-ui-vulkan",
"xenia-vfs",
"xenia-patcher",
@@ -58,7 +56,6 @@ project("xenia-gpu-vulkan-trace-viewer")
"libavutil",
"mspack",
"snappy",
"spirv-tools",
"xxhash",
})
includedirs({
@@ -77,12 +74,6 @@ project("xenia-gpu-vulkan-trace-viewer")
})
filter("platforms:Windows")
links({
"xenia-apu-xaudio2",
"xenia-hid-winkey",
"xenia-hid-xinput",
})
-- Only create the .user file if it doesn't already exist.
local user_file = project_root.."/build/xenia-gpu-vulkan-trace-viewer.vcxproj.user"
if not os.isfile(user_file) then
@@ -111,7 +102,6 @@ project("xenia-gpu-vulkan-trace-dump")
"xenia-hid-nop",
"xenia-kernel",
"xenia-ui",
"xenia-ui-spirv",
"xenia-ui-vulkan",
"xenia-vfs",
"xenia-patcher",
@@ -126,7 +116,6 @@ project("xenia-gpu-vulkan-trace-dump")
"libavutil",
"mspack",
"snappy",
"spirv-tools",
"xxhash",
})
includedirs({

File diff suppressed because it is too large Load Diff

View File

@@ -1,406 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_RENDER_CACHE_H_
#define XENIA_GPU_VULKAN_RENDER_CACHE_H_
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/registers.h"
#include "xenia/gpu/shader.h"
#include "xenia/gpu/texture_info.h"
#include "xenia/gpu/vulkan/vulkan_shader.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace gpu {
namespace vulkan {
// TODO(benvanik): make public API?
class CachedTileView;
class CachedFramebuffer;
class CachedRenderPass;
// Uniquely identifies EDRAM tiles.
struct TileViewKey {
// Offset into EDRAM in 5120b tiles.
uint16_t tile_offset;
// Tile width of the view in base 80x16 tiles.
uint16_t tile_width;
// Tile height of the view in base 80x16 tiles.
uint16_t tile_height;
// 1 if format is ColorRenderTargetFormat, else DepthRenderTargetFormat.
uint16_t color_or_depth : 1;
// Surface MSAA samples
uint16_t msaa_samples : 2;
// Either ColorRenderTargetFormat or DepthRenderTargetFormat.
uint16_t edram_format : 13;
};
static_assert(sizeof(TileViewKey) == 8, "Key must be tightly packed");
// Cached view representing EDRAM memory.
// TODO(benvanik): reuse VkImage's with multiple VkViews for compatible
// formats?
class CachedTileView {
public:
// Key identifying the view in the cache.
TileViewKey key;
// Image
VkImage image = nullptr;
// Simple view on the image matching the format.
VkImageView image_view = nullptr;
// Image layout
VkImageLayout image_layout = VK_IMAGE_LAYOUT_UNDEFINED;
// Memory buffer
VkDeviceMemory memory = nullptr;
// Image sample count
VkSampleCountFlagBits sample_count = VK_SAMPLE_COUNT_1_BIT;
// (if a depth view) Image view of depth aspect
VkImageView image_view_depth = nullptr;
// (if a depth view) Image view of stencil aspect
VkImageView image_view_stencil = nullptr;
CachedTileView(const ui::vulkan::VulkanProvider& provider,
VkDeviceMemory edram_memory, TileViewKey view_key);
~CachedTileView();
VkResult Initialize(VkCommandBuffer command_buffer);
bool IsEqual(const TileViewKey& other_key) const {
auto a = reinterpret_cast<const uint64_t*>(&key);
auto b = reinterpret_cast<const uint64_t*>(&other_key);
return *a == *b;
}
bool operator<(const CachedTileView& other) const {
return key.tile_offset < other.key.tile_offset;
}
VkExtent2D GetSize() const {
return {key.tile_width * 80u, key.tile_height * 16u};
}
private:
const ui::vulkan::VulkanProvider& provider_;
};
// Parsed render configuration from the current render state.
struct RenderConfiguration {
// Render mode (color+depth, depth-only, etc).
xenos::ModeControl mode_control;
// Target surface pitch multiplied by MSAA, in pixels.
uint32_t surface_pitch_px;
// ESTIMATED target surface height multiplied by MSAA, in pixels.
uint32_t surface_height_px;
// Surface MSAA setting.
xenos::MsaaSamples surface_msaa;
// Color attachments for the 4 render targets.
struct {
bool used;
uint32_t edram_base;
xenos::ColorRenderTargetFormat format;
} color[4];
// Depth/stencil attachment.
struct {
bool used;
uint32_t edram_base;
xenos::DepthRenderTargetFormat format;
} depth_stencil;
};
// Current render state based on the register-specified configuration.
struct RenderState {
// Parsed configuration.
RenderConfiguration config;
// Render pass (to be used with pipelines/etc).
CachedRenderPass* render_pass = nullptr;
VkRenderPass render_pass_handle = nullptr;
// Target framebuffer bound to the render pass.
CachedFramebuffer* framebuffer = nullptr;
VkFramebuffer framebuffer_handle = nullptr;
bool color_attachment_written[4] = {false};
bool depth_attachment_written = false;
};
// Manages the virtualized EDRAM and the render target cache.
//
// On the 360 the render target is an opaque block of memory in EDRAM that's
// only accessible via resolves. We use this to our advantage to simulate
// something like it as best we can by having a shared backing memory with
// a multitude of views for each tile location in EDRAM.
//
// This allows us to have the same base address write to the same memory
// regardless of framebuffer format. Resolving then uses whatever format the
// resolve requests straight from the backing memory.
//
// EDRAM is a beast and we only approximate it as best we can. Basically,
// the 10MiB of EDRAM is composed of 2048 5120b tiles. Each tile is 80x16px.
// +-----+-----+-----+---
// |tile0|tile1|tile2|... 2048 times
// +-----+-----+-----+---
// Operations dealing with EDRAM deal in tile offsets, so base 0x100 is tile
// offset 256, 256*5120=1310720b into the buffer. All rendering operations are
// aligned to tiles so trying to draw at 256px wide will have a real width of
// 320px by rounding up to the next tile.
//
// MSAA and other settings will modify the exact pixel sizes, like 4X makes
// each tile effectively 40x8px / 2X makes each tile 80x8px, but they are still
// all 5120b. As we try to emulate this we adjust our viewport when rendering to
// stretch pixels as needed.
//
// It appears that games also take advantage of MSAA stretching tiles when doing
// clears. Games will clear a view with 1/2X pitch/height and 4X MSAA and then
// later draw to that view with 1X pitch/height and 1X MSAA.
//
// The good news is that games cannot read EDRAM directly but must use a copy
// operation to get the data out. That gives us a chance to do whatever we
// need to (re-tile, etc) only when requested.
//
// To approximate the tiled EDRAM layout we use a single large chunk of memory.
// From this memory we create many VkImages (and VkImageViews) of various
// formats and dimensions as requested by the game. These are used as
// attachments during rendering and as sources during copies. They are also
// heavily aliased - lots of images will reference the same locations in the
// underlying EDRAM buffer. The only requirement is that there are no hazards
// with specific tiles (reading/writing the same tile through different images)
// and otherwise it should be ok *fingers crossed*.
//
// One complication is the copy/resolve process itself: we need to give back
// the data asked for in the format desired and where it goes is arbitrary
// (any address in physical memory). If the game is good we get resolves of
// EDRAM into fixed base addresses with scissored regions. If the game is bad
// we are broken.
//
// Resolves from EDRAM result in tiled textures - that's texture tiles, not
// EDRAM tiles. If we wanted to ensure byte-for-byte correctness we'd need to
// then tile the images as we wrote them out. For now, we just attempt to
// get the (X, Y) in linear space and do that. This really comes into play
// when multiple resolves write to the same texture or memory aliased by
// multiple textures - which is common due to predicated tiling. The examples
// below demonstrate what this looks like, but the important thing is that
// we are aware of partial textures and overlapping regions.
//
// TODO(benvanik): what, if any, barriers do we need? any transitions?
//
// Example with multiple render targets:
// Two color targets of 256x256px tightly packed in EDRAM:
// color target 0: base 0x0, pitch 320, scissor 0,0, 256x256
// starts at tile 0, buffer offset 0
// contains 64 tiles (320/80)*(256/16)
// color target 1: base 0x40, pitch 320, scissor 256,0, 256x256
// starts at tile 64 (after color target 0), buffer offset 327680b
// contains 64 tiles
// In EDRAM each set of 64 tiles is contiguous:
// +------+------+ +------+------+------+
// |ct0.0 |ct0.1 |...|ct0.63|ct1.0 |ct1.1 |...
// +------+------+ +------+------+------+
// To render into these, we setup two VkImages:
// image 0: bound to buffer offset 0, 320x256x4=327680b
// image 1: bound to buffer offset 327680b, 320x256x4=327680b
// So when we render to them:
// +------+-+ scissored to 256x256, actually 320x256
// | . | | <- . appears at some untiled offset in the buffer, but
// | | | consistent if aliased with the same format
// +------+-+
// In theory, this gives us proper aliasing in most cases.
//
// Example with horizontal predicated tiling:
// Trying to render 1024x576 @4X MSAA, splitting into two regions
// horizontally:
// +----------+
// | 1024x288 |
// +----------+
// | 1024x288 |
// +----------+
// EDRAM configured for 1056x288px with tile size 2112x567px (4X MSAA):
// color target 0: base 0x0, pitch 1080, 26x36 tiles
// First render (top):
// window offset 0,0
// scissor 0,0, 1024x288
// First resolve (top):
// RB_COPY_DEST_BASE 0x1F45D000
// RB_COPY_DEST_PITCH pitch=1024, height=576
// vertices: 0,0, 1024,0, 1024,288
// Second render (bottom):
// window offset 0,-288
// scissor 0,288, 1024x288
// Second resolve (bottom):
// RB_COPY_DEST_BASE 0x1F57D000 (+1179648b)
// RB_COPY_DEST_PITCH pitch=1024, height=576
// (exactly 1024x288*4b after first resolve)
// vertices: 0,288, 1024,288, 1024,576
// Resolving here is easy as the textures are contiguous in memory. We can
// snoop in the first resolve with the dest height to know the total size,
// and in the second resolve see that it overlaps and place it in the
// existing target.
//
// Example with vertical predicated tiling:
// Trying to render 1280x720 @2X MSAA, splitting into two regions
// vertically:
// +-----+-----+
// | 640 | 640 |
// | x | x |
// | 720 | 720 |
// +-----+-----+
// EDRAM configured for 640x736px with tile size 640x1472px (2X MSAA):
// color target 0: base 0x0, pitch 640, 8x92 tiles
// First render (left):
// window offset 0,0
// scissor 0,0, 640x720
// First resolve (left):
// RB_COPY_DEST_BASE 0x1BC6D000
// RB_COPY_DEST_PITCH pitch=1280, height=720
// vertices: 0,0, 640,0, 640,720
// Second render (right):
// window offset -640,0
// scissor 640,0, 640x720
// Second resolve (right):
// RB_COPY_DEST_BASE 0x1BC81000 (+81920b)
// RB_COPY_DEST_PITCH pitch=1280, height=720
// vertices: 640,0, 1280,0, 1280,720
// Resolving here is much more difficult as resolves are tiled and the right
// half of the texture is 81920b away:
// 81920/4bpp=20480px, /32 (texture tile size)=640px
// We know the texture size with the first resolve and with the second we
// must check for overlap then compute the offset (in both X and Y).
class RenderCache {
public:
RenderCache(RegisterFile* register_file,
const ui::vulkan::VulkanProvider& provider);
~RenderCache();
VkResult Initialize();
void Shutdown();
// Call this to determine if you should start a new render pass or continue
// with an already open pass.
bool dirty() const;
CachedTileView* FindTileView(uint32_t base, uint32_t pitch,
xenos::MsaaSamples samples, bool color_or_depth,
uint32_t format);
// Begins a render pass targeting the state-specified framebuffer formats.
// The command buffer will be transitioned into the render pass phase.
const RenderState* BeginRenderPass(VkCommandBuffer command_buffer,
VulkanShader* vertex_shader,
VulkanShader* pixel_shader);
// Ends the current render pass.
// The command buffer will be transitioned out of the render pass phase.
void EndRenderPass();
// Clears all cached content.
void ClearCache();
// Queues commands to copy EDRAM contents into an image.
// The command buffer must not be inside of a render pass when calling this.
void RawCopyToImage(VkCommandBuffer command_buffer, uint32_t edram_base,
VkImage image, VkImageLayout image_layout,
bool color_or_depth, VkOffset3D offset,
VkExtent3D extents);
// Queues commands to blit EDRAM contents into an image.
// The command buffer must not be inside of a render pass when calling this.
void BlitToImage(VkCommandBuffer command_buffer, uint32_t edram_base,
uint32_t pitch, uint32_t height,
xenos::MsaaSamples num_samples, VkImage image,
VkImageLayout image_layout, bool color_or_depth,
uint32_t format, VkFilter filter, VkOffset3D offset,
VkExtent3D extents);
// Queues commands to clear EDRAM contents with a solid color.
// The command buffer must not be inside of a render pass when calling this.
void ClearEDRAMColor(VkCommandBuffer command_buffer, uint32_t edram_base,
xenos::ColorRenderTargetFormat format, uint32_t pitch,
uint32_t height, xenos::MsaaSamples num_samples,
float* color);
// Queues commands to clear EDRAM contents with depth/stencil values.
// The command buffer must not be inside of a render pass when calling this.
void ClearEDRAMDepthStencil(VkCommandBuffer command_buffer,
uint32_t edram_base,
xenos::DepthRenderTargetFormat format,
uint32_t pitch, uint32_t height,
xenos::MsaaSamples num_samples, float depth,
uint32_t stencil);
// Queues commands to fill EDRAM contents with a constant value.
// The command buffer must not be inside of a render pass when calling this.
void FillEDRAM(VkCommandBuffer command_buffer, uint32_t value);
private:
// Parses the current state into a configuration object.
bool ParseConfiguration(RenderConfiguration* config);
// Finds a tile view. Returns nullptr if none found matching the key.
CachedTileView* FindTileView(const TileViewKey& view_key) const;
// Gets or creates a tile view with the given parameters.
CachedTileView* FindOrCreateTileView(VkCommandBuffer command_buffer,
const TileViewKey& view_key);
void UpdateTileView(VkCommandBuffer command_buffer, CachedTileView* view,
bool load, bool insert_barrier = true);
// Gets or creates a render pass and frame buffer for the given configuration.
// This attempts to reuse as much as possible across render passes and
// framebuffers.
bool ConfigureRenderPass(VkCommandBuffer command_buffer,
RenderConfiguration* config,
CachedRenderPass** out_render_pass,
CachedFramebuffer** out_framebuffer);
RegisterFile* register_file_ = nullptr;
const ui::vulkan::VulkanProvider& provider_;
// Entire 10MiB of EDRAM.
VkDeviceMemory edram_memory_ = nullptr;
// Buffer overlayed 1:1 with edram_memory_ to allow raw access.
VkBuffer edram_buffer_ = nullptr;
// Cache of VkImage and VkImageView's for all of our EDRAM tilings.
// TODO(benvanik): non-linear lookup? Should only be a small number of these.
std::vector<CachedTileView*> cached_tile_views_;
// Cache of render passes based on formats.
std::vector<CachedRenderPass*> cached_render_passes_;
// Shadows of the registers that impact the render pass we choose.
// If the registers don't change between passes we can quickly reuse the
// previous one.
struct ShadowRegisters {
reg::RB_MODECONTROL rb_modecontrol;
reg::RB_SURFACE_INFO rb_surface_info;
reg::RB_COLOR_INFO rb_color_info;
reg::RB_COLOR_INFO rb_color1_info;
reg::RB_COLOR_INFO rb_color2_info;
reg::RB_COLOR_INFO rb_color3_info;
reg::RB_DEPTH_INFO rb_depth_info;
uint32_t pa_sc_window_scissor_tl;
uint32_t pa_sc_window_scissor_br;
ShadowRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} shadow_registers_;
bool SetShadowRegister(uint32_t* dest, uint32_t register_name);
// Configuration used for the current/previous Begin/End, representing the
// current shadow register state.
RenderState current_state_;
// Only valid during a BeginRenderPass/EndRenderPass block.
VkCommandBuffer current_command_buffer_ = nullptr;
};
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_RENDER_CACHE_H_

View File

@@ -1,2 +0,0 @@
DisableFormat: true
SortIncludes: false

View File

@@ -1,52 +0,0 @@
// Generated with `xb buildshaders`.
#if 0
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 16104
; Schema: 0
OpCapability Shader
OpCapability Sampled1D
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %5663 "main" %3302 %4841
OpExecutionMode %5663 OriginUpperLeft
OpDecorate %3302 Location 0
OpDecorate %4841 Location 0
%void = OpTypeVoid
%1282 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%uint = OpTypeInt 32 0
%uint_16 = OpConstant %uint 16
%_arr_v4float_uint_16 = OpTypeArray %v4float %uint_16
%_ptr_Input__arr_v4float_uint_16 = OpTypePointer Input %_arr_v4float_uint_16
%3302 = OpVariable %_ptr_Input__arr_v4float_uint_16 Input
%uint_4 = OpConstant %uint 4
%_arr_v4float_uint_4 = OpTypeArray %v4float %uint_4
%_ptr_Output__arr_v4float_uint_4 = OpTypePointer Output %_arr_v4float_uint_4
%4841 = OpVariable %_ptr_Output__arr_v4float_uint_4 Output
%5663 = OpFunction %void None %1282
%16103 = OpLabel
OpReturn
OpFunctionEnd
#endif
const uint32_t dummy_ps[] = {
0x07230203, 0x00010000, 0x0008000A, 0x00003EE8, 0x00000000, 0x00020011,
0x00000001, 0x00020011, 0x0000002B, 0x0006000B, 0x00000001, 0x4C534C47,
0x6474732E, 0x3035342E, 0x00000000, 0x0003000E, 0x00000000, 0x00000001,
0x0007000F, 0x00000004, 0x0000161F, 0x6E69616D, 0x00000000, 0x00000CE6,
0x000012E9, 0x00030010, 0x0000161F, 0x00000007, 0x00040047, 0x00000CE6,
0x0000001E, 0x00000000, 0x00040047, 0x000012E9, 0x0000001E, 0x00000000,
0x00020013, 0x00000008, 0x00030021, 0x00000502, 0x00000008, 0x00030016,
0x0000000D, 0x00000020, 0x00040017, 0x0000001D, 0x0000000D, 0x00000004,
0x00040015, 0x0000000B, 0x00000020, 0x00000000, 0x0004002B, 0x0000000B,
0x00000A3A, 0x00000010, 0x0004001C, 0x0000056F, 0x0000001D, 0x00000A3A,
0x00040020, 0x000007EC, 0x00000001, 0x0000056F, 0x0004003B, 0x000007EC,
0x00000CE6, 0x00000001, 0x0004002B, 0x0000000B, 0x00000A16, 0x00000004,
0x0004001C, 0x000005C3, 0x0000001D, 0x00000A16, 0x00040020, 0x00000840,
0x00000003, 0x000005C3, 0x0004003B, 0x00000840, 0x000012E9, 0x00000003,
0x00050036, 0x00000008, 0x0000161F, 0x00000000, 0x00000502, 0x000200F8,
0x00003EE7, 0x000100FD, 0x00010038,
};

View File

@@ -1,193 +0,0 @@
// Generated with `xb buildshaders`.
#if 0
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 23916
; Schema: 0
OpCapability Geometry
OpCapability GeometryPointSize
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %5663 "main" %4930 %5305 %5430 %3302 %4044 %4656 %3736
OpExecutionMode %5663 InputLinesAdjacency
OpExecutionMode %5663 Invocations 1
OpExecutionMode %5663 OutputLineStrip
OpExecutionMode %5663 OutputVertices 5
OpMemberDecorate %_struct_1032 0 BuiltIn Position
OpMemberDecorate %_struct_1032 1 BuiltIn PointSize
OpDecorate %_struct_1032 Block
OpMemberDecorate %_struct_1033 0 BuiltIn Position
OpMemberDecorate %_struct_1033 1 BuiltIn PointSize
OpDecorate %_struct_1033 Block
OpDecorate %5430 Location 0
OpDecorate %3302 Location 0
OpDecorate %4044 Location 16
OpDecorate %4656 Location 17
OpDecorate %3736 Location 16
%void = OpTypeVoid
%1282 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_struct_1032 = OpTypeStruct %v4float %float
%_ptr_Output__struct_1032 = OpTypePointer Output %_struct_1032
%4930 = OpVariable %_ptr_Output__struct_1032 Output
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_struct_1033 = OpTypeStruct %v4float %float
%uint = OpTypeInt 32 0
%uint_4 = OpConstant %uint 4
%_arr__struct_1033_uint_4 = OpTypeArray %_struct_1033 %uint_4
%_ptr_Input__arr__struct_1033_uint_4 = OpTypePointer Input %_arr__struct_1033_uint_4
%5305 = OpVariable %_ptr_Input__arr__struct_1033_uint_4 Input
%_ptr_Input_v4float = OpTypePointer Input %v4float
%_ptr_Output_v4float = OpTypePointer Output %v4float
%int_1 = OpConstant %int 1
%_ptr_Input_float = OpTypePointer Input %float
%_ptr_Output_float = OpTypePointer Output %float
%uint_16 = OpConstant %uint 16
%_arr_v4float_uint_16 = OpTypeArray %v4float %uint_16
%_ptr_Output__arr_v4float_uint_16 = OpTypePointer Output %_arr_v4float_uint_16
%5430 = OpVariable %_ptr_Output__arr_v4float_uint_16 Output
%_arr__arr_v4float_uint_16_uint_4 = OpTypeArray %_arr_v4float_uint_16 %uint_4
%_ptr_Input__arr__arr_v4float_uint_16_uint_4 = OpTypePointer Input %_arr__arr_v4float_uint_16_uint_4
%3302 = OpVariable %_ptr_Input__arr__arr_v4float_uint_16_uint_4 Input
%_ptr_Input__arr_v4float_uint_16 = OpTypePointer Input %_arr_v4float_uint_16
%int_2 = OpConstant %int 2
%int_3 = OpConstant %int 3
%v2float = OpTypeVector %float 2
%_arr_v2float_uint_4 = OpTypeArray %v2float %uint_4
%_ptr_Input__arr_v2float_uint_4 = OpTypePointer Input %_arr_v2float_uint_4
%4044 = OpVariable %_ptr_Input__arr_v2float_uint_4 Input
%_arr_float_uint_4 = OpTypeArray %float %uint_4
%_ptr_Input__arr_float_uint_4 = OpTypePointer Input %_arr_float_uint_4
%4656 = OpVariable %_ptr_Input__arr_float_uint_4 Input
%_ptr_Output_v2float = OpTypePointer Output %v2float
%3736 = OpVariable %_ptr_Output_v2float Output
%5663 = OpFunction %void None %1282
%23915 = OpLabel
%7129 = OpAccessChain %_ptr_Input_v4float %5305 %int_0 %int_0
%15646 = OpLoad %v4float %7129
%19981 = OpAccessChain %_ptr_Output_v4float %4930 %int_0
OpStore %19981 %15646
%19905 = OpAccessChain %_ptr_Input_float %5305 %int_0 %int_1
%7391 = OpLoad %float %19905
%19982 = OpAccessChain %_ptr_Output_float %4930 %int_1
OpStore %19982 %7391
%19848 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_0
%10874 = OpLoad %_arr_v4float_uint_16 %19848
OpStore %5430 %10874
OpEmitVertex
%22812 = OpAccessChain %_ptr_Input_v4float %5305 %int_1 %int_0
%11398 = OpLoad %v4float %22812
OpStore %19981 %11398
%16622 = OpAccessChain %_ptr_Input_float %5305 %int_1 %int_1
%7967 = OpLoad %float %16622
OpStore %19982 %7967
%16623 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_1
%10875 = OpLoad %_arr_v4float_uint_16 %16623
OpStore %5430 %10875
OpEmitVertex
%22813 = OpAccessChain %_ptr_Input_v4float %5305 %int_2 %int_0
%11399 = OpLoad %v4float %22813
OpStore %19981 %11399
%16624 = OpAccessChain %_ptr_Input_float %5305 %int_2 %int_1
%7968 = OpLoad %float %16624
OpStore %19982 %7968
%16625 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_2
%10876 = OpLoad %_arr_v4float_uint_16 %16625
OpStore %5430 %10876
OpEmitVertex
%22814 = OpAccessChain %_ptr_Input_v4float %5305 %int_3 %int_0
%11400 = OpLoad %v4float %22814
OpStore %19981 %11400
%16626 = OpAccessChain %_ptr_Input_float %5305 %int_3 %int_1
%7969 = OpLoad %float %16626
OpStore %19982 %7969
%16627 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_3
%10877 = OpLoad %_arr_v4float_uint_16 %16627
OpStore %5430 %10877
OpEmitVertex
OpStore %19981 %15646
OpStore %19982 %7391
OpStore %5430 %10874
OpEmitVertex
OpEndPrimitive
OpReturn
OpFunctionEnd
#endif
const uint32_t line_quad_list_gs[] = {
0x07230203, 0x00010000, 0x0008000A, 0x00005D6C, 0x00000000, 0x00020011,
0x00000002, 0x00020011, 0x00000018, 0x0006000B, 0x00000001, 0x4C534C47,
0x6474732E, 0x3035342E, 0x00000000, 0x0003000E, 0x00000000, 0x00000001,
0x000C000F, 0x00000003, 0x0000161F, 0x6E69616D, 0x00000000, 0x00001342,
0x000014B9, 0x00001536, 0x00000CE6, 0x00000FCC, 0x00001230, 0x00000E98,
0x00030010, 0x0000161F, 0x00000015, 0x00040010, 0x0000161F, 0x00000000,
0x00000001, 0x00030010, 0x0000161F, 0x0000001C, 0x00040010, 0x0000161F,
0x0000001A, 0x00000005, 0x00050048, 0x00000408, 0x00000000, 0x0000000B,
0x00000000, 0x00050048, 0x00000408, 0x00000001, 0x0000000B, 0x00000001,
0x00030047, 0x00000408, 0x00000002, 0x00050048, 0x00000409, 0x00000000,
0x0000000B, 0x00000000, 0x00050048, 0x00000409, 0x00000001, 0x0000000B,
0x00000001, 0x00030047, 0x00000409, 0x00000002, 0x00040047, 0x00001536,
0x0000001E, 0x00000000, 0x00040047, 0x00000CE6, 0x0000001E, 0x00000000,
0x00040047, 0x00000FCC, 0x0000001E, 0x00000010, 0x00040047, 0x00001230,
0x0000001E, 0x00000011, 0x00040047, 0x00000E98, 0x0000001E, 0x00000010,
0x00020013, 0x00000008, 0x00030021, 0x00000502, 0x00000008, 0x00030016,
0x0000000D, 0x00000020, 0x00040017, 0x0000001D, 0x0000000D, 0x00000004,
0x0004001E, 0x00000408, 0x0000001D, 0x0000000D, 0x00040020, 0x00000685,
0x00000003, 0x00000408, 0x0004003B, 0x00000685, 0x00001342, 0x00000003,
0x00040015, 0x0000000C, 0x00000020, 0x00000001, 0x0004002B, 0x0000000C,
0x00000A0B, 0x00000000, 0x0004001E, 0x00000409, 0x0000001D, 0x0000000D,
0x00040015, 0x0000000B, 0x00000020, 0x00000000, 0x0004002B, 0x0000000B,
0x00000A16, 0x00000004, 0x0004001C, 0x0000032E, 0x00000409, 0x00000A16,
0x00040020, 0x000005AB, 0x00000001, 0x0000032E, 0x0004003B, 0x000005AB,
0x000014B9, 0x00000001, 0x00040020, 0x0000029A, 0x00000001, 0x0000001D,
0x00040020, 0x0000029B, 0x00000003, 0x0000001D, 0x0004002B, 0x0000000C,
0x00000A0E, 0x00000001, 0x00040020, 0x0000028A, 0x00000001, 0x0000000D,
0x00040020, 0x0000028B, 0x00000003, 0x0000000D, 0x0004002B, 0x0000000B,
0x00000A3A, 0x00000010, 0x0004001C, 0x00000473, 0x0000001D, 0x00000A3A,
0x00040020, 0x000006F0, 0x00000003, 0x00000473, 0x0004003B, 0x000006F0,
0x00001536, 0x00000003, 0x0004001C, 0x00000973, 0x00000473, 0x00000A16,
0x00040020, 0x0000002D, 0x00000001, 0x00000973, 0x0004003B, 0x0000002D,
0x00000CE6, 0x00000001, 0x00040020, 0x000006F1, 0x00000001, 0x00000473,
0x0004002B, 0x0000000C, 0x00000A11, 0x00000002, 0x0004002B, 0x0000000C,
0x00000A14, 0x00000003, 0x00040017, 0x00000013, 0x0000000D, 0x00000002,
0x0004001C, 0x000002A2, 0x00000013, 0x00000A16, 0x00040020, 0x0000051F,
0x00000001, 0x000002A2, 0x0004003B, 0x0000051F, 0x00000FCC, 0x00000001,
0x0004001C, 0x00000248, 0x0000000D, 0x00000A16, 0x00040020, 0x000004C5,
0x00000001, 0x00000248, 0x0004003B, 0x000004C5, 0x00001230, 0x00000001,
0x00040020, 0x00000290, 0x00000003, 0x00000013, 0x0004003B, 0x00000290,
0x00000E98, 0x00000003, 0x00050036, 0x00000008, 0x0000161F, 0x00000000,
0x00000502, 0x000200F8, 0x00005D6B, 0x00060041, 0x0000029A, 0x00001BD9,
0x000014B9, 0x00000A0B, 0x00000A0B, 0x0004003D, 0x0000001D, 0x00003D1E,
0x00001BD9, 0x00050041, 0x0000029B, 0x00004E0D, 0x00001342, 0x00000A0B,
0x0003003E, 0x00004E0D, 0x00003D1E, 0x00060041, 0x0000028A, 0x00004DC1,
0x000014B9, 0x00000A0B, 0x00000A0E, 0x0004003D, 0x0000000D, 0x00001CDF,
0x00004DC1, 0x00050041, 0x0000028B, 0x00004E0E, 0x00001342, 0x00000A0E,
0x0003003E, 0x00004E0E, 0x00001CDF, 0x00050041, 0x000006F1, 0x00004D88,
0x00000CE6, 0x00000A0B, 0x0004003D, 0x00000473, 0x00002A7A, 0x00004D88,
0x0003003E, 0x00001536, 0x00002A7A, 0x000100DA, 0x00060041, 0x0000029A,
0x0000591C, 0x000014B9, 0x00000A0E, 0x00000A0B, 0x0004003D, 0x0000001D,
0x00002C86, 0x0000591C, 0x0003003E, 0x00004E0D, 0x00002C86, 0x00060041,
0x0000028A, 0x000040EE, 0x000014B9, 0x00000A0E, 0x00000A0E, 0x0004003D,
0x0000000D, 0x00001F1F, 0x000040EE, 0x0003003E, 0x00004E0E, 0x00001F1F,
0x00050041, 0x000006F1, 0x000040EF, 0x00000CE6, 0x00000A0E, 0x0004003D,
0x00000473, 0x00002A7B, 0x000040EF, 0x0003003E, 0x00001536, 0x00002A7B,
0x000100DA, 0x00060041, 0x0000029A, 0x0000591D, 0x000014B9, 0x00000A11,
0x00000A0B, 0x0004003D, 0x0000001D, 0x00002C87, 0x0000591D, 0x0003003E,
0x00004E0D, 0x00002C87, 0x00060041, 0x0000028A, 0x000040F0, 0x000014B9,
0x00000A11, 0x00000A0E, 0x0004003D, 0x0000000D, 0x00001F20, 0x000040F0,
0x0003003E, 0x00004E0E, 0x00001F20, 0x00050041, 0x000006F1, 0x000040F1,
0x00000CE6, 0x00000A11, 0x0004003D, 0x00000473, 0x00002A7C, 0x000040F1,
0x0003003E, 0x00001536, 0x00002A7C, 0x000100DA, 0x00060041, 0x0000029A,
0x0000591E, 0x000014B9, 0x00000A14, 0x00000A0B, 0x0004003D, 0x0000001D,
0x00002C88, 0x0000591E, 0x0003003E, 0x00004E0D, 0x00002C88, 0x00060041,
0x0000028A, 0x000040F2, 0x000014B9, 0x00000A14, 0x00000A0E, 0x0004003D,
0x0000000D, 0x00001F21, 0x000040F2, 0x0003003E, 0x00004E0E, 0x00001F21,
0x00050041, 0x000006F1, 0x000040F3, 0x00000CE6, 0x00000A14, 0x0004003D,
0x00000473, 0x00002A7D, 0x000040F3, 0x0003003E, 0x00001536, 0x00002A7D,
0x000100DA, 0x0003003E, 0x00004E0D, 0x00003D1E, 0x0003003E, 0x00004E0E,
0x00001CDF, 0x0003003E, 0x00001536, 0x00002A7A, 0x000100DA, 0x000100DB,
0x000100FD, 0x00010038,
};

View File

@@ -1,244 +0,0 @@
// Generated with `xb buildshaders`.
#if 0
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 24916
; Schema: 0
OpCapability Geometry
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %5663 "main" %5305 %4065 %4930 %5430 %3302 %5753 %5479
OpExecutionMode %5663 InputPoints
OpExecutionMode %5663 Invocations 1
OpExecutionMode %5663 OutputTriangleStrip
OpExecutionMode %5663 OutputVertices 4
OpMemberDecorate %_struct_1017 0 BuiltIn Position
OpDecorate %_struct_1017 Block
OpMemberDecorate %_struct_1287 0 Offset 0
OpMemberDecorate %_struct_1287 1 Offset 16
OpMemberDecorate %_struct_1287 2 Offset 32
OpMemberDecorate %_struct_1287 3 Offset 48
OpMemberDecorate %_struct_1287 4 Offset 64
OpDecorate %_struct_1287 Block
OpDecorate %4065 Location 17
OpMemberDecorate %_struct_1018 0 BuiltIn Position
OpDecorate %_struct_1018 Block
OpDecorate %5430 Location 0
OpDecorate %3302 Location 0
OpDecorate %5753 Location 16
OpDecorate %5479 Location 16
%void = OpTypeVoid
%1282 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_struct_1017 = OpTypeStruct %v4float
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%_arr__struct_1017_uint_1 = OpTypeArray %_struct_1017 %uint_1
%_ptr_Input__arr__struct_1017_uint_1 = OpTypePointer Input %_arr__struct_1017_uint_1
%5305 = OpVariable %_ptr_Input__arr__struct_1017_uint_1 Input
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_ptr_Input_v4float = OpTypePointer Input %v4float
%v2float = OpTypeVector %float 2
%_ptr_Function_v2float = OpTypePointer Function %v2float
%_struct_1287 = OpTypeStruct %v4float %v4float %v4float %v4float %uint
%_ptr_PushConstant__struct_1287 = OpTypePointer PushConstant %_struct_1287
%3463 = OpVariable %_ptr_PushConstant__struct_1287 PushConstant
%int_2 = OpConstant %int 2
%_ptr_PushConstant_v4float = OpTypePointer PushConstant %v4float
%_arr_float_uint_1 = OpTypeArray %float %uint_1
%_ptr_Input__arr_float_uint_1 = OpTypePointer Input %_arr_float_uint_1
%4065 = OpVariable %_ptr_Input__arr_float_uint_1 Input
%_ptr_Input_float = OpTypePointer Input %float
%float_0 = OpConstant %float 0
%bool = OpTypeBool
%int_4 = OpConstant %int 4
%_struct_1018 = OpTypeStruct %v4float
%_ptr_Output__struct_1018 = OpTypePointer Output %_struct_1018
%4930 = OpVariable %_ptr_Output__struct_1018 Output
%uint_4 = OpConstant %uint 4
%_arr_v2float_uint_4 = OpTypeArray %v2float %uint_4
%float_n1 = OpConstant %float -1
%float_1 = OpConstant %float 1
%73 = OpConstantComposite %v2float %float_n1 %float_1
%768 = OpConstantComposite %v2float %float_1 %float_1
%74 = OpConstantComposite %v2float %float_n1 %float_n1
%769 = OpConstantComposite %v2float %float_1 %float_n1
%2941 = OpConstantComposite %_arr_v2float_uint_4 %73 %768 %74 %769
%_ptr_Function__arr_v2float_uint_4 = OpTypePointer Function %_arr_v2float_uint_4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%uint_16 = OpConstant %uint 16
%_arr_v4float_uint_16 = OpTypeArray %v4float %uint_16
%_ptr_Output__arr_v4float_uint_16 = OpTypePointer Output %_arr_v4float_uint_16
%5430 = OpVariable %_ptr_Output__arr_v4float_uint_16 Output
%_arr__arr_v4float_uint_16_uint_1 = OpTypeArray %_arr_v4float_uint_16 %uint_1
%_ptr_Input__arr__arr_v4float_uint_16_uint_1 = OpTypePointer Input %_arr__arr_v4float_uint_16_uint_1
%3302 = OpVariable %_ptr_Input__arr__arr_v4float_uint_16_uint_1 Input
%_ptr_Input__arr_v4float_uint_16 = OpTypePointer Input %_arr_v4float_uint_16
%_ptr_Output_v2float = OpTypePointer Output %v2float
%5753 = OpVariable %_ptr_Output_v2float Output
%1823 = OpConstantComposite %v2float %float_0 %float_0
%int_1 = OpConstant %int 1
%_arr_v2float_uint_1 = OpTypeArray %v2float %uint_1
%_ptr_Input__arr_v2float_uint_1 = OpTypePointer Input %_arr_v2float_uint_1
%5479 = OpVariable %_ptr_Input__arr_v2float_uint_1 Input
%5663 = OpFunction %void None %1282
%24915 = OpLabel
%18491 = OpVariable %_ptr_Function__arr_v2float_uint_4 Function
%5238 = OpVariable %_ptr_Function__arr_v2float_uint_4 Function
%22270 = OpAccessChain %_ptr_Input_v4float %5305 %int_0 %int_0
%8181 = OpLoad %v4float %22270
%20420 = OpAccessChain %_ptr_PushConstant_v4float %3463 %int_2
%20062 = OpLoad %v4float %20420
%19110 = OpVectorShuffle %v2float %20062 %20062 0 1
%7988 = OpAccessChain %_ptr_Input_float %4065 %int_0
%13069 = OpLoad %float %7988
%23515 = OpFOrdGreaterThan %bool %13069 %float_0
OpSelectionMerge %16839 None
OpBranchConditional %23515 %13106 %16839
%13106 = OpLabel
%18836 = OpCompositeConstruct %v2float %13069 %13069
OpBranch %16839
%16839 = OpLabel
%19748 = OpPhi %v2float %19110 %24915 %18836 %13106
%24067 = OpAccessChain %_ptr_PushConstant_v4float %3463 %int_0
%15439 = OpLoad %v4float %24067
%10399 = OpVectorShuffle %v2float %15439 %15439 2 3
%24282 = OpFDiv %v2float %19748 %10399
OpBranch %6318
%6318 = OpLabel
%22958 = OpPhi %int %int_0 %16839 %11651 %12148
%24788 = OpSLessThan %bool %22958 %int_4
OpLoopMerge %12265 %12148 None
OpBranchConditional %24788 %12148 %12265
%12148 = OpLabel
%17761 = OpVectorShuffle %v2float %8181 %8181 0 1
OpStore %18491 %2941
%19574 = OpAccessChain %_ptr_Function_v2float %18491 %22958
%15971 = OpLoad %v2float %19574
%17243 = OpFMul %v2float %15971 %24282
%16594 = OpFAdd %v2float %17761 %17243
%10618 = OpCompositeExtract %float %16594 0
%14087 = OpCompositeExtract %float %16594 1
%7641 = OpCompositeExtract %float %8181 2
%7529 = OpCompositeExtract %float %8181 3
%18260 = OpCompositeConstruct %v4float %10618 %14087 %7641 %7529
%8483 = OpAccessChain %_ptr_Output_v4float %4930 %int_0
OpStore %8483 %18260
%19848 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_0
%7910 = OpLoad %_arr_v4float_uint_16 %19848
OpStore %5430 %7910
OpStore %5238 %2941
%13290 = OpAccessChain %_ptr_Function_v2float %5238 %22958
%19207 = OpLoad %v2float %13290
%8973 = OpExtInst %v2float %1 FMax %19207 %1823
OpStore %5753 %8973
OpEmitVertex
%11651 = OpIAdd %int %22958 %int_1
OpBranch %6318
%12265 = OpLabel
OpEndPrimitive
OpReturn
OpFunctionEnd
#endif
const uint32_t point_list_gs[] = {
0x07230203, 0x00010000, 0x0008000A, 0x00006154, 0x00000000, 0x00020011,
0x00000002, 0x0006000B, 0x00000001, 0x4C534C47, 0x6474732E, 0x3035342E,
0x00000000, 0x0003000E, 0x00000000, 0x00000001, 0x000C000F, 0x00000003,
0x0000161F, 0x6E69616D, 0x00000000, 0x000014B9, 0x00000FE1, 0x00001342,
0x00001536, 0x00000CE6, 0x00001679, 0x00001567, 0x00030010, 0x0000161F,
0x00000013, 0x00040010, 0x0000161F, 0x00000000, 0x00000001, 0x00030010,
0x0000161F, 0x0000001D, 0x00040010, 0x0000161F, 0x0000001A, 0x00000004,
0x00050048, 0x000003F9, 0x00000000, 0x0000000B, 0x00000000, 0x00030047,
0x000003F9, 0x00000002, 0x00050048, 0x00000507, 0x00000000, 0x00000023,
0x00000000, 0x00050048, 0x00000507, 0x00000001, 0x00000023, 0x00000010,
0x00050048, 0x00000507, 0x00000002, 0x00000023, 0x00000020, 0x00050048,
0x00000507, 0x00000003, 0x00000023, 0x00000030, 0x00050048, 0x00000507,
0x00000004, 0x00000023, 0x00000040, 0x00030047, 0x00000507, 0x00000002,
0x00040047, 0x00000FE1, 0x0000001E, 0x00000011, 0x00050048, 0x000003FA,
0x00000000, 0x0000000B, 0x00000000, 0x00030047, 0x000003FA, 0x00000002,
0x00040047, 0x00001536, 0x0000001E, 0x00000000, 0x00040047, 0x00000CE6,
0x0000001E, 0x00000000, 0x00040047, 0x00001679, 0x0000001E, 0x00000010,
0x00040047, 0x00001567, 0x0000001E, 0x00000010, 0x00020013, 0x00000008,
0x00030021, 0x00000502, 0x00000008, 0x00030016, 0x0000000D, 0x00000020,
0x00040017, 0x0000001D, 0x0000000D, 0x00000004, 0x0003001E, 0x000003F9,
0x0000001D, 0x00040015, 0x0000000B, 0x00000020, 0x00000000, 0x0004002B,
0x0000000B, 0x00000A0D, 0x00000001, 0x0004001C, 0x0000023D, 0x000003F9,
0x00000A0D, 0x00040020, 0x000004BA, 0x00000001, 0x0000023D, 0x0004003B,
0x000004BA, 0x000014B9, 0x00000001, 0x00040015, 0x0000000C, 0x00000020,
0x00000001, 0x0004002B, 0x0000000C, 0x00000A0B, 0x00000000, 0x00040020,
0x0000029A, 0x00000001, 0x0000001D, 0x00040017, 0x00000013, 0x0000000D,
0x00000002, 0x00040020, 0x00000290, 0x00000007, 0x00000013, 0x0007001E,
0x00000507, 0x0000001D, 0x0000001D, 0x0000001D, 0x0000001D, 0x0000000B,
0x00040020, 0x00000784, 0x00000009, 0x00000507, 0x0004003B, 0x00000784,
0x00000D87, 0x00000009, 0x0004002B, 0x0000000C, 0x00000A11, 0x00000002,
0x00040020, 0x0000029B, 0x00000009, 0x0000001D, 0x0004001C, 0x00000239,
0x0000000D, 0x00000A0D, 0x00040020, 0x000004B6, 0x00000001, 0x00000239,
0x0004003B, 0x000004B6, 0x00000FE1, 0x00000001, 0x00040020, 0x0000028A,
0x00000001, 0x0000000D, 0x0004002B, 0x0000000D, 0x00000A0C, 0x00000000,
0x00020014, 0x00000009, 0x0004002B, 0x0000000C, 0x00000A17, 0x00000004,
0x0003001E, 0x000003FA, 0x0000001D, 0x00040020, 0x00000676, 0x00000003,
0x000003FA, 0x0004003B, 0x00000676, 0x00001342, 0x00000003, 0x0004002B,
0x0000000B, 0x00000A16, 0x00000004, 0x0004001C, 0x000004D3, 0x00000013,
0x00000A16, 0x0004002B, 0x0000000D, 0x00000341, 0xBF800000, 0x0004002B,
0x0000000D, 0x0000008A, 0x3F800000, 0x0005002C, 0x00000013, 0x00000049,
0x00000341, 0x0000008A, 0x0005002C, 0x00000013, 0x00000300, 0x0000008A,
0x0000008A, 0x0005002C, 0x00000013, 0x0000004A, 0x00000341, 0x00000341,
0x0005002C, 0x00000013, 0x00000301, 0x0000008A, 0x00000341, 0x0007002C,
0x000004D3, 0x00000B7D, 0x00000049, 0x00000300, 0x0000004A, 0x00000301,
0x00040020, 0x00000750, 0x00000007, 0x000004D3, 0x00040020, 0x0000029C,
0x00000003, 0x0000001D, 0x0004002B, 0x0000000B, 0x00000A3A, 0x00000010,
0x0004001C, 0x00000989, 0x0000001D, 0x00000A3A, 0x00040020, 0x00000043,
0x00000003, 0x00000989, 0x0004003B, 0x00000043, 0x00001536, 0x00000003,
0x0004001C, 0x00000A2E, 0x00000989, 0x00000A0D, 0x00040020, 0x000000E8,
0x00000001, 0x00000A2E, 0x0004003B, 0x000000E8, 0x00000CE6, 0x00000001,
0x00040020, 0x00000044, 0x00000001, 0x00000989, 0x00040020, 0x00000291,
0x00000003, 0x00000013, 0x0004003B, 0x00000291, 0x00001679, 0x00000003,
0x0005002C, 0x00000013, 0x0000071F, 0x00000A0C, 0x00000A0C, 0x0004002B,
0x0000000C, 0x00000A0E, 0x00000001, 0x0004001C, 0x00000281, 0x00000013,
0x00000A0D, 0x00040020, 0x000004FE, 0x00000001, 0x00000281, 0x0004003B,
0x000004FE, 0x00001567, 0x00000001, 0x00050036, 0x00000008, 0x0000161F,
0x00000000, 0x00000502, 0x000200F8, 0x00006153, 0x0004003B, 0x00000750,
0x0000483B, 0x00000007, 0x0004003B, 0x00000750, 0x00001476, 0x00000007,
0x00060041, 0x0000029A, 0x000056FE, 0x000014B9, 0x00000A0B, 0x00000A0B,
0x0004003D, 0x0000001D, 0x00001FF5, 0x000056FE, 0x00050041, 0x0000029B,
0x00004FC4, 0x00000D87, 0x00000A11, 0x0004003D, 0x0000001D, 0x00004E5E,
0x00004FC4, 0x0007004F, 0x00000013, 0x00004AA6, 0x00004E5E, 0x00004E5E,
0x00000000, 0x00000001, 0x00050041, 0x0000028A, 0x00001F34, 0x00000FE1,
0x00000A0B, 0x0004003D, 0x0000000D, 0x0000330D, 0x00001F34, 0x000500BA,
0x00000009, 0x00005BDB, 0x0000330D, 0x00000A0C, 0x000300F7, 0x000041C7,
0x00000000, 0x000400FA, 0x00005BDB, 0x00003332, 0x000041C7, 0x000200F8,
0x00003332, 0x00050050, 0x00000013, 0x00004994, 0x0000330D, 0x0000330D,
0x000200F9, 0x000041C7, 0x000200F8, 0x000041C7, 0x000700F5, 0x00000013,
0x00004D24, 0x00004AA6, 0x00006153, 0x00004994, 0x00003332, 0x00050041,
0x0000029B, 0x00005E03, 0x00000D87, 0x00000A0B, 0x0004003D, 0x0000001D,
0x00003C4F, 0x00005E03, 0x0007004F, 0x00000013, 0x0000289F, 0x00003C4F,
0x00003C4F, 0x00000002, 0x00000003, 0x00050088, 0x00000013, 0x00005EDA,
0x00004D24, 0x0000289F, 0x000200F9, 0x000018AE, 0x000200F8, 0x000018AE,
0x000700F5, 0x0000000C, 0x000059AE, 0x00000A0B, 0x000041C7, 0x00002D83,
0x00002F74, 0x000500B1, 0x00000009, 0x000060D4, 0x000059AE, 0x00000A17,
0x000400F6, 0x00002FE9, 0x00002F74, 0x00000000, 0x000400FA, 0x000060D4,
0x00002F74, 0x00002FE9, 0x000200F8, 0x00002F74, 0x0007004F, 0x00000013,
0x00004561, 0x00001FF5, 0x00001FF5, 0x00000000, 0x00000001, 0x0003003E,
0x0000483B, 0x00000B7D, 0x00050041, 0x00000290, 0x00004C76, 0x0000483B,
0x000059AE, 0x0004003D, 0x00000013, 0x00003E63, 0x00004C76, 0x00050085,
0x00000013, 0x0000435B, 0x00003E63, 0x00005EDA, 0x00050081, 0x00000013,
0x000040D2, 0x00004561, 0x0000435B, 0x00050051, 0x0000000D, 0x0000297A,
0x000040D2, 0x00000000, 0x00050051, 0x0000000D, 0x00003707, 0x000040D2,
0x00000001, 0x00050051, 0x0000000D, 0x00001DD9, 0x00001FF5, 0x00000002,
0x00050051, 0x0000000D, 0x00001D69, 0x00001FF5, 0x00000003, 0x00070050,
0x0000001D, 0x00004754, 0x0000297A, 0x00003707, 0x00001DD9, 0x00001D69,
0x00050041, 0x0000029C, 0x00002123, 0x00001342, 0x00000A0B, 0x0003003E,
0x00002123, 0x00004754, 0x00050041, 0x00000044, 0x00004D88, 0x00000CE6,
0x00000A0B, 0x0004003D, 0x00000989, 0x00001EE6, 0x00004D88, 0x0003003E,
0x00001536, 0x00001EE6, 0x0003003E, 0x00001476, 0x00000B7D, 0x00050041,
0x00000290, 0x000033EA, 0x00001476, 0x000059AE, 0x0004003D, 0x00000013,
0x00004B07, 0x000033EA, 0x0007000C, 0x00000013, 0x0000230D, 0x00000001,
0x00000028, 0x00004B07, 0x0000071F, 0x0003003E, 0x00001679, 0x0000230D,
0x000100DA, 0x00050080, 0x0000000C, 0x00002D83, 0x000059AE, 0x00000A0E,
0x000200F9, 0x000018AE, 0x000200F8, 0x00002FE9, 0x000100DB, 0x000100FD,
0x00010038,
};

View File

@@ -1,170 +0,0 @@
// Generated with `xb buildshaders`.
#if 0
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 24789
; Schema: 0
OpCapability Geometry
OpCapability GeometryPointSize
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %5663 "main" %4930 %5305 %5430 %3302 %4044 %4656 %3736
OpExecutionMode %5663 InputLinesAdjacency
OpExecutionMode %5663 Invocations 1
OpExecutionMode %5663 OutputTriangleStrip
OpExecutionMode %5663 OutputVertices 4
OpMemberDecorate %_struct_1032 0 BuiltIn Position
OpMemberDecorate %_struct_1032 1 BuiltIn PointSize
OpDecorate %_struct_1032 Block
OpMemberDecorate %_struct_1033 0 BuiltIn Position
OpMemberDecorate %_struct_1033 1 BuiltIn PointSize
OpDecorate %_struct_1033 Block
OpDecorate %5430 Location 0
OpDecorate %3302 Location 0
OpDecorate %4044 Location 16
OpDecorate %4656 Location 17
OpDecorate %3736 Location 16
%void = OpTypeVoid
%1282 = OpTypeFunction %void
%int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
%int_0 = OpConstant %int 0
%int_4 = OpConstant %int 4
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_4 = OpConstant %uint 4
%_arr_int_uint_4 = OpTypeArray %int %uint_4
%int_1 = OpConstant %int 1
%int_3 = OpConstant %int 3
%int_2 = OpConstant %int 2
%566 = OpConstantComposite %_arr_int_uint_4 %int_0 %int_1 %int_3 %int_2
%_ptr_Function__arr_int_uint_4 = OpTypePointer Function %_arr_int_uint_4
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_struct_1032 = OpTypeStruct %v4float %float
%_ptr_Output__struct_1032 = OpTypePointer Output %_struct_1032
%4930 = OpVariable %_ptr_Output__struct_1032 Output
%_struct_1033 = OpTypeStruct %v4float %float
%_arr__struct_1033_uint_4 = OpTypeArray %_struct_1033 %uint_4
%_ptr_Input__arr__struct_1033_uint_4 = OpTypePointer Input %_arr__struct_1033_uint_4
%5305 = OpVariable %_ptr_Input__arr__struct_1033_uint_4 Input
%_ptr_Input_v4float = OpTypePointer Input %v4float
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_ptr_Input_float = OpTypePointer Input %float
%_ptr_Output_float = OpTypePointer Output %float
%uint_16 = OpConstant %uint 16
%_arr_v4float_uint_16 = OpTypeArray %v4float %uint_16
%_ptr_Output__arr_v4float_uint_16 = OpTypePointer Output %_arr_v4float_uint_16
%5430 = OpVariable %_ptr_Output__arr_v4float_uint_16 Output
%_arr__arr_v4float_uint_16_uint_4 = OpTypeArray %_arr_v4float_uint_16 %uint_4
%_ptr_Input__arr__arr_v4float_uint_16_uint_4 = OpTypePointer Input %_arr__arr_v4float_uint_16_uint_4
%3302 = OpVariable %_ptr_Input__arr__arr_v4float_uint_16_uint_4 Input
%_ptr_Input__arr_v4float_uint_16 = OpTypePointer Input %_arr_v4float_uint_16
%v2float = OpTypeVector %float 2
%_arr_v2float_uint_4 = OpTypeArray %v2float %uint_4
%_ptr_Input__arr_v2float_uint_4 = OpTypePointer Input %_arr_v2float_uint_4
%4044 = OpVariable %_ptr_Input__arr_v2float_uint_4 Input
%_arr_float_uint_4 = OpTypeArray %float %uint_4
%_ptr_Input__arr_float_uint_4 = OpTypePointer Input %_arr_float_uint_4
%4656 = OpVariable %_ptr_Input__arr_float_uint_4 Input
%_ptr_Output_v2float = OpTypePointer Output %v2float
%3736 = OpVariable %_ptr_Output_v2float Output
%5663 = OpFunction %void None %1282
%9454 = OpLabel
%5238 = OpVariable %_ptr_Function__arr_int_uint_4 Function
OpBranch %18173
%18173 = OpLabel
%22958 = OpPhi %int %int_0 %9454 %11651 %15146
%24788 = OpSLessThan %bool %22958 %int_4
OpLoopMerge %12265 %15146 None
OpBranchConditional %24788 %15146 %12265
%15146 = OpLabel
OpStore %5238 %566
%22512 = OpAccessChain %_ptr_Function_int %5238 %22958
%7372 = OpLoad %int %22512
%20154 = OpAccessChain %_ptr_Input_v4float %5305 %7372 %int_0
%22427 = OpLoad %v4float %20154
%19981 = OpAccessChain %_ptr_Output_v4float %4930 %int_0
OpStore %19981 %22427
%19905 = OpAccessChain %_ptr_Input_float %5305 %7372 %int_1
%7391 = OpLoad %float %19905
%19982 = OpAccessChain %_ptr_Output_float %4930 %int_1
OpStore %19982 %7391
%19848 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %7372
%10874 = OpLoad %_arr_v4float_uint_16 %19848
OpStore %5430 %10874
OpEmitVertex
%11651 = OpIAdd %int %22958 %int_1
OpBranch %18173
%12265 = OpLabel
OpEndPrimitive
OpReturn
OpFunctionEnd
#endif
const uint32_t quad_list_gs[] = {
0x07230203, 0x00010000, 0x0008000A, 0x000060D5, 0x00000000, 0x00020011,
0x00000002, 0x00020011, 0x00000018, 0x0006000B, 0x00000001, 0x4C534C47,
0x6474732E, 0x3035342E, 0x00000000, 0x0003000E, 0x00000000, 0x00000001,
0x000C000F, 0x00000003, 0x0000161F, 0x6E69616D, 0x00000000, 0x00001342,
0x000014B9, 0x00001536, 0x00000CE6, 0x00000FCC, 0x00001230, 0x00000E98,
0x00030010, 0x0000161F, 0x00000015, 0x00040010, 0x0000161F, 0x00000000,
0x00000001, 0x00030010, 0x0000161F, 0x0000001D, 0x00040010, 0x0000161F,
0x0000001A, 0x00000004, 0x00050048, 0x00000408, 0x00000000, 0x0000000B,
0x00000000, 0x00050048, 0x00000408, 0x00000001, 0x0000000B, 0x00000001,
0x00030047, 0x00000408, 0x00000002, 0x00050048, 0x00000409, 0x00000000,
0x0000000B, 0x00000000, 0x00050048, 0x00000409, 0x00000001, 0x0000000B,
0x00000001, 0x00030047, 0x00000409, 0x00000002, 0x00040047, 0x00001536,
0x0000001E, 0x00000000, 0x00040047, 0x00000CE6, 0x0000001E, 0x00000000,
0x00040047, 0x00000FCC, 0x0000001E, 0x00000010, 0x00040047, 0x00001230,
0x0000001E, 0x00000011, 0x00040047, 0x00000E98, 0x0000001E, 0x00000010,
0x00020013, 0x00000008, 0x00030021, 0x00000502, 0x00000008, 0x00040015,
0x0000000C, 0x00000020, 0x00000001, 0x00040020, 0x00000289, 0x00000007,
0x0000000C, 0x0004002B, 0x0000000C, 0x00000A0B, 0x00000000, 0x0004002B,
0x0000000C, 0x00000A17, 0x00000004, 0x00020014, 0x00000009, 0x00040015,
0x0000000B, 0x00000020, 0x00000000, 0x0004002B, 0x0000000B, 0x00000A16,
0x00000004, 0x0004001C, 0x00000251, 0x0000000C, 0x00000A16, 0x0004002B,
0x0000000C, 0x00000A0E, 0x00000001, 0x0004002B, 0x0000000C, 0x00000A14,
0x00000003, 0x0004002B, 0x0000000C, 0x00000A11, 0x00000002, 0x0007002C,
0x00000251, 0x00000236, 0x00000A0B, 0x00000A0E, 0x00000A14, 0x00000A11,
0x00040020, 0x000004CE, 0x00000007, 0x00000251, 0x00030016, 0x0000000D,
0x00000020, 0x00040017, 0x0000001D, 0x0000000D, 0x00000004, 0x0004001E,
0x00000408, 0x0000001D, 0x0000000D, 0x00040020, 0x00000685, 0x00000003,
0x00000408, 0x0004003B, 0x00000685, 0x00001342, 0x00000003, 0x0004001E,
0x00000409, 0x0000001D, 0x0000000D, 0x0004001C, 0x000003A8, 0x00000409,
0x00000A16, 0x00040020, 0x00000625, 0x00000001, 0x000003A8, 0x0004003B,
0x00000625, 0x000014B9, 0x00000001, 0x00040020, 0x0000029A, 0x00000001,
0x0000001D, 0x00040020, 0x0000029B, 0x00000003, 0x0000001D, 0x00040020,
0x0000028A, 0x00000001, 0x0000000D, 0x00040020, 0x0000028B, 0x00000003,
0x0000000D, 0x0004002B, 0x0000000B, 0x00000A3A, 0x00000010, 0x0004001C,
0x00000656, 0x0000001D, 0x00000A3A, 0x00040020, 0x000008D3, 0x00000003,
0x00000656, 0x0004003B, 0x000008D3, 0x00001536, 0x00000003, 0x0004001C,
0x00000503, 0x00000656, 0x00000A16, 0x00040020, 0x0000077F, 0x00000001,
0x00000503, 0x0004003B, 0x0000077F, 0x00000CE6, 0x00000001, 0x00040020,
0x000008D4, 0x00000001, 0x00000656, 0x00040017, 0x00000013, 0x0000000D,
0x00000002, 0x0004001C, 0x000002E4, 0x00000013, 0x00000A16, 0x00040020,
0x00000561, 0x00000001, 0x000002E4, 0x0004003B, 0x00000561, 0x00000FCC,
0x00000001, 0x0004001C, 0x00000266, 0x0000000D, 0x00000A16, 0x00040020,
0x000004E3, 0x00000001, 0x00000266, 0x0004003B, 0x000004E3, 0x00001230,
0x00000001, 0x00040020, 0x00000290, 0x00000003, 0x00000013, 0x0004003B,
0x00000290, 0x00000E98, 0x00000003, 0x00050036, 0x00000008, 0x0000161F,
0x00000000, 0x00000502, 0x000200F8, 0x000024EE, 0x0004003B, 0x000004CE,
0x00001476, 0x00000007, 0x000200F9, 0x000046FD, 0x000200F8, 0x000046FD,
0x000700F5, 0x0000000C, 0x000059AE, 0x00000A0B, 0x000024EE, 0x00002D83,
0x00003B2A, 0x000500B1, 0x00000009, 0x000060D4, 0x000059AE, 0x00000A17,
0x000400F6, 0x00002FE9, 0x00003B2A, 0x00000000, 0x000400FA, 0x000060D4,
0x00003B2A, 0x00002FE9, 0x000200F8, 0x00003B2A, 0x0003003E, 0x00001476,
0x00000236, 0x00050041, 0x00000289, 0x000057F0, 0x00001476, 0x000059AE,
0x0004003D, 0x0000000C, 0x00001CCC, 0x000057F0, 0x00060041, 0x0000029A,
0x00004EBA, 0x000014B9, 0x00001CCC, 0x00000A0B, 0x0004003D, 0x0000001D,
0x0000579B, 0x00004EBA, 0x00050041, 0x0000029B, 0x00004E0D, 0x00001342,
0x00000A0B, 0x0003003E, 0x00004E0D, 0x0000579B, 0x00060041, 0x0000028A,
0x00004DC1, 0x000014B9, 0x00001CCC, 0x00000A0E, 0x0004003D, 0x0000000D,
0x00001CDF, 0x00004DC1, 0x00050041, 0x0000028B, 0x00004E0E, 0x00001342,
0x00000A0E, 0x0003003E, 0x00004E0E, 0x00001CDF, 0x00050041, 0x000008D4,
0x00004D88, 0x00000CE6, 0x00001CCC, 0x0004003D, 0x00000656, 0x00002A7A,
0x00004D88, 0x0003003E, 0x00001536, 0x00002A7A, 0x000100DA, 0x00050080,
0x0000000C, 0x00002D83, 0x000059AE, 0x00000A0E, 0x000200F9, 0x000046FD,
0x000200F8, 0x00002FE9, 0x000100DB, 0x000100FD, 0x00010038,
};

View File

@@ -1,430 +0,0 @@
// Generated with `xb buildshaders`.
#if 0
; SPIR-V
; Version: 1.0
; Generator: Khronos Glslang Reference Front End; 10
; Bound: 24790
; Schema: 0
OpCapability Geometry
OpCapability GeometryPointSize
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %5663 "main" %5305 %4930 %5430 %3302 %4044 %4656 %3736
OpExecutionMode %5663 Triangles
OpExecutionMode %5663 Invocations 1
OpExecutionMode %5663 OutputTriangleStrip
OpExecutionMode %5663 OutputVertices 6
OpMemberDecorate %_struct_1032 0 BuiltIn Position
OpMemberDecorate %_struct_1032 1 BuiltIn PointSize
OpDecorate %_struct_1032 Block
OpMemberDecorate %_struct_1033 0 BuiltIn Position
OpMemberDecorate %_struct_1033 1 BuiltIn PointSize
OpDecorate %_struct_1033 Block
OpDecorate %5430 Location 0
OpDecorate %3302 Location 0
OpDecorate %4044 Location 16
OpDecorate %4656 Location 17
OpDecorate %3736 Location 16
%void = OpTypeVoid
%1282 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%bool = OpTypeBool
%v2bool = OpTypeVector %bool 2
%v4float = OpTypeVector %float 4
%_struct_1032 = OpTypeStruct %v4float %float
%uint = OpTypeInt 32 0
%uint_3 = OpConstant %uint 3
%_arr__struct_1032_uint_3 = OpTypeArray %_struct_1032 %uint_3
%_ptr_Input__arr__struct_1032_uint_3 = OpTypePointer Input %_arr__struct_1032_uint_3
%5305 = OpVariable %_ptr_Input__arr__struct_1032_uint_3 Input
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%int_2 = OpConstant %int 2
%uint_0 = OpConstant %uint 0
%_ptr_Input_float = OpTypePointer Input %float
%int_1 = OpConstant %int 1
%uint_1 = OpConstant %uint 1
%float_0_00100000005 = OpConstant %float 0.00100000005
%_ptr_Input_v4float = OpTypePointer Input %v4float
%_struct_1033 = OpTypeStruct %v4float %float
%_ptr_Output__struct_1033 = OpTypePointer Output %_struct_1033
%4930 = OpVariable %_ptr_Output__struct_1033 Output
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_ptr_Output_float = OpTypePointer Output %float
%uint_16 = OpConstant %uint 16
%_arr_v4float_uint_16 = OpTypeArray %v4float %uint_16
%_ptr_Output__arr_v4float_uint_16 = OpTypePointer Output %_arr_v4float_uint_16
%5430 = OpVariable %_ptr_Output__arr_v4float_uint_16 Output
%_arr__arr_v4float_uint_16_uint_3 = OpTypeArray %_arr_v4float_uint_16 %uint_3
%_ptr_Input__arr__arr_v4float_uint_16_uint_3 = OpTypePointer Input %_arr__arr_v4float_uint_16_uint_3
%3302 = OpVariable %_ptr_Input__arr__arr_v4float_uint_16_uint_3 Input
%_ptr_Input__arr_v4float_uint_16 = OpTypePointer Input %_arr_v4float_uint_16
%int_16 = OpConstant %int 16
%_arr_v2float_uint_3 = OpTypeArray %v2float %uint_3
%_ptr_Input__arr_v2float_uint_3 = OpTypePointer Input %_arr_v2float_uint_3
%4044 = OpVariable %_ptr_Input__arr_v2float_uint_3 Input
%_arr_float_uint_3 = OpTypeArray %float %uint_3
%_ptr_Input__arr_float_uint_3 = OpTypePointer Input %_arr_float_uint_3
%4656 = OpVariable %_ptr_Input__arr_float_uint_3 Input
%_ptr_Output_v2float = OpTypePointer Output %v2float
%3736 = OpVariable %_ptr_Output_v2float Output
%1759 = OpConstantComposite %v2float %float_0_00100000005 %float_0_00100000005
%5663 = OpFunction %void None %1282
%23915 = OpLabel
%7129 = OpAccessChain %_ptr_Input_float %5305 %int_2 %int_0 %uint_0
%15627 = OpLoad %float %7129
%20439 = OpAccessChain %_ptr_Input_float %5305 %int_1 %int_0 %uint_1
%19889 = OpLoad %float %20439
%10917 = OpCompositeConstruct %v2float %15627 %19889
%24777 = OpAccessChain %_ptr_Input_v4float %5305 %int_0 %int_0
%7883 = OpLoad %v4float %24777
%6765 = OpVectorShuffle %v2float %7883 %7883 0 1
%15739 = OpFSub %v2float %6765 %10917
%7757 = OpExtInst %v2float %1 FAbs %15739
%19021 = OpFOrdLessThanEqual %v2bool %7757 %1759
%15711 = OpAll %bool %19021
%11402 = OpLogicalNot %bool %15711
OpSelectionMerge %13286 None
OpBranchConditional %11402 %12129 %13286
%12129 = OpLabel
%18210 = OpAccessChain %_ptr_Input_float %5305 %int_1 %int_0 %uint_0
%15628 = OpLoad %float %18210
%20440 = OpAccessChain %_ptr_Input_float %5305 %int_2 %int_0 %uint_1
%21143 = OpLoad %float %20440
%17643 = OpCompositeConstruct %v2float %15628 %21143
%15490 = OpFSub %v2float %6765 %17643
%24406 = OpExtInst %v2float %1 FAbs %15490
%20560 = OpFOrdLessThanEqual %v2bool %24406 %1759
%20788 = OpAll %bool %20560
OpBranch %13286
%13286 = OpLabel
%10924 = OpPhi %bool %15711 %23915 %20788 %12129
OpSelectionMerge %23648 None
OpBranchConditional %10924 %12148 %9186
%12148 = OpLabel
%18037 = OpAccessChain %_ptr_Output_v4float %4930 %int_0
OpStore %18037 %7883
%19905 = OpAccessChain %_ptr_Input_float %5305 %int_0 %int_1
%7391 = OpLoad %float %19905
%19981 = OpAccessChain %_ptr_Output_float %4930 %int_1
OpStore %19981 %7391
%19848 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_0
%10874 = OpLoad %_arr_v4float_uint_16 %19848
OpStore %5430 %10874
OpEmitVertex
%22812 = OpAccessChain %_ptr_Input_v4float %5305 %int_1 %int_0
%11398 = OpLoad %v4float %22812
OpStore %18037 %11398
%16622 = OpAccessChain %_ptr_Input_float %5305 %int_1 %int_1
%7967 = OpLoad %float %16622
OpStore %19981 %7967
%16623 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_1
%10875 = OpLoad %_arr_v4float_uint_16 %16623
OpStore %5430 %10875
OpEmitVertex
%22813 = OpAccessChain %_ptr_Input_v4float %5305 %int_2 %int_0
%11399 = OpLoad %v4float %22813
OpStore %18037 %11399
%16624 = OpAccessChain %_ptr_Input_float %5305 %int_2 %int_1
%7968 = OpLoad %float %16624
OpStore %19981 %7968
%16625 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_2
%10876 = OpLoad %_arr_v4float_uint_16 %16625
OpStore %5430 %10876
OpEmitVertex
OpEndPrimitive
OpStore %18037 %11399
OpStore %19981 %7968
OpStore %5430 %10876
OpEmitVertex
OpStore %18037 %11398
OpStore %19981 %7967
OpStore %5430 %10875
OpEmitVertex
%8851 = OpFNegate %v2float %6765
%13757 = OpVectorShuffle %v2float %11398 %11398 0 1
%21457 = OpFAdd %v2float %8851 %13757
%7434 = OpVectorShuffle %v2float %11399 %11399 0 1
%21812 = OpFAdd %v2float %21457 %7434
%18423 = OpCompositeExtract %float %21812 0
%14087 = OpCompositeExtract %float %21812 1
%7641 = OpCompositeExtract %float %11399 2
%7472 = OpCompositeExtract %float %11399 3
%18779 = OpCompositeConstruct %v4float %18423 %14087 %7641 %7472
OpStore %18037 %18779
OpStore %19981 %7968
OpBranch %17364
%17364 = OpLabel
%22958 = OpPhi %int %int_0 %12148 %21301 %14551
%24788 = OpSLessThan %bool %22958 %int_16
OpLoopMerge %11792 %14551 None
OpBranchConditional %24788 %14551 %11792
%14551 = OpLabel
%19388 = OpAccessChain %_ptr_Input_v4float %3302 %int_0 %22958
%24048 = OpLoad %v4float %19388
%19880 = OpFNegate %v4float %24048
%6667 = OpAccessChain %_ptr_Input_v4float %3302 %int_1 %22958
%6828 = OpLoad %v4float %6667
%22565 = OpFAdd %v4float %19880 %6828
%18783 = OpAccessChain %_ptr_Input_v4float %3302 %int_2 %22958
%21055 = OpLoad %v4float %18783
%22584 = OpFAdd %v4float %22565 %21055
%18591 = OpAccessChain %_ptr_Output_v4float %5430 %22958
OpStore %18591 %22584
%21301 = OpIAdd %int %22958 %int_1
OpBranch %17364
%11792 = OpLabel
OpEmitVertex
OpEndPrimitive
OpBranch %23648
%9186 = OpLabel
%20459 = OpAccessChain %_ptr_Output_v4float %4930 %int_0
OpStore %20459 %7883
%19906 = OpAccessChain %_ptr_Input_float %5305 %int_0 %int_1
%7392 = OpLoad %float %19906
%19982 = OpAccessChain %_ptr_Output_float %4930 %int_1
OpStore %19982 %7392
%19849 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_0
%10877 = OpLoad %_arr_v4float_uint_16 %19849
OpStore %5430 %10877
OpEmitVertex
%22814 = OpAccessChain %_ptr_Input_v4float %5305 %int_1 %int_0
%11400 = OpLoad %v4float %22814
OpStore %20459 %11400
%16626 = OpAccessChain %_ptr_Input_float %5305 %int_1 %int_1
%7969 = OpLoad %float %16626
OpStore %19982 %7969
%16627 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_1
%10878 = OpLoad %_arr_v4float_uint_16 %16627
OpStore %5430 %10878
OpEmitVertex
%22815 = OpAccessChain %_ptr_Input_v4float %5305 %int_2 %int_0
%11401 = OpLoad %v4float %22815
OpStore %20459 %11401
%16628 = OpAccessChain %_ptr_Input_float %5305 %int_2 %int_1
%7970 = OpLoad %float %16628
OpStore %19982 %7970
%16629 = OpAccessChain %_ptr_Input__arr_v4float_uint_16 %3302 %int_2
%10879 = OpLoad %_arr_v4float_uint_16 %16629
OpStore %5430 %10879
OpEmitVertex
OpEndPrimitive
OpStore %20459 %7883
OpStore %19982 %7392
OpStore %5430 %10877
OpEmitVertex
OpStore %20459 %11401
OpStore %19982 %7970
OpStore %5430 %10879
OpEmitVertex
%12391 = OpVectorShuffle %v2float %11400 %11400 0 1
%21222 = OpFNegate %v2float %12391
%8335 = OpFAdd %v2float %6765 %21222
%13861 = OpVectorShuffle %v2float %11401 %11401 0 1
%21813 = OpFAdd %v2float %8335 %13861
%18424 = OpCompositeExtract %float %21813 0
%14088 = OpCompositeExtract %float %21813 1
%7642 = OpCompositeExtract %float %11401 2
%7473 = OpCompositeExtract %float %11401 3
%18780 = OpCompositeConstruct %v4float %18424 %14088 %7642 %7473
OpStore %20459 %18780
OpStore %19982 %7970
OpBranch %17365
%17365 = OpLabel
%22959 = OpPhi %int %int_0 %9186 %21302 %14552
%24789 = OpSLessThan %bool %22959 %int_16
OpLoopMerge %11793 %14552 None
OpBranchConditional %24789 %14552 %11793
%14552 = OpLabel
%18211 = OpAccessChain %_ptr_Input_v4float %3302 %int_0 %22959
%15629 = OpLoad %v4float %18211
%21332 = OpAccessChain %_ptr_Input_v4float %3302 %int_1 %22959
%12974 = OpLoad %v4float %21332
%8884 = OpFNegate %v4float %12974
%7862 = OpFAdd %v4float %15629 %8884
%14199 = OpAccessChain %_ptr_Input_v4float %3302 %int_2 %22959
%21056 = OpLoad %v4float %14199
%22585 = OpFAdd %v4float %7862 %21056
%18592 = OpAccessChain %_ptr_Output_v4float %5430 %22959
OpStore %18592 %22585
%21302 = OpIAdd %int %22959 %int_1
OpBranch %17365
%11793 = OpLabel
OpEmitVertex
OpEndPrimitive
OpBranch %23648
%23648 = OpLabel
OpReturn
OpFunctionEnd
#endif
const uint32_t rect_list_gs[] = {
0x07230203, 0x00010000, 0x0008000A, 0x000060D6, 0x00000000, 0x00020011,
0x00000002, 0x00020011, 0x00000018, 0x0006000B, 0x00000001, 0x4C534C47,
0x6474732E, 0x3035342E, 0x00000000, 0x0003000E, 0x00000000, 0x00000001,
0x000C000F, 0x00000003, 0x0000161F, 0x6E69616D, 0x00000000, 0x000014B9,
0x00001342, 0x00001536, 0x00000CE6, 0x00000FCC, 0x00001230, 0x00000E98,
0x00030010, 0x0000161F, 0x00000016, 0x00040010, 0x0000161F, 0x00000000,
0x00000001, 0x00030010, 0x0000161F, 0x0000001D, 0x00040010, 0x0000161F,
0x0000001A, 0x00000006, 0x00050048, 0x00000408, 0x00000000, 0x0000000B,
0x00000000, 0x00050048, 0x00000408, 0x00000001, 0x0000000B, 0x00000001,
0x00030047, 0x00000408, 0x00000002, 0x00050048, 0x00000409, 0x00000000,
0x0000000B, 0x00000000, 0x00050048, 0x00000409, 0x00000001, 0x0000000B,
0x00000001, 0x00030047, 0x00000409, 0x00000002, 0x00040047, 0x00001536,
0x0000001E, 0x00000000, 0x00040047, 0x00000CE6, 0x0000001E, 0x00000000,
0x00040047, 0x00000FCC, 0x0000001E, 0x00000010, 0x00040047, 0x00001230,
0x0000001E, 0x00000011, 0x00040047, 0x00000E98, 0x0000001E, 0x00000010,
0x00020013, 0x00000008, 0x00030021, 0x00000502, 0x00000008, 0x00030016,
0x0000000D, 0x00000020, 0x00040017, 0x00000013, 0x0000000D, 0x00000002,
0x00020014, 0x00000009, 0x00040017, 0x0000000F, 0x00000009, 0x00000002,
0x00040017, 0x0000001D, 0x0000000D, 0x00000004, 0x0004001E, 0x00000408,
0x0000001D, 0x0000000D, 0x00040015, 0x0000000B, 0x00000020, 0x00000000,
0x0004002B, 0x0000000B, 0x00000A13, 0x00000003, 0x0004001C, 0x0000085F,
0x00000408, 0x00000A13, 0x00040020, 0x00000ADC, 0x00000001, 0x0000085F,
0x0004003B, 0x00000ADC, 0x000014B9, 0x00000001, 0x00040015, 0x0000000C,
0x00000020, 0x00000001, 0x0004002B, 0x0000000C, 0x00000A0B, 0x00000000,
0x0004002B, 0x0000000C, 0x00000A11, 0x00000002, 0x0004002B, 0x0000000B,
0x00000A0A, 0x00000000, 0x00040020, 0x0000028A, 0x00000001, 0x0000000D,
0x0004002B, 0x0000000C, 0x00000A0E, 0x00000001, 0x0004002B, 0x0000000B,
0x00000A0D, 0x00000001, 0x0004002B, 0x0000000D, 0x00000030, 0x3A83126F,
0x00040020, 0x0000029A, 0x00000001, 0x0000001D, 0x0004001E, 0x00000409,
0x0000001D, 0x0000000D, 0x00040020, 0x00000685, 0x00000003, 0x00000409,
0x0004003B, 0x00000685, 0x00001342, 0x00000003, 0x00040020, 0x0000029B,
0x00000003, 0x0000001D, 0x00040020, 0x0000028B, 0x00000003, 0x0000000D,
0x0004002B, 0x0000000B, 0x00000A3A, 0x00000010, 0x0004001C, 0x000008F6,
0x0000001D, 0x00000A3A, 0x00040020, 0x00000B73, 0x00000003, 0x000008F6,
0x0004003B, 0x00000B73, 0x00001536, 0x00000003, 0x0004001C, 0x0000084A,
0x000008F6, 0x00000A13, 0x00040020, 0x00000AC7, 0x00000001, 0x0000084A,
0x0004003B, 0x00000AC7, 0x00000CE6, 0x00000001, 0x00040020, 0x00000B74,
0x00000001, 0x000008F6, 0x0004002B, 0x0000000C, 0x00000A3B, 0x00000010,
0x0004001C, 0x00000352, 0x00000013, 0x00000A13, 0x00040020, 0x000005CF,
0x00000001, 0x00000352, 0x0004003B, 0x000005CF, 0x00000FCC, 0x00000001,
0x0004001C, 0x00000298, 0x0000000D, 0x00000A13, 0x00040020, 0x00000515,
0x00000001, 0x00000298, 0x0004003B, 0x00000515, 0x00001230, 0x00000001,
0x00040020, 0x00000290, 0x00000003, 0x00000013, 0x0004003B, 0x00000290,
0x00000E98, 0x00000003, 0x0005002C, 0x00000013, 0x000006DF, 0x00000030,
0x00000030, 0x00050036, 0x00000008, 0x0000161F, 0x00000000, 0x00000502,
0x000200F8, 0x00005D6B, 0x00070041, 0x0000028A, 0x00001BD9, 0x000014B9,
0x00000A11, 0x00000A0B, 0x00000A0A, 0x0004003D, 0x0000000D, 0x00003D0B,
0x00001BD9, 0x00070041, 0x0000028A, 0x00004FD7, 0x000014B9, 0x00000A0E,
0x00000A0B, 0x00000A0D, 0x0004003D, 0x0000000D, 0x00004DB1, 0x00004FD7,
0x00050050, 0x00000013, 0x00002AA5, 0x00003D0B, 0x00004DB1, 0x00060041,
0x0000029A, 0x000060C9, 0x000014B9, 0x00000A0B, 0x00000A0B, 0x0004003D,
0x0000001D, 0x00001ECB, 0x000060C9, 0x0007004F, 0x00000013, 0x00001A6D,
0x00001ECB, 0x00001ECB, 0x00000000, 0x00000001, 0x00050083, 0x00000013,
0x00003D7B, 0x00001A6D, 0x00002AA5, 0x0006000C, 0x00000013, 0x00001E4D,
0x00000001, 0x00000004, 0x00003D7B, 0x000500BC, 0x0000000F, 0x00004A4D,
0x00001E4D, 0x000006DF, 0x0004009B, 0x00000009, 0x00003D5F, 0x00004A4D,
0x000400A8, 0x00000009, 0x00002C8A, 0x00003D5F, 0x000300F7, 0x000033E6,
0x00000000, 0x000400FA, 0x00002C8A, 0x00002F61, 0x000033E6, 0x000200F8,
0x00002F61, 0x00070041, 0x0000028A, 0x00004722, 0x000014B9, 0x00000A0E,
0x00000A0B, 0x00000A0A, 0x0004003D, 0x0000000D, 0x00003D0C, 0x00004722,
0x00070041, 0x0000028A, 0x00004FD8, 0x000014B9, 0x00000A11, 0x00000A0B,
0x00000A0D, 0x0004003D, 0x0000000D, 0x00005297, 0x00004FD8, 0x00050050,
0x00000013, 0x000044EB, 0x00003D0C, 0x00005297, 0x00050083, 0x00000013,
0x00003C82, 0x00001A6D, 0x000044EB, 0x0006000C, 0x00000013, 0x00005F56,
0x00000001, 0x00000004, 0x00003C82, 0x000500BC, 0x0000000F, 0x00005050,
0x00005F56, 0x000006DF, 0x0004009B, 0x00000009, 0x00005134, 0x00005050,
0x000200F9, 0x000033E6, 0x000200F8, 0x000033E6, 0x000700F5, 0x00000009,
0x00002AAC, 0x00003D5F, 0x00005D6B, 0x00005134, 0x00002F61, 0x000300F7,
0x00005C60, 0x00000000, 0x000400FA, 0x00002AAC, 0x00002F74, 0x000023E2,
0x000200F8, 0x00002F74, 0x00050041, 0x0000029B, 0x00004675, 0x00001342,
0x00000A0B, 0x0003003E, 0x00004675, 0x00001ECB, 0x00060041, 0x0000028A,
0x00004DC1, 0x000014B9, 0x00000A0B, 0x00000A0E, 0x0004003D, 0x0000000D,
0x00001CDF, 0x00004DC1, 0x00050041, 0x0000028B, 0x00004E0D, 0x00001342,
0x00000A0E, 0x0003003E, 0x00004E0D, 0x00001CDF, 0x00050041, 0x00000B74,
0x00004D88, 0x00000CE6, 0x00000A0B, 0x0004003D, 0x000008F6, 0x00002A7A,
0x00004D88, 0x0003003E, 0x00001536, 0x00002A7A, 0x000100DA, 0x00060041,
0x0000029A, 0x0000591C, 0x000014B9, 0x00000A0E, 0x00000A0B, 0x0004003D,
0x0000001D, 0x00002C86, 0x0000591C, 0x0003003E, 0x00004675, 0x00002C86,
0x00060041, 0x0000028A, 0x000040EE, 0x000014B9, 0x00000A0E, 0x00000A0E,
0x0004003D, 0x0000000D, 0x00001F1F, 0x000040EE, 0x0003003E, 0x00004E0D,
0x00001F1F, 0x00050041, 0x00000B74, 0x000040EF, 0x00000CE6, 0x00000A0E,
0x0004003D, 0x000008F6, 0x00002A7B, 0x000040EF, 0x0003003E, 0x00001536,
0x00002A7B, 0x000100DA, 0x00060041, 0x0000029A, 0x0000591D, 0x000014B9,
0x00000A11, 0x00000A0B, 0x0004003D, 0x0000001D, 0x00002C87, 0x0000591D,
0x0003003E, 0x00004675, 0x00002C87, 0x00060041, 0x0000028A, 0x000040F0,
0x000014B9, 0x00000A11, 0x00000A0E, 0x0004003D, 0x0000000D, 0x00001F20,
0x000040F0, 0x0003003E, 0x00004E0D, 0x00001F20, 0x00050041, 0x00000B74,
0x000040F1, 0x00000CE6, 0x00000A11, 0x0004003D, 0x000008F6, 0x00002A7C,
0x000040F1, 0x0003003E, 0x00001536, 0x00002A7C, 0x000100DA, 0x000100DB,
0x0003003E, 0x00004675, 0x00002C87, 0x0003003E, 0x00004E0D, 0x00001F20,
0x0003003E, 0x00001536, 0x00002A7C, 0x000100DA, 0x0003003E, 0x00004675,
0x00002C86, 0x0003003E, 0x00004E0D, 0x00001F1F, 0x0003003E, 0x00001536,
0x00002A7B, 0x000100DA, 0x0004007F, 0x00000013, 0x00002293, 0x00001A6D,
0x0007004F, 0x00000013, 0x000035BD, 0x00002C86, 0x00002C86, 0x00000000,
0x00000001, 0x00050081, 0x00000013, 0x000053D1, 0x00002293, 0x000035BD,
0x0007004F, 0x00000013, 0x00001D0A, 0x00002C87, 0x00002C87, 0x00000000,
0x00000001, 0x00050081, 0x00000013, 0x00005534, 0x000053D1, 0x00001D0A,
0x00050051, 0x0000000D, 0x000047F7, 0x00005534, 0x00000000, 0x00050051,
0x0000000D, 0x00003707, 0x00005534, 0x00000001, 0x00050051, 0x0000000D,
0x00001DD9, 0x00002C87, 0x00000002, 0x00050051, 0x0000000D, 0x00001D30,
0x00002C87, 0x00000003, 0x00070050, 0x0000001D, 0x0000495B, 0x000047F7,
0x00003707, 0x00001DD9, 0x00001D30, 0x0003003E, 0x00004675, 0x0000495B,
0x0003003E, 0x00004E0D, 0x00001F20, 0x000200F9, 0x000043D4, 0x000200F8,
0x000043D4, 0x000700F5, 0x0000000C, 0x000059AE, 0x00000A0B, 0x00002F74,
0x00005335, 0x000038D7, 0x000500B1, 0x00000009, 0x000060D4, 0x000059AE,
0x00000A3B, 0x000400F6, 0x00002E10, 0x000038D7, 0x00000000, 0x000400FA,
0x000060D4, 0x000038D7, 0x00002E10, 0x000200F8, 0x000038D7, 0x00060041,
0x0000029A, 0x00004BBC, 0x00000CE6, 0x00000A0B, 0x000059AE, 0x0004003D,
0x0000001D, 0x00005DF0, 0x00004BBC, 0x0004007F, 0x0000001D, 0x00004DA8,
0x00005DF0, 0x00060041, 0x0000029A, 0x00001A0B, 0x00000CE6, 0x00000A0E,
0x000059AE, 0x0004003D, 0x0000001D, 0x00001AAC, 0x00001A0B, 0x00050081,
0x0000001D, 0x00005825, 0x00004DA8, 0x00001AAC, 0x00060041, 0x0000029A,
0x0000495F, 0x00000CE6, 0x00000A11, 0x000059AE, 0x0004003D, 0x0000001D,
0x0000523F, 0x0000495F, 0x00050081, 0x0000001D, 0x00005838, 0x00005825,
0x0000523F, 0x00050041, 0x0000029B, 0x0000489F, 0x00001536, 0x000059AE,
0x0003003E, 0x0000489F, 0x00005838, 0x00050080, 0x0000000C, 0x00005335,
0x000059AE, 0x00000A0E, 0x000200F9, 0x000043D4, 0x000200F8, 0x00002E10,
0x000100DA, 0x000100DB, 0x000200F9, 0x00005C60, 0x000200F8, 0x000023E2,
0x00050041, 0x0000029B, 0x00004FEB, 0x00001342, 0x00000A0B, 0x0003003E,
0x00004FEB, 0x00001ECB, 0x00060041, 0x0000028A, 0x00004DC2, 0x000014B9,
0x00000A0B, 0x00000A0E, 0x0004003D, 0x0000000D, 0x00001CE0, 0x00004DC2,
0x00050041, 0x0000028B, 0x00004E0E, 0x00001342, 0x00000A0E, 0x0003003E,
0x00004E0E, 0x00001CE0, 0x00050041, 0x00000B74, 0x00004D89, 0x00000CE6,
0x00000A0B, 0x0004003D, 0x000008F6, 0x00002A7D, 0x00004D89, 0x0003003E,
0x00001536, 0x00002A7D, 0x000100DA, 0x00060041, 0x0000029A, 0x0000591E,
0x000014B9, 0x00000A0E, 0x00000A0B, 0x0004003D, 0x0000001D, 0x00002C88,
0x0000591E, 0x0003003E, 0x00004FEB, 0x00002C88, 0x00060041, 0x0000028A,
0x000040F2, 0x000014B9, 0x00000A0E, 0x00000A0E, 0x0004003D, 0x0000000D,
0x00001F21, 0x000040F2, 0x0003003E, 0x00004E0E, 0x00001F21, 0x00050041,
0x00000B74, 0x000040F3, 0x00000CE6, 0x00000A0E, 0x0004003D, 0x000008F6,
0x00002A7E, 0x000040F3, 0x0003003E, 0x00001536, 0x00002A7E, 0x000100DA,
0x00060041, 0x0000029A, 0x0000591F, 0x000014B9, 0x00000A11, 0x00000A0B,
0x0004003D, 0x0000001D, 0x00002C89, 0x0000591F, 0x0003003E, 0x00004FEB,
0x00002C89, 0x00060041, 0x0000028A, 0x000040F4, 0x000014B9, 0x00000A11,
0x00000A0E, 0x0004003D, 0x0000000D, 0x00001F22, 0x000040F4, 0x0003003E,
0x00004E0E, 0x00001F22, 0x00050041, 0x00000B74, 0x000040F5, 0x00000CE6,
0x00000A11, 0x0004003D, 0x000008F6, 0x00002A7F, 0x000040F5, 0x0003003E,
0x00001536, 0x00002A7F, 0x000100DA, 0x000100DB, 0x0003003E, 0x00004FEB,
0x00001ECB, 0x0003003E, 0x00004E0E, 0x00001CE0, 0x0003003E, 0x00001536,
0x00002A7D, 0x000100DA, 0x0003003E, 0x00004FEB, 0x00002C89, 0x0003003E,
0x00004E0E, 0x00001F22, 0x0003003E, 0x00001536, 0x00002A7F, 0x000100DA,
0x0007004F, 0x00000013, 0x00003067, 0x00002C88, 0x00002C88, 0x00000000,
0x00000001, 0x0004007F, 0x00000013, 0x000052E6, 0x00003067, 0x00050081,
0x00000013, 0x0000208F, 0x00001A6D, 0x000052E6, 0x0007004F, 0x00000013,
0x00003625, 0x00002C89, 0x00002C89, 0x00000000, 0x00000001, 0x00050081,
0x00000013, 0x00005535, 0x0000208F, 0x00003625, 0x00050051, 0x0000000D,
0x000047F8, 0x00005535, 0x00000000, 0x00050051, 0x0000000D, 0x00003708,
0x00005535, 0x00000001, 0x00050051, 0x0000000D, 0x00001DDA, 0x00002C89,
0x00000002, 0x00050051, 0x0000000D, 0x00001D31, 0x00002C89, 0x00000003,
0x00070050, 0x0000001D, 0x0000495C, 0x000047F8, 0x00003708, 0x00001DDA,
0x00001D31, 0x0003003E, 0x00004FEB, 0x0000495C, 0x0003003E, 0x00004E0E,
0x00001F22, 0x000200F9, 0x000043D5, 0x000200F8, 0x000043D5, 0x000700F5,
0x0000000C, 0x000059AF, 0x00000A0B, 0x000023E2, 0x00005336, 0x000038D8,
0x000500B1, 0x00000009, 0x000060D5, 0x000059AF, 0x00000A3B, 0x000400F6,
0x00002E11, 0x000038D8, 0x00000000, 0x000400FA, 0x000060D5, 0x000038D8,
0x00002E11, 0x000200F8, 0x000038D8, 0x00060041, 0x0000029A, 0x00004723,
0x00000CE6, 0x00000A0B, 0x000059AF, 0x0004003D, 0x0000001D, 0x00003D0D,
0x00004723, 0x00060041, 0x0000029A, 0x00005354, 0x00000CE6, 0x00000A0E,
0x000059AF, 0x0004003D, 0x0000001D, 0x000032AE, 0x00005354, 0x0004007F,
0x0000001D, 0x000022B4, 0x000032AE, 0x00050081, 0x0000001D, 0x00001EB6,
0x00003D0D, 0x000022B4, 0x00060041, 0x0000029A, 0x00003777, 0x00000CE6,
0x00000A11, 0x000059AF, 0x0004003D, 0x0000001D, 0x00005240, 0x00003777,
0x00050081, 0x0000001D, 0x00005839, 0x00001EB6, 0x00005240, 0x00050041,
0x0000029B, 0x000048A0, 0x00001536, 0x000059AF, 0x0003003E, 0x000048A0,
0x00005839, 0x00050080, 0x0000000C, 0x00005336, 0x000059AF, 0x00000A0E,
0x000200F9, 0x000043D5, 0x000200F8, 0x00002E11, 0x000100DA, 0x000100DB,
0x000200F9, 0x00005C60, 0x000200F8, 0x00005C60, 0x000100FD, 0x00010038,
};

View File

@@ -1,35 +0,0 @@
// NOTE: This file is compiled and embedded into the exe.
// Use `xenia-build genspirv` and check in any changes under bin/.
#version 450 core
#extension all : warn
#extension GL_ARB_shading_language_420pack : require
#extension GL_ARB_separate_shader_objects : require
#extension GL_ARB_explicit_attrib_location : require
layout(set = 0, binding = 1) uniform consts_type {
vec4 float_consts[512];
uint loop_consts[32];
uint bool_consts[8];
} consts;
layout(push_constant) uniform push_consts_type {
vec4 window_scale;
vec4 vtx_fmt;
vec4 point_size;
vec4 alpha_test;
uint ps_param_gen;
} push_constants;
layout(set = 1, binding = 0) uniform sampler1D textures1D[32];
layout(set = 1, binding = 1) uniform sampler2D textures2D[32];
layout(set = 1, binding = 2) uniform sampler3D textures3D[32];
layout(set = 1, binding = 3) uniform samplerCube textures4D[32];
layout(location = 0) in vec4 in_interpolators[16];
layout(location = 0) out vec4 oC[4];
void main() {
// This shader does absolutely nothing!
return;
}

View File

@@ -1,53 +0,0 @@
// NOTE: This file is compiled and embedded into the exe.
// Use `xenia-build genspirv` and check in any changes under bin/.
#version 450 core
#extension all : warn
#extension GL_ARB_separate_shader_objects : require
#extension GL_ARB_explicit_attrib_location : require
in gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
// float gl_ClipDistance[];
} gl_in[];
out gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
// float gl_ClipDistance[];
};
layout(location = 0) in vec4 in_interpolators[][16];
layout(location = 0) out vec4 out_interpolators[16];
layout(location = 16) in vec2 _in_point_coord_unused[];
layout(location = 17) in float _in_point_size_unused[];
layout(location = 16) out vec2 _out_point_coord_unused;
layout(lines_adjacency) in;
layout(line_strip, max_vertices = 5) out;
void main() {
gl_Position = gl_in[0].gl_Position;
gl_PointSize = gl_in[0].gl_PointSize;
out_interpolators = in_interpolators[0];
EmitVertex();
gl_Position = gl_in[1].gl_Position;
gl_PointSize = gl_in[1].gl_PointSize;
out_interpolators = in_interpolators[1];
EmitVertex();
gl_Position = gl_in[2].gl_Position;
gl_PointSize = gl_in[2].gl_PointSize;
out_interpolators = in_interpolators[2];
EmitVertex();
gl_Position = gl_in[3].gl_Position;
gl_PointSize = gl_in[3].gl_PointSize;
out_interpolators = in_interpolators[3];
EmitVertex();
gl_Position = gl_in[0].gl_Position;
gl_PointSize = gl_in[0].gl_PointSize;
out_interpolators = in_interpolators[0];
EmitVertex();
EndPrimitive();
}

View File

@@ -1,63 +0,0 @@
// NOTE: This file is compiled and embedded into the exe.
// Use `xenia-build genspirv` and check in any changes under bin/.
#version 450 core
#extension all : warn
#extension GL_ARB_shading_language_420pack : require
#extension GL_ARB_separate_shader_objects : require
#extension GL_ARB_explicit_attrib_location : require
layout(push_constant) uniform push_consts_type {
vec4 window_scale;
vec4 vtx_fmt;
vec4 point_size;
vec4 alpha_test;
uint ps_param_gen;
} push_constants;
in gl_PerVertex {
vec4 gl_Position;
// float gl_ClipDistance[];
} gl_in[];
out gl_PerVertex {
vec4 gl_Position;
// float gl_ClipDistance[];
};
layout(location = 0) in vec4 in_interpolators[][16];
layout(location = 16) in vec2 in_point_coord_unused[];
layout(location = 17) in float point_size[];
layout(location = 0) out vec4 out_interpolators[16];
layout(location = 16) out vec2 point_coord;
// TODO(benvanik): clamp to min/max.
// TODO(benvanik): figure out how to see which interpolator gets adjusted.
layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
void main() {
const vec2 offsets[4] = {
vec2(-1.0, 1.0),
vec2( 1.0, 1.0),
vec2(-1.0, -1.0),
vec2( 1.0, -1.0),
};
vec4 pos = gl_in[0].gl_Position;
vec2 window_scaled_psize = push_constants.point_size.xy;
// Shader header writes -1.0f to pointSize by default, so any positive value
// means that it was overwritten by the translated vertex shader.
if (point_size[0] > 0.0f) {
window_scaled_psize = vec2(point_size[0]);
}
window_scaled_psize /= push_constants.window_scale.zw;
for (int i = 0; i < 4; ++i) {
gl_Position = vec4(pos.xy + (offsets[i] * window_scaled_psize), pos.zw);
out_interpolators = in_interpolators[0];
point_coord = max(offsets[i], vec2(0.0f));
EmitVertex();
}
EndPrimitive();
}

View File

@@ -1,42 +0,0 @@
// NOTE: This file is compiled and embedded into the exe.
// Use `xenia-build genspirv` and check in any changes under bin/.
#version 450 core
#extension all : warn
#extension GL_ARB_shading_language_420pack : require
#extension GL_ARB_separate_shader_objects : require
#extension GL_ARB_explicit_attrib_location : require
in gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
// float gl_ClipDistance[];
} gl_in[];
out gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
// float gl_ClipDistance[];
};
layout(location = 0) in vec4 in_interpolators[][16];
layout(location = 0) out vec4 out_interpolators[16];
layout(location = 16) in vec2 _in_point_coord_unused[];
layout(location = 17) in float _in_point_size_unused[];
layout(location = 16) out vec2 _out_point_coord_unused;
layout(lines_adjacency) in;
layout(triangle_strip, max_vertices = 4) out;
void main() {
const int order[4] = { 0, 1, 3, 2 };
for (int i = 0; i < 4; ++i) {
int input_index = order[i];
gl_Position = gl_in[input_index].gl_Position;
gl_PointSize = gl_in[input_index].gl_PointSize;
out_interpolators = in_interpolators[input_index];
EmitVertex();
}
EndPrimitive();
}

View File

@@ -1,124 +0,0 @@
// NOTE: This file is compiled and embedded into the exe.
// Use `xenia-build genspirv` and check in any changes under bin/.
#version 450 core
#extension all : warn
#extension GL_ARB_separate_shader_objects : require
#extension GL_ARB_explicit_attrib_location : require
in gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
// float gl_ClipDistance[];
} gl_in[];
out gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
// float gl_ClipDistance[];
};
layout(location = 0) in vec4 in_interpolators[][16];
layout(location = 0) out vec4 out_interpolators[16];
layout(location = 16) in vec2 _in_point_coord_unused[];
layout(location = 17) in float _in_point_size_unused[];
layout(location = 16) out vec2 _out_point_coord_unused;
layout(triangles) in;
layout(triangle_strip, max_vertices = 6) out;
bool equalsEpsilon(vec2 left, vec2 right, float epsilon) {
return all(lessThanEqual(abs(left - right), vec2(epsilon)));
}
void main() {
// Most games use a left-aligned form.
if (equalsEpsilon(gl_in[0].gl_Position.xy, vec2(gl_in[2].gl_Position.x, gl_in[1].gl_Position.y), 0.001) ||
equalsEpsilon(gl_in[0].gl_Position.xy, vec2(gl_in[1].gl_Position.x, gl_in[2].gl_Position.y), 0.001)) {
// 0 ------ 1 0: -1,-1
// | - | 1: 1,-1
// | // | 2: -1, 1
// | - | 3: [ 1, 1 ]
// 2 ----- [3]
//
// 0 ------ 2 0: -1,-1
// | - | 1: -1, 1
// | // | 2: 1,-1
// | - | 3: [ 1, 1 ]
// 1 ------[3]
gl_Position = gl_in[0].gl_Position;
gl_PointSize = gl_in[0].gl_PointSize;
out_interpolators = in_interpolators[0];
EmitVertex();
gl_Position = gl_in[1].gl_Position;
gl_PointSize = gl_in[1].gl_PointSize;
out_interpolators = in_interpolators[1];
EmitVertex();
gl_Position = gl_in[2].gl_Position;
gl_PointSize = gl_in[2].gl_PointSize;
out_interpolators = in_interpolators[2];
EmitVertex();
EndPrimitive();
gl_Position = gl_in[2].gl_Position;
gl_PointSize = gl_in[2].gl_PointSize;
out_interpolators = in_interpolators[2];
EmitVertex();
gl_Position = gl_in[1].gl_Position;
gl_PointSize = gl_in[1].gl_PointSize;
out_interpolators = in_interpolators[1];
EmitVertex();
gl_Position = vec4((-gl_in[0].gl_Position.xy) +
gl_in[1].gl_Position.xy +
gl_in[2].gl_Position.xy,
gl_in[2].gl_Position.zw);
gl_PointSize = gl_in[2].gl_PointSize;
for (int i = 0; i < 16; ++i) {
out_interpolators[i] = (-in_interpolators[0][i]) +
in_interpolators[1][i] +
in_interpolators[2][i];
}
EmitVertex();
EndPrimitive();
} else {
// 0 ------ 1 0: -1,-1
// | - | 1: 1,-1
// | \\ | 2: 1, 1
// | - | 3: [-1, 1 ]
// [3] ----- 2
gl_Position = gl_in[0].gl_Position;
gl_PointSize = gl_in[0].gl_PointSize;
out_interpolators = in_interpolators[0];
EmitVertex();
gl_Position = gl_in[1].gl_Position;
gl_PointSize = gl_in[1].gl_PointSize;
out_interpolators = in_interpolators[1];
EmitVertex();
gl_Position = gl_in[2].gl_Position;
gl_PointSize = gl_in[2].gl_PointSize;
out_interpolators = in_interpolators[2];
EmitVertex();
EndPrimitive();
gl_Position = gl_in[0].gl_Position;
gl_PointSize = gl_in[0].gl_PointSize;
out_interpolators = in_interpolators[0];
EmitVertex();
gl_Position = gl_in[2].gl_Position;
gl_PointSize = gl_in[2].gl_PointSize;
out_interpolators = in_interpolators[2];
EmitVertex();
gl_Position = vec4( gl_in[0].gl_Position.xy +
(-gl_in[1].gl_Position.xy) +
gl_in[2].gl_Position.xy,
gl_in[2].gl_Position.zw);
gl_PointSize = gl_in[2].gl_PointSize;
for (int i = 0; i < 16; ++i) {
out_interpolators[i] = in_interpolators[0][i] +
(-in_interpolators[1][i]) +
in_interpolators[2][i];
}
EmitVertex();
EndPrimitive();
}
}

View File

@@ -1,146 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/vulkan/texture_config.h"
namespace xe {
namespace gpu {
namespace vulkan {
#define COMP_SWIZ(r, g, b, a) \
{ \
VK_COMPONENT_SWIZZLE_##r, VK_COMPONENT_SWIZZLE_##g, \
VK_COMPONENT_SWIZZLE_##b, VK_COMPONENT_SWIZZLE_##a \
}
#define VEC_SWIZ(x, y, z, w) \
{ \
VECTOR_SWIZZLE_##x, VECTOR_SWIZZLE_##y, VECTOR_SWIZZLE_##z, \
VECTOR_SWIZZLE_##w \
}
#define RGBA COMP_SWIZ(R, G, B, A)
#define ___R COMP_SWIZ(IDENTITY, IDENTITY, IDENTITY, R)
#define RRRR COMP_SWIZ(R, R, R, R)
#define XYZW VEC_SWIZ(X, Y, Z, W)
#define YXWZ VEC_SWIZ(Y, X, W, Z)
#define ZYXW VEC_SWIZ(Z, Y, X, W)
#define ___(format) \
{ VK_FORMAT_##format }
#define _c_(format, component_swizzle) \
{ VK_FORMAT_##format, component_swizzle, XYZW }
#define __v(format, vector_swizzle) \
{ VK_FORMAT_##format, RGBA, vector_swizzle }
#define _cv(format, component_swizzle, vector_swizzle) \
{ VK_FORMAT_##format, component_swizzle, vector_swizzle }
// https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkFormat.html
const TextureConfig texture_configs[64] = {
/* k_1_REVERSE */ ___(UNDEFINED),
/* k_1 */ ___(UNDEFINED),
/* k_8 */ ___(R8_UNORM),
/* k_1_5_5_5 */ __v(A1R5G5B5_UNORM_PACK16, ZYXW),
/* k_5_6_5 */ __v(R5G6B5_UNORM_PACK16, ZYXW),
/* k_6_5_5 */ ___(UNDEFINED),
/* k_8_8_8_8 */ ___(R8G8B8A8_UNORM),
/* k_2_10_10_10 */ ___(A2R10G10B10_UNORM_PACK32),
/* k_8_A */ ___(R8_UNORM),
/* k_8_B */ ___(UNDEFINED),
/* k_8_8 */ ___(R8G8_UNORM),
/* k_Cr_Y1_Cb_Y0_REP */ ___(UNDEFINED),
/* k_Y1_Cr_Y0_Cb_REP */ ___(UNDEFINED),
/* k_16_16_EDRAM */ ___(UNDEFINED),
/* k_8_8_8_8_A */ ___(UNDEFINED),
/* k_4_4_4_4 */ __v(R4G4B4A4_UNORM_PACK16, YXWZ),
// TODO: Verify if these two are correct (I think not).
/* k_10_11_11 */ ___(B10G11R11_UFLOAT_PACK32),
/* k_11_11_10 */ ___(B10G11R11_UFLOAT_PACK32),
/* k_DXT1 */ ___(BC1_RGBA_UNORM_BLOCK),
/* k_DXT2_3 */ ___(BC2_UNORM_BLOCK),
/* k_DXT4_5 */ ___(BC3_UNORM_BLOCK),
/* k_16_16_16_16_EDRAM */ ___(UNDEFINED),
// TODO: D24 unsupported on AMD.
/* k_24_8 */ ___(D24_UNORM_S8_UINT),
/* k_24_8_FLOAT */ ___(D32_SFLOAT_S8_UINT),
/* k_16 */ ___(R16_UNORM),
/* k_16_16 */ ___(R16G16_UNORM),
/* k_16_16_16_16 */ ___(R16G16B16A16_UNORM),
/* k_16_EXPAND */ ___(R16_SFLOAT),
/* k_16_16_EXPAND */ ___(R16G16_SFLOAT),
/* k_16_16_16_16_EXPAND */ ___(R16G16B16A16_SFLOAT),
/* k_16_FLOAT */ ___(R16_SFLOAT),
/* k_16_16_FLOAT */ ___(R16G16_SFLOAT),
/* k_16_16_16_16_FLOAT */ ___(R16G16B16A16_SFLOAT),
// ! These are UNORM formats, not SINT.
/* k_32 */ ___(R32_SINT),
/* k_32_32 */ ___(R32G32_SINT),
/* k_32_32_32_32 */ ___(R32G32B32A32_SINT),
/* k_32_FLOAT */ ___(R32_SFLOAT),
/* k_32_32_FLOAT */ ___(R32G32_SFLOAT),
/* k_32_32_32_32_FLOAT */ ___(R32G32B32A32_SFLOAT),
/* k_32_AS_8 */ ___(UNDEFINED),
/* k_32_AS_8_8 */ ___(UNDEFINED),
/* k_16_MPEG */ ___(UNDEFINED),
/* k_16_16_MPEG */ ___(UNDEFINED),
/* k_8_INTERLACED */ ___(UNDEFINED),
/* k_32_AS_8_INTERLACED */ ___(UNDEFINED),
/* k_32_AS_8_8_INTERLACED */ ___(UNDEFINED),
/* k_16_INTERLACED */ ___(UNDEFINED),
/* k_16_MPEG_INTERLACED */ ___(UNDEFINED),
/* k_16_16_MPEG_INTERLACED */ ___(UNDEFINED),
// https://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf
/* k_DXN */ ___(BC5_UNORM_BLOCK), // ?
/* k_8_8_8_8_AS_16_16_16_16 */ ___(R8G8B8A8_UNORM),
/* k_DXT1_AS_16_16_16_16 */ ___(BC1_RGBA_UNORM_BLOCK),
/* k_DXT2_3_AS_16_16_16_16 */ ___(BC2_UNORM_BLOCK),
/* k_DXT4_5_AS_16_16_16_16 */ ___(BC3_UNORM_BLOCK),
/* k_2_10_10_10_AS_16_16_16_16 */ ___(A2R10G10B10_UNORM_PACK32),
// TODO: Verify if these two are correct (I think not).
/* k_10_11_11_AS_16_16_16_16 */ ___(B10G11R11_UFLOAT_PACK32), // ?
/* k_11_11_10_AS_16_16_16_16 */ ___(B10G11R11_UFLOAT_PACK32), // ?
/* k_32_32_32_FLOAT */ ___(R32G32B32_SFLOAT),
/* k_DXT3A */ _c_(BC2_UNORM_BLOCK, ___R),
/* k_DXT5A */ _c_(BC4_UNORM_BLOCK, RRRR), // ATI1N
// https://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf
/* k_CTX1 */ ___(R8G8_UINT),
/* k_DXT3A_AS_1_1_1_1 */ ___(UNDEFINED),
/* k_8_8_8_8_GAMMA_EDRAM */ ___(UNDEFINED),
/* k_2_10_10_10_FLOAT_EDRAM */ ___(UNDEFINED),
};
#undef _cv
#undef __v
#undef _c_
#undef ___
#undef ZYXW
#undef YXWZ
#undef XYZW
#undef RRRR
#undef ___R
#undef RGBA
#undef VEC_SWIZ
#undef COMP_SWIZ
} // namespace vulkan
} // namespace gpu
} // namespace xe

View File

@@ -1,50 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_TEXTURE_CONFIG_H_
#define XENIA_GPU_VULKAN_TEXTURE_CONFIG_H_
#include "xenia/gpu/texture_info.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace gpu {
namespace vulkan {
typedef enum VectorSwizzle {
VECTOR_SWIZZLE_X = 0,
VECTOR_SWIZZLE_Y = 1,
VECTOR_SWIZZLE_Z = 2,
VECTOR_SWIZZLE_W = 3,
} VectorSwizzle;
struct TextureConfig {
VkFormat host_format;
struct {
VkComponentSwizzle r = VK_COMPONENT_SWIZZLE_R;
VkComponentSwizzle g = VK_COMPONENT_SWIZZLE_G;
VkComponentSwizzle b = VK_COMPONENT_SWIZZLE_B;
VkComponentSwizzle a = VK_COMPONENT_SWIZZLE_A;
} component_swizzle;
struct {
VectorSwizzle x = VECTOR_SWIZZLE_X;
VectorSwizzle y = VECTOR_SWIZZLE_Y;
VectorSwizzle z = VECTOR_SWIZZLE_Z;
VectorSwizzle w = VECTOR_SWIZZLE_W;
} vector_swizzle;
};
extern const TextureConfig texture_configs[64];
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_TEXTURE_CONFIG_H_

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -10,69 +10,264 @@
#ifndef XENIA_GPU_VULKAN_VULKAN_COMMAND_PROCESSOR_H_
#define XENIA_GPU_VULKAN_VULKAN_COMMAND_PROCESSOR_H_
#include <atomic>
#include <array>
#include <climits>
#include <cstdint>
#include <cstring>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "xenia/base/threading.h"
#include "xenia/base/assert.h"
#include "xenia/base/hash.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/vulkan/buffer_cache.h"
#include "xenia/gpu/vulkan/render_cache.h"
#include "xenia/gpu/draw_util.h"
#include "xenia/gpu/registers.h"
#include "xenia/gpu/spirv_shader_translator.h"
#include "xenia/gpu/vulkan/deferred_command_buffer.h"
#include "xenia/gpu/vulkan/vulkan_graphics_system.h"
#include "xenia/gpu/vulkan/vulkan_pipeline_cache.h"
#include "xenia/gpu/vulkan/vulkan_primitive_processor.h"
#include "xenia/gpu/vulkan/vulkan_render_target_cache.h"
#include "xenia/gpu/vulkan/vulkan_shader.h"
#include "xenia/gpu/vulkan/vulkan_shared_memory.h"
#include "xenia/gpu/vulkan/vulkan_texture_cache.h"
#include "xenia/gpu/xenos.h"
#include "xenia/kernel/xthread.h"
#include "xenia/memory.h"
#include "xenia/ui/vulkan/blitter.h"
#include "xenia/ui/vulkan/fenced_pools.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/ui/vulkan/single_type_descriptor_set_allocator.h"
#include "xenia/ui/vulkan/vulkan_presenter.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_submission_tracker.h"
#include "xenia/ui/vulkan/vulkan_util.h"
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
namespace xe {
namespace gpu {
namespace vulkan {
class VulkanTextureCache;
class VulkanCommandProcessor : public CommandProcessor {
public:
// Single-descriptor layouts for use within a single frame.
enum class SingleTransientDescriptorLayout {
kUniformBufferGuestVertex,
kUniformBufferFragment,
kUniformBufferGuestShader,
kUniformBufferSystemConstants,
kUniformBufferCompute,
kStorageBufferCompute,
kCount,
};
class ScratchBufferAcquisition {
public:
explicit ScratchBufferAcquisition() = default;
explicit ScratchBufferAcquisition(VulkanCommandProcessor& command_processor,
VkBuffer buffer,
VkPipelineStageFlags stage_mask,
VkAccessFlags access_mask)
: command_processor_(&command_processor),
buffer_(buffer),
stage_mask_(stage_mask),
access_mask_(access_mask) {}
ScratchBufferAcquisition(const ScratchBufferAcquisition& acquisition) =
delete;
ScratchBufferAcquisition& operator=(
const ScratchBufferAcquisition& acquisition) = delete;
ScratchBufferAcquisition(ScratchBufferAcquisition&& acquisition) {
command_processor_ = acquisition.command_processor_;
buffer_ = acquisition.buffer_;
stage_mask_ = acquisition.stage_mask_;
access_mask_ = acquisition.access_mask_;
acquisition.command_processor_ = nullptr;
acquisition.buffer_ = VK_NULL_HANDLE;
acquisition.stage_mask_ = 0;
acquisition.access_mask_ = 0;
}
ScratchBufferAcquisition& operator=(
ScratchBufferAcquisition&& acquisition) {
if (this == &acquisition) {
return *this;
}
command_processor_ = acquisition.command_processor_;
buffer_ = acquisition.buffer_;
stage_mask_ = acquisition.stage_mask_;
access_mask_ = acquisition.access_mask_;
acquisition.command_processor_ = nullptr;
acquisition.buffer_ = VK_NULL_HANDLE;
acquisition.stage_mask_ = 0;
acquisition.access_mask_ = 0;
return *this;
}
~ScratchBufferAcquisition() {
if (buffer_ != VK_NULL_HANDLE) {
assert_true(command_processor_->scratch_buffer_used_);
assert_true(command_processor_->scratch_buffer_ == buffer_);
command_processor_->scratch_buffer_last_stage_mask_ = stage_mask_;
command_processor_->scratch_buffer_last_access_mask_ = access_mask_;
command_processor_->scratch_buffer_last_usage_submission_ =
command_processor_->GetCurrentSubmission();
command_processor_->scratch_buffer_used_ = false;
}
}
// VK_NULL_HANDLE if failed to acquire or if moved.
VkBuffer buffer() const { return buffer_; }
VkPipelineStageFlags GetStageMask() const { return stage_mask_; }
VkPipelineStageFlags SetStageMask(VkPipelineStageFlags new_stage_mask) {
VkPipelineStageFlags old_stage_mask = stage_mask_;
stage_mask_ = new_stage_mask;
return old_stage_mask;
}
VkAccessFlags GetAccessMask() const { return access_mask_; }
VkAccessFlags SetAccessMask(VkAccessFlags new_access_mask) {
VkAccessFlags old_access_mask = access_mask_;
access_mask_ = new_access_mask;
return old_access_mask;
}
private:
VulkanCommandProcessor* command_processor_ = nullptr;
VkBuffer buffer_ = VK_NULL_HANDLE;
VkPipelineStageFlags stage_mask_ = 0;
VkAccessFlags access_mask_ = 0;
};
VulkanCommandProcessor(VulkanGraphicsSystem* graphics_system,
kernel::KernelState* kernel_state);
~VulkanCommandProcessor() override;
~VulkanCommandProcessor();
void RequestFrameTrace(const std::filesystem::path& root_path) override;
void TracePlaybackWroteMemory(uint32_t base_ptr, uint32_t length) override;
void RestoreEdramSnapshot(const void* snapshot) override;
void ClearCaches() override;
void TracePlaybackWroteMemory(uint32_t base_ptr, uint32_t length) override;
void RestoreEdramSnapshot(const void* snapshot) override;
ui::vulkan::VulkanProvider& GetVulkanProvider() const {
return *static_cast<ui::vulkan::VulkanProvider*>(
graphics_system_->provider());
}
RenderCache* render_cache() { return render_cache_.get(); }
// Returns the deferred drawing command list for the currently open
// submission.
DeferredCommandBuffer& deferred_command_buffer() {
assert_true(submission_open_);
return deferred_command_buffer_;
}
private:
bool submission_open() const { return submission_open_; }
uint64_t GetCurrentSubmission() const {
return submission_completed_ +
uint64_t(submissions_in_flight_fences_.size()) + 1;
}
uint64_t GetCompletedSubmission() const { return submission_completed_; }
// Sparse binds are:
// - In a single submission, all submitted in one vkQueueBindSparse.
// - Sent to the queue without waiting for a semaphore.
// Thus, multiple sparse binds between the completed and the current
// submission, and within one submission, must not touch any overlapping
// memory regions.
void SparseBindBuffer(VkBuffer buffer, uint32_t bind_count,
const VkSparseMemoryBind* binds,
VkPipelineStageFlags wait_stage_mask);
uint64_t GetCurrentFrame() const { return frame_current_; }
uint64_t GetCompletedFrame() const { return frame_completed_; }
// Submission must be open to insert barriers. If no pipeline stages access
// the resource in a synchronization scope, the stage masks should be 0 (top /
// bottom of pipe should be specified only if explicitly needed). Returning
// true if the barrier has actually been inserted and not dropped.
bool PushBufferMemoryBarrier(
VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size,
VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask,
uint32_t src_queue_family_index = VK_QUEUE_FAMILY_IGNORED,
uint32_t dst_queue_family_index = VK_QUEUE_FAMILY_IGNORED,
bool skip_if_equal = true);
bool PushImageMemoryBarrier(
VkImage image, const VkImageSubresourceRange& subresource_range,
VkPipelineStageFlags src_stage_mask, VkPipelineStageFlags dst_stage_mask,
VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask,
VkImageLayout old_layout, VkImageLayout new_layout,
uint32_t src_queue_family_index = VK_QUEUE_FAMILY_IGNORED,
uint32_t dst_queue_family_index = VK_QUEUE_FAMILY_IGNORED,
bool skip_if_equal = true);
// Returns whether any barriers have been submitted - if true is returned, the
// render pass will also be closed.
bool SubmitBarriers(bool force_end_render_pass);
// If not started yet, begins a render pass from the render target cache.
// Submission must be open.
void SubmitBarriersAndEnterRenderTargetCacheRenderPass(
VkRenderPass render_pass,
const VulkanRenderTargetCache::Framebuffer* framebuffer);
// Must be called before doing anything outside the render pass scope,
// including adding pipeline barriers that are not a part of the render pass
// scope. Submission must be open.
void EndRenderPass();
VkDescriptorSetLayout GetSingleTransientDescriptorLayout(
SingleTransientDescriptorLayout transient_descriptor_layout) const {
return descriptor_set_layouts_single_transient_[size_t(
transient_descriptor_layout)];
}
// A frame must be open.
VkDescriptorSet AllocateSingleTransientDescriptor(
SingleTransientDescriptorLayout transient_descriptor_layout);
// Allocates a descriptor, space in the uniform buffer pool, and fills the
// VkWriteDescriptorSet structure and VkDescriptorBufferInfo referenced by it.
// Returns null in case of failure.
uint8_t* WriteTransientUniformBufferBinding(
size_t size, SingleTransientDescriptorLayout transient_descriptor_layout,
VkDescriptorBufferInfo& descriptor_buffer_info_out,
VkWriteDescriptorSet& write_descriptor_set_out);
uint8_t* WriteTransientUniformBufferBinding(
size_t size, SingleTransientDescriptorLayout transient_descriptor_layout,
VkDescriptorSet& descriptor_set_out);
// The returned reference is valid until a cache clear.
VkDescriptorSetLayout GetTextureDescriptorSetLayout(bool is_samplers,
bool is_vertex,
size_t binding_count);
// The returned reference is valid until a cache clear.
const VulkanPipelineCache::PipelineLayoutProvider* GetPipelineLayout(
size_t texture_count_pixel, size_t sampler_count_pixel,
size_t texture_count_vertex, size_t sampler_count_vertex);
// Returns a single temporary GPU-side buffer within a submission for tasks
// like texture untiling and resolving. May push a buffer memory barrier into
// the initial usage. Submission must be open.
ScratchBufferAcquisition AcquireScratchGpuBuffer(
VkDeviceSize size, VkPipelineStageFlags initial_stage_mask,
VkAccessFlags initial_access_mask);
// Binds a graphics pipeline for host-specific purposes, invalidating the
// affected state. keep_dynamic_* must be false (to invalidate the dynamic
// state after binding the pipeline with the same state being static, or if
// the caller changes the dynamic state bypassing the VulkanCommandProcessor)
// unless the caller has these state variables as dynamic and uses the
// tracking in VulkanCommandProcessor to modify them.
void BindExternalGraphicsPipeline(VkPipeline pipeline,
bool keep_dynamic_depth_bias = false,
bool keep_dynamic_blend_constants = false,
bool keep_dynamic_stencil_mask_ref = false);
void BindExternalComputePipeline(VkPipeline pipeline);
void SetViewport(const VkViewport& viewport);
void SetScissor(const VkRect2D& scissor);
protected:
bool SetupContext() override;
void ShutdownContext() override;
void MakeCoherent() override;
void WriteRegister(uint32_t index, uint32_t value) override;
void BeginFrame();
void EndFrame();
void OnGammaRamp256EntryTableValueWritten() override;
void OnGammaRampPWLValueWritten() override;
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
uint32_t frontbuffer_height) override;
@@ -81,52 +276,459 @@ class VulkanCommandProcessor : public CommandProcessor {
const uint32_t* host_address,
uint32_t dword_count) override;
bool IssueDraw(xenos::PrimitiveType primitive_type, uint32_t index_count,
bool IssueDraw(xenos::PrimitiveType prim_type, uint32_t index_count,
IndexBufferInfo* index_buffer_info,
bool major_mode_explicit) override;
bool PopulateConstants(VkCommandBuffer command_buffer,
VulkanShader* vertex_shader,
VulkanShader* pixel_shader);
bool PopulateIndexBuffer(VkCommandBuffer command_buffer,
IndexBufferInfo* index_buffer_info);
bool PopulateVertexBuffers(VkCommandBuffer command_buffer,
VkCommandBuffer setup_buffer,
VulkanShader* vertex_shader);
bool PopulateSamplers(VkCommandBuffer command_buffer,
VkCommandBuffer setup_buffer,
VulkanShader* vertex_shader,
VulkanShader* pixel_shader);
bool IssueCopy() override;
uint64_t dirty_float_constants_ = 0; // Dirty float constants in blocks of 4
uint8_t dirty_bool_constants_ = 0;
uint32_t dirty_loop_constants_ = 0;
uint8_t dirty_gamma_constants_ = 0;
void InitializeTrace() override;
uint32_t coher_base_vc_ = 0;
uint32_t coher_size_vc_ = 0;
private:
struct CommandBuffer {
VkCommandPool pool;
VkCommandBuffer buffer;
};
struct SparseBufferBind {
VkBuffer buffer;
size_t bind_offset;
uint32_t bind_count;
};
union TextureDescriptorSetLayoutKey {
uint32_t key;
struct {
// 0 - sampled image descriptors, 1 - sampler descriptors.
uint32_t is_samplers : 1;
uint32_t is_vertex : 1;
// For 0, use descriptor_set_layout_empty_ instead as these are owning
// references.
uint32_t binding_count : 30;
};
TextureDescriptorSetLayoutKey() : key(0) {
static_assert_size(*this, sizeof(key));
}
struct Hasher {
size_t operator()(const TextureDescriptorSetLayoutKey& key) const {
return std::hash<decltype(key.key)>{}(key.key);
}
};
bool operator==(const TextureDescriptorSetLayoutKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const TextureDescriptorSetLayoutKey& other_key) const {
return !(*this == other_key);
}
};
union PipelineLayoutKey {
uint64_t key;
struct {
// Pixel textures in the low bits since those are varied much more
// commonly.
uint16_t texture_count_pixel;
uint16_t sampler_count_pixel;
uint16_t texture_count_vertex;
uint16_t sampler_count_vertex;
};
PipelineLayoutKey() : key(0) { static_assert_size(*this, sizeof(key)); }
struct Hasher {
size_t operator()(const PipelineLayoutKey& key) const {
return std::hash<decltype(key.key)>{}(key.key);
}
};
bool operator==(const PipelineLayoutKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const PipelineLayoutKey& other_key) const {
return !(*this == other_key);
}
};
class PipelineLayout : public VulkanPipelineCache::PipelineLayoutProvider {
public:
explicit PipelineLayout(
VkPipelineLayout pipeline_layout,
VkDescriptorSetLayout descriptor_set_layout_textures_vertex_ref,
VkDescriptorSetLayout descriptor_set_layout_samplers_vertex_ref,
VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref,
VkDescriptorSetLayout descriptor_set_layout_samplers_pixel_ref)
: pipeline_layout_(pipeline_layout),
descriptor_set_layout_textures_vertex_ref_(
descriptor_set_layout_textures_vertex_ref),
descriptor_set_layout_samplers_vertex_ref_(
descriptor_set_layout_samplers_vertex_ref),
descriptor_set_layout_textures_pixel_ref_(
descriptor_set_layout_textures_pixel_ref),
descriptor_set_layout_samplers_pixel_ref_(
descriptor_set_layout_samplers_pixel_ref) {}
VkPipelineLayout GetPipelineLayout() const override {
return pipeline_layout_;
}
VkDescriptorSetLayout descriptor_set_layout_textures_vertex_ref() const {
return descriptor_set_layout_textures_vertex_ref_;
}
VkDescriptorSetLayout descriptor_set_layout_samplers_vertex_ref() const {
return descriptor_set_layout_samplers_vertex_ref_;
}
VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref() const {
return descriptor_set_layout_textures_pixel_ref_;
}
VkDescriptorSetLayout descriptor_set_layout_samplers_pixel_ref() const {
return descriptor_set_layout_samplers_pixel_ref_;
}
private:
VkPipelineLayout pipeline_layout_;
VkDescriptorSetLayout descriptor_set_layout_textures_vertex_ref_;
VkDescriptorSetLayout descriptor_set_layout_samplers_vertex_ref_;
VkDescriptorSetLayout descriptor_set_layout_textures_pixel_ref_;
VkDescriptorSetLayout descriptor_set_layout_samplers_pixel_ref_;
};
struct UsedSingleTransientDescriptor {
uint64_t frame;
SingleTransientDescriptorLayout layout;
VkDescriptorSet set;
};
struct UsedTextureTransientDescriptorSet {
uint64_t frame;
TextureDescriptorSetLayoutKey layout;
VkDescriptorSet set;
};
enum SwapApplyGammaDescriptorSet : uint32_t {
kSwapApplyGammaDescriptorSetRamp,
kSwapApplyGammaDescriptorSetSource,
kSwapApplyGammaDescriptorSetCount,
};
// Framebuffer for the current presenter's guest output image revision, and
// its usage tracking.
struct SwapFramebuffer {
VkFramebuffer framebuffer = VK_NULL_HANDLE;
uint64_t version = UINT64_MAX;
uint64_t last_submission = 0;
};
// BeginSubmission and EndSubmission may be called at any time. If there's an
// open non-frame submission, BeginSubmission(true) will promote it to a
// frame. EndSubmission(true) will close the frame no matter whether the
// submission has already been closed.
// Unlike on Direct3D 12, submission boundaries do not imply any memory
// barriers aside from an incoming host write (but not outgoing host read)
// dependency.
// Rechecks submission number and reclaims per-submission resources. Pass 0 as
// the submission to await to simply check status, or pass
// GetCurrentSubmission() to wait for all queue operations to be completed.
void CheckSubmissionFenceAndDeviceLoss(uint64_t await_submission);
// If is_guest_command is true, a new full frame - with full cleanup of
// resources and, if needed, starting capturing - is opened if pending (as
// opposed to simply resuming after mid-frame synchronization). Returns
// whether a submission is open currently and the device is not lost.
bool BeginSubmission(bool is_guest_command);
// If is_swap is true, a full frame is closed - with, if needed, cache
// clearing and stopping capturing. Returns whether the submission was done
// successfully, if it has failed, leaves it open.
bool EndSubmission(bool is_swap);
bool AwaitAllQueueOperationsCompletion() {
CheckSubmissionFenceAndDeviceLoss(GetCurrentSubmission());
return !submission_open_ && submissions_in_flight_fences_.empty();
}
void ClearTransientDescriptorPools();
void SplitPendingBarrier();
void DestroyScratchBuffer();
void UpdateDynamicState(const draw_util::ViewportInfo& viewport_info,
bool primitive_polygonal,
reg::RB_DEPTHCONTROL normalized_depth_control);
void UpdateSystemConstantValues(bool primitive_polygonal,
xenos::Endian index_endian,
const draw_util::ViewportInfo& viewport_info,
uint32_t used_texture_mask);
bool UpdateBindings(const VulkanShader* vertex_shader,
const VulkanShader* pixel_shader);
// Allocates a descriptor set and fills the VkWriteDescriptorSet structure.
// The descriptor set layout must be the one for the given is_samplers,
// is_vertex, binding_count (from GetTextureDescriptorSetLayout - may be
// already available at the moment of the call, no need to locate it again).
// Returns whether the allocation was successful.
bool WriteTransientTextureBindings(
bool is_samplers, bool is_vertex, uint32_t binding_count,
VkDescriptorSetLayout descriptor_set_layout,
const VkDescriptorImageInfo* image_info,
VkWriteDescriptorSet& write_descriptor_set_out);
bool device_lost_ = false;
bool capturing_ = false;
bool trace_requested_ = false;
bool cache_clear_requested_ = false;
std::unique_ptr<BufferCache> buffer_cache_;
// Host shader types that guest shaders can be translated into - they can
// access the shared memory (via vertex fetch, memory export, or manual index
// buffer reading) and textures.
VkPipelineStageFlags guest_shader_pipeline_stages_ = 0;
VkShaderStageFlags guest_shader_vertex_stages_ = 0;
std::vector<VkFence> fences_free_;
std::vector<VkSemaphore> semaphores_free_;
bool submission_open_ = false;
uint64_t submission_completed_ = 0;
// In case vkQueueSubmit fails after something like a successful
// vkQueueBindSparse, to wait correctly on the next attempt.
std::vector<VkSemaphore> current_submission_wait_semaphores_;
std::vector<VkPipelineStageFlags> current_submission_wait_stage_masks_;
std::vector<VkFence> submissions_in_flight_fences_;
std::deque<std::pair<uint64_t, VkSemaphore>>
submissions_in_flight_semaphores_;
static constexpr uint32_t kMaxFramesInFlight = 3;
bool frame_open_ = false;
// Guest frame index, since some transient resources can be reused across
// submissions. Values updated in the beginning of a frame.
uint64_t frame_current_ = 1;
uint64_t frame_completed_ = 0;
// Submission indices of frames that have already been submitted.
uint64_t closed_frame_submissions_[kMaxFramesInFlight] = {};
// <Submission where last used, resource>, sorted by the submission number.
std::deque<std::pair<uint64_t, VkDeviceMemory>> destroy_memory_;
std::deque<std::pair<uint64_t, VkBuffer>> destroy_buffers_;
std::deque<std::pair<uint64_t, VkFramebuffer>> destroy_framebuffers_;
std::vector<CommandBuffer> command_buffers_writable_;
std::deque<std::pair<uint64_t, CommandBuffer>> command_buffers_submitted_;
DeferredCommandBuffer deferred_command_buffer_;
std::vector<VkSparseMemoryBind> sparse_memory_binds_;
std::vector<SparseBufferBind> sparse_buffer_binds_;
// SparseBufferBind converted to VkSparseBufferMemoryBindInfo to this buffer
// on submission (because pBinds should point to a place in std::vector, but
// it may be reallocated).
std::vector<VkSparseBufferMemoryBindInfo> sparse_buffer_bind_infos_temp_;
VkPipelineStageFlags sparse_bind_wait_stage_mask_ = 0;
// Temporary storage with reusable memory for creating descriptor set layouts.
std::vector<VkDescriptorSetLayoutBinding> descriptor_set_layout_bindings_;
// Temporary storage with reusable memory for writing image and sampler
// descriptors.
std::vector<VkDescriptorImageInfo> descriptor_write_image_info_;
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool> uniform_buffer_pool_;
// Descriptor set layouts used by different shaders.
VkDescriptorSetLayout descriptor_set_layout_empty_ = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptor_set_layout_shared_memory_and_edram_ =
VK_NULL_HANDLE;
std::array<VkDescriptorSetLayout,
size_t(SingleTransientDescriptorLayout::kCount)>
descriptor_set_layouts_single_transient_{};
// Descriptor set layouts are referenced by pipeline_layouts_.
std::unordered_map<TextureDescriptorSetLayoutKey, VkDescriptorSetLayout,
TextureDescriptorSetLayoutKey::Hasher>
descriptor_set_layouts_textures_;
// Pipeline layouts are referenced by VulkanPipelineCache.
std::unordered_map<PipelineLayoutKey, PipelineLayout,
PipelineLayoutKey::Hasher>
pipeline_layouts_;
ui::vulkan::SingleTypeDescriptorSetAllocator
transient_descriptor_allocator_uniform_buffer_;
ui::vulkan::SingleTypeDescriptorSetAllocator
transient_descriptor_allocator_storage_buffer_;
std::deque<UsedSingleTransientDescriptor> single_transient_descriptors_used_;
std::array<std::vector<VkDescriptorSet>,
size_t(SingleTransientDescriptorLayout::kCount)>
single_transient_descriptors_free_;
ui::vulkan::SingleTypeDescriptorSetAllocator
transient_descriptor_allocator_sampled_image_;
ui::vulkan::SingleTypeDescriptorSetAllocator
transient_descriptor_allocator_sampler_;
std::deque<UsedTextureTransientDescriptorSet>
texture_transient_descriptor_sets_used_;
std::unordered_map<TextureDescriptorSetLayoutKey,
std::vector<VkDescriptorSet>,
TextureDescriptorSetLayoutKey::Hasher>
texture_transient_descriptor_sets_free_;
std::unique_ptr<VulkanSharedMemory> shared_memory_;
std::unique_ptr<VulkanPrimitiveProcessor> primitive_processor_;
std::unique_ptr<VulkanRenderTargetCache> render_target_cache_;
std::unique_ptr<VulkanPipelineCache> pipeline_cache_;
std::unique_ptr<RenderCache> render_cache_;
std::unique_ptr<VulkanTextureCache> texture_cache_;
std::unique_ptr<ui::vulkan::Blitter> blitter_;
std::unique_ptr<ui::vulkan::CommandBufferPool> command_buffer_pool_;
VkDescriptorPool shared_memory_and_edram_descriptor_pool_ = VK_NULL_HANDLE;
VkDescriptorSet shared_memory_and_edram_descriptor_set_;
bool frame_open_ = false;
const RenderState* current_render_state_ = nullptr;
VkCommandBuffer current_command_buffer_ = nullptr;
VkCommandBuffer current_setup_buffer_ = nullptr;
VkFence current_batch_fence_;
// Bytes 0x0...0x3FF - 256-entry gamma ramp table with B10G10R10X2 data (read
// as R10G10B10X2 with swizzle).
// Bytes 0x400...0x9FF - 128-entry PWL R16G16 gamma ramp (R - base, G - delta,
// low 6 bits of each are zero, 3 elements per entry).
// kMaxFramesInFlight pairs of gamma ramps if in host-visible memory and
// uploaded directly, one otherwise.
VkDeviceMemory gamma_ramp_buffer_memory_ = VK_NULL_HANDLE;
VkBuffer gamma_ramp_buffer_ = VK_NULL_HANDLE;
// kMaxFramesInFlight pairs, only when the gamma ramp buffer is not
// host-visible.
VkDeviceMemory gamma_ramp_upload_buffer_memory_ = VK_NULL_HANDLE;
VkBuffer gamma_ramp_upload_buffer_ = VK_NULL_HANDLE;
VkDeviceSize gamma_ramp_upload_memory_size_;
uint32_t gamma_ramp_upload_memory_type_;
// Mapping of either gamma_ramp_buffer_memory_ (if it's host-visible) or
// gamma_ramp_upload_buffer_memory_ (otherwise).
void* gamma_ramp_upload_mapping_;
std::array<VkBufferView, 2 * kMaxFramesInFlight> gamma_ramp_buffer_views_{};
// UINT32_MAX if outdated.
uint32_t gamma_ramp_256_entry_table_current_frame_ = UINT32_MAX;
uint32_t gamma_ramp_pwl_current_frame_ = UINT32_MAX;
ui::vulkan::VulkanSubmissionTracker swap_submission_tracker_;
VkFramebuffer swap_framebuffer_ = VK_NULL_HANDLE;
uint64_t swap_framebuffer_version_ = UINT64_MAX;
VkDescriptorSetLayout swap_descriptor_set_layout_sampled_image_ =
VK_NULL_HANDLE;
VkDescriptorSetLayout swap_descriptor_set_layout_uniform_texel_buffer_ =
VK_NULL_HANDLE;
// Descriptor pool for allocating descriptors needed for presentation, such as
// the destination images and the gamma ramps.
VkDescriptorPool swap_descriptor_pool_ = VK_NULL_HANDLE;
// Interleaved 256-entry table and PWL texel buffer descriptors.
// kMaxFramesInFlight pairs of gamma ramps if in host-visible memory and
// uploaded directly, one otherwise.
std::array<VkDescriptorSet, 2 * kMaxFramesInFlight>
swap_descriptors_gamma_ramp_;
// Sampled images.
std::array<VkDescriptorSet, kMaxFramesInFlight> swap_descriptors_source_;
VkPipelineLayout swap_apply_gamma_pipeline_layout_ = VK_NULL_HANDLE;
// Has no dependencies on specific pipeline stages on both ends to simplify
// use in different scenarios with different pipelines - use explicit barriers
// for synchronization.
VkRenderPass swap_apply_gamma_render_pass_ = VK_NULL_HANDLE;
VkPipeline swap_apply_gamma_256_entry_table_pipeline_ = VK_NULL_HANDLE;
VkPipeline swap_apply_gamma_pwl_pipeline_ = VK_NULL_HANDLE;
std::array<SwapFramebuffer,
ui::vulkan::VulkanPresenter::kMaxActiveGuestOutputImageVersions>
swap_framebuffers_;
// Pending pipeline barriers.
std::vector<VkBufferMemoryBarrier> pending_barriers_buffer_memory_barriers_;
std::vector<VkImageMemoryBarrier> pending_barriers_image_memory_barriers_;
struct PendingBarrier {
VkPipelineStageFlags src_stage_mask = 0;
VkPipelineStageFlags dst_stage_mask = 0;
size_t buffer_memory_barriers_offset = 0;
size_t image_memory_barriers_offset = 0;
};
std::vector<PendingBarrier> pending_barriers_;
PendingBarrier current_pending_barrier_;
// GPU-local scratch buffer.
static constexpr VkDeviceSize kScratchBufferSizeIncrement = 16 * 1024 * 1024;
VkDeviceMemory scratch_buffer_memory_ = VK_NULL_HANDLE;
VkBuffer scratch_buffer_ = VK_NULL_HANDLE;
VkDeviceSize scratch_buffer_size_ = 0;
VkPipelineStageFlags scratch_buffer_last_stage_mask_ = 0;
VkAccessFlags scratch_buffer_last_access_mask_ = 0;
uint64_t scratch_buffer_last_usage_submission_ = 0;
bool scratch_buffer_used_ = false;
// The current dynamic state of the graphics pipeline bind point. Note that
// binding any pipeline to the bind point with static state (even if it's
// unused, like depth bias being disabled, but the values themselves still not
// declared as dynamic in the pipeline) invalidates such dynamic state.
VkViewport dynamic_viewport_;
VkRect2D dynamic_scissor_;
float dynamic_depth_bias_constant_factor_;
float dynamic_depth_bias_slope_factor_;
float dynamic_blend_constants_[4];
// The stencil values are pre-initialized (to D3D11_DEFAULT_STENCIL_*, and the
// initial values for front and back are the same for portability subset
// safety) because they're updated conditionally to avoid changing the back
// face values when stencil is disabled and the primitive type is changed
// between polygonal and non-polygonal.
uint32_t dynamic_stencil_compare_mask_front_ = UINT8_MAX;
uint32_t dynamic_stencil_compare_mask_back_ = UINT8_MAX;
uint32_t dynamic_stencil_write_mask_front_ = UINT8_MAX;
uint32_t dynamic_stencil_write_mask_back_ = UINT8_MAX;
uint32_t dynamic_stencil_reference_front_ = 0;
uint32_t dynamic_stencil_reference_back_ = 0;
bool dynamic_viewport_update_needed_;
bool dynamic_scissor_update_needed_;
bool dynamic_depth_bias_update_needed_;
bool dynamic_blend_constants_update_needed_;
bool dynamic_stencil_compare_mask_front_update_needed_;
bool dynamic_stencil_compare_mask_back_update_needed_;
bool dynamic_stencil_write_mask_front_update_needed_;
bool dynamic_stencil_write_mask_back_update_needed_;
bool dynamic_stencil_reference_front_update_needed_;
bool dynamic_stencil_reference_back_update_needed_;
// Currently used samplers.
std::vector<std::pair<VulkanTextureCache::SamplerParameters, VkSampler>>
current_samplers_vertex_;
std::vector<std::pair<VulkanTextureCache::SamplerParameters, VkSampler>>
current_samplers_pixel_;
// Cache render pass currently started in the command buffer with the
// framebuffer.
VkRenderPass current_render_pass_;
const VulkanRenderTargetCache::Framebuffer* current_framebuffer_;
// Currently bound graphics pipeline, either from the pipeline cache (with
// potentially deferred creation - current_external_graphics_pipeline_ is
// VK_NULL_HANDLE in this case) or a non-Xenos one
// (current_guest_graphics_pipeline_ is VK_NULL_HANDLE in this case).
// TODO(Triang3l): Change to a deferred compilation handle.
VkPipeline current_guest_graphics_pipeline_;
VkPipeline current_external_graphics_pipeline_;
VkPipeline current_external_compute_pipeline_;
// Pipeline layout of the current guest graphics pipeline.
const PipelineLayout* current_guest_graphics_pipeline_layout_;
VkDescriptorSet current_graphics_descriptor_sets_
[SpirvShaderTranslator::kDescriptorSetCount];
// Whether descriptor sets in current_graphics_descriptor_sets_ point to
// up-to-date data.
uint32_t current_graphics_descriptor_set_values_up_to_date_;
// Whether the descriptor sets currently bound to the command buffer - only
// low bits for the descriptor set layouts that remained the same are kept
// when changing the pipeline layout. May be out of sync with
// current_graphics_descriptor_set_values_up_to_date_, but should be ensured
// to be a subset of it at some point when it becomes important; bits for
// non-existent descriptor set layouts may also be set, but need to be ignored
// when they start to matter.
uint32_t current_graphics_descriptor_sets_bound_up_to_date_;
static_assert(
SpirvShaderTranslator::kDescriptorSetCount <=
sizeof(current_graphics_descriptor_set_values_up_to_date_) * CHAR_BIT,
"Bit fields storing descriptor set validity must be large enough");
static_assert(
SpirvShaderTranslator::kDescriptorSetCount <=
sizeof(current_graphics_descriptor_sets_bound_up_to_date_) * CHAR_BIT,
"Bit fields storing descriptor set validity must be large enough");
// Float constant usage masks of the last draw call.
uint64_t current_float_constant_map_vertex_[4];
uint64_t current_float_constant_map_pixel_[4];
// System shader constants.
SpirvShaderTranslator::SystemConstants system_constants_;
};
} // namespace vulkan

View File

@@ -1,16 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/vulkan/vulkan_gpu_flags.h"
DEFINE_bool(vulkan_renderdoc_capture_all, false,
"Capture everything with RenderDoc.", "Vulkan");
DEFINE_bool(vulkan_native_msaa, false, "Use native MSAA", "Vulkan");
DEFINE_bool(vulkan_dump_disasm, false,
"Dump shader disassembly. NVIDIA only supported.", "Vulkan");

View File

@@ -1,20 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_VULKAN_GPU_FLAGS_H_
#define XENIA_GPU_VULKAN_VULKAN_GPU_FLAGS_H_
#define FINE_GRAINED_DRAW_SCOPES 1
#include "xenia/base/cvar.h"
DECLARE_bool(vulkan_renderdoc_capture_all);
DECLARE_bool(vulkan_native_msaa);
DECLARE_bool(vulkan_dump_disasm);
#endif // XENIA_GPU_VULKAN_VULKAN_GPU_FLAGS_H_

View File

@@ -26,7 +26,9 @@ class VulkanGraphicsSystem : public GraphicsSystem {
static bool IsAvailable() { return true; }
std::string name() const override { return "Vulkan - obsolete"; }
std::string name() const override {
return "Vulkan - HEAVILY INCOMPLETE, early development";
}
X_STATUS Setup(cpu::Processor* processor, kernel::KernelState* kernel_state,
ui::WindowedAppContext* app_context,

File diff suppressed because it is too large Load Diff

View File

@@ -2,312 +2,322 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_VULKAN_PIPELINE_CACHE_H_
#define XENIA_GPU_VULKAN_VULKAN_PIPELINE_CACHE_H_
#ifndef XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
#define XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_
#include <cstddef>
#include <cstring>
#include <functional>
#include <memory>
#include <unordered_map>
#include <utility>
#include "xenia/base/string_buffer.h"
#include "xenia/base/hash.h"
#include "xenia/base/platform.h"
#include "xenia/base/xxhash.h"
#include "xenia/gpu/primitive_processor.h"
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/registers.h"
#include "xenia/gpu/spirv_shader_translator.h"
#include "xenia/gpu/vulkan/render_cache.h"
#include "xenia/gpu/vulkan/vulkan_render_target_cache.h"
#include "xenia/gpu/vulkan/vulkan_shader.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/spirv/spirv_disassembler.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace gpu {
namespace vulkan {
// Configures and caches pipelines based on render state.
// This is responsible for properly setting all state required for a draw
// including shaders, various blend/etc options, and input configuration.
class VulkanCommandProcessor;
// TODO(Triang3l): Create a common base for both the Vulkan and the Direct3D
// implementations.
class VulkanPipelineCache {
public:
enum class UpdateStatus {
kCompatible,
kMismatch,
kError,
static constexpr size_t kLayoutUIDEmpty = 0;
class PipelineLayoutProvider {
public:
virtual ~PipelineLayoutProvider() {}
virtual VkPipelineLayout GetPipelineLayout() const = 0;
protected:
PipelineLayoutProvider() = default;
};
VulkanPipelineCache(RegisterFile* register_file,
const ui::vulkan::VulkanProvider& provider);
VulkanPipelineCache(VulkanCommandProcessor& command_processor,
const RegisterFile& register_file,
VulkanRenderTargetCache& render_target_cache,
VkShaderStageFlags guest_shader_vertex_stages);
~VulkanPipelineCache();
VkResult Initialize(VkDescriptorSetLayout uniform_descriptor_set_layout,
VkDescriptorSetLayout texture_descriptor_set_layout,
VkDescriptorSetLayout vertex_descriptor_set_layout);
bool Initialize();
void Shutdown();
// Loads a shader from the cache, possibly translating it.
VulkanShader* LoadShader(xenos::ShaderType shader_type,
uint32_t guest_address, const uint32_t* host_address,
uint32_t dword_count);
const uint32_t* host_address, uint32_t dword_count);
// Analyze shader microcode on the translator thread.
void AnalyzeShaderUcode(Shader& shader) {
shader.AnalyzeUcode(ucode_disasm_buffer_);
}
// Configures a pipeline using the current render state and the given render
// pass. If a previously available pipeline is available it will be used,
// otherwise a new one may be created. Any state that can be set dynamically
// in the command buffer is issued at this time.
// Returns whether the pipeline could be successfully created.
UpdateStatus ConfigurePipeline(VkCommandBuffer command_buffer,
const RenderState* render_state,
VulkanShader* vertex_shader,
VulkanShader* pixel_shader,
xenos::PrimitiveType primitive_type,
VkPipeline* pipeline_out);
// Retrieves the shader modification for the current state. The shader must
// have microcode analyzed.
SpirvShaderTranslator::Modification GetCurrentVertexShaderModification(
const Shader& shader,
Shader::HostVertexShaderType host_vertex_shader_type) const;
SpirvShaderTranslator::Modification GetCurrentPixelShaderModification(
const Shader& shader, uint32_t normalized_color_mask) const;
// Sets required dynamic state on the command buffer.
// Only state that has changed since the last call will be set unless
// full_update is true.
bool SetDynamicState(VkCommandBuffer command_buffer, bool full_update);
// Pipeline layout shared by all pipelines.
VkPipelineLayout pipeline_layout() const { return pipeline_layout_; }
// Clears all cached content.
void ClearCache();
bool EnsureShadersTranslated(VulkanShader::VulkanTranslation* vertex_shader,
VulkanShader::VulkanTranslation* pixel_shader);
// TODO(Triang3l): Return a deferred creation handle.
bool ConfigurePipeline(
VulkanShader::VulkanTranslation* vertex_shader,
VulkanShader::VulkanTranslation* pixel_shader,
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
reg::RB_DEPTHCONTROL normalized_depth_control,
uint32_t normalized_color_mask,
VulkanRenderTargetCache::RenderPassKey render_pass_key,
VkPipeline& pipeline_out,
const PipelineLayoutProvider*& pipeline_layout_out);
private:
// Creates or retrieves an existing pipeline for the currently configured
// state.
VkPipeline GetPipeline(const RenderState* render_state, uint64_t hash_key);
enum class PipelineGeometryShader : uint32_t {
kNone,
kRectangleList,
kQuadList,
};
bool TranslateShader(VulkanShader::VulkanTranslation& translation);
enum class PipelinePrimitiveTopology : uint32_t {
kPointList,
kLineList,
kLineStrip,
kTriangleList,
kTriangleStrip,
kTriangleFan,
kLineListWithAdjacency,
kPatchList,
};
void DumpShaderDisasmAMD(VkPipeline pipeline);
void DumpShaderDisasmNV(const VkGraphicsPipelineCreateInfo& info);
enum class PipelinePolygonMode : uint32_t {
kFill,
kLine,
kPoint,
};
// Gets a geometry shader used to emulate the given primitive type.
// Returns nullptr if the primitive doesn't need to be emulated.
VkShaderModule GetGeometryShader(xenos::PrimitiveType primitive_type,
bool is_line_mode);
enum class PipelineBlendFactor : uint32_t {
kZero,
kOne,
kSrcColor,
kOneMinusSrcColor,
kDstColor,
kOneMinusDstColor,
kSrcAlpha,
kOneMinusSrcAlpha,
kDstAlpha,
kOneMinusDstAlpha,
kConstantColor,
kOneMinusConstantColor,
kConstantAlpha,
kOneMinusConstantAlpha,
kSrcAlphaSaturate,
};
RegisterFile* register_file_ = nullptr;
const ui::vulkan::VulkanProvider& provider_;
// Update PipelineDescription::kVersion if anything is changed!
XEPACKEDSTRUCT(PipelineRenderTarget, {
PipelineBlendFactor src_color_blend_factor : 4; // 4
PipelineBlendFactor dst_color_blend_factor : 4; // 8
xenos::BlendOp color_blend_op : 3; // 11
PipelineBlendFactor src_alpha_blend_factor : 4; // 15
PipelineBlendFactor dst_alpha_blend_factor : 4; // 19
xenos::BlendOp alpha_blend_op : 3; // 22
uint32_t color_write_mask : 4; // 26
});
// Temporary storage for AnalyzeUcode calls.
XEPACKEDSTRUCT(PipelineDescription, {
uint64_t vertex_shader_hash;
uint64_t vertex_shader_modification;
// 0 if no pixel shader.
uint64_t pixel_shader_hash;
uint64_t pixel_shader_modification;
VulkanRenderTargetCache::RenderPassKey render_pass_key;
// Shader stages.
PipelineGeometryShader geometry_shader : 2; // 2
// Input assembly.
PipelinePrimitiveTopology primitive_topology : 3; // 5
uint32_t primitive_restart : 1; // 6
// Rasterization.
uint32_t depth_clamp_enable : 1; // 7
PipelinePolygonMode polygon_mode : 2; // 9
uint32_t cull_front : 1; // 10
uint32_t cull_back : 1; // 11
uint32_t front_face_clockwise : 1; // 12
// Depth / stencil.
uint32_t depth_write_enable : 1; // 13
xenos::CompareFunction depth_compare_op : 3; // 15
uint32_t stencil_test_enable : 1; // 17
xenos::StencilOp stencil_front_fail_op : 3; // 20
xenos::StencilOp stencil_front_pass_op : 3; // 23
xenos::StencilOp stencil_front_depth_fail_op : 3; // 26
xenos::CompareFunction stencil_front_compare_op : 3; // 29
xenos::StencilOp stencil_back_fail_op : 3; // 32
xenos::StencilOp stencil_back_pass_op : 3; // 3
xenos::StencilOp stencil_back_depth_fail_op : 3; // 6
xenos::CompareFunction stencil_back_compare_op : 3; // 9
// Filled only for the attachments present in the render pass object.
PipelineRenderTarget render_targets[xenos::kMaxColorRenderTargets];
// Including all the padding, for a stable hash.
PipelineDescription() { Reset(); }
PipelineDescription(const PipelineDescription& description) {
std::memcpy(this, &description, sizeof(*this));
}
PipelineDescription& operator=(const PipelineDescription& description) {
std::memcpy(this, &description, sizeof(*this));
return *this;
}
bool operator==(const PipelineDescription& description) const {
return std::memcmp(this, &description, sizeof(*this)) == 0;
}
void Reset() { std::memset(this, 0, sizeof(*this)); }
uint64_t GetHash() const { return XXH3_64bits(this, sizeof(*this)); }
struct Hasher {
size_t operator()(const PipelineDescription& description) const {
return size_t(description.GetHash());
}
};
});
struct Pipeline {
VkPipeline pipeline = VK_NULL_HANDLE;
// The layouts are owned by the VulkanCommandProcessor, and must not be
// destroyed by it while the pipeline cache is active.
const PipelineLayoutProvider* pipeline_layout;
Pipeline(const PipelineLayoutProvider* pipeline_layout_provider)
: pipeline_layout(pipeline_layout_provider) {}
};
// Description that can be passed from the command processor thread to the
// creation threads, with everything needed from caches pre-looked-up.
struct PipelineCreationArguments {
std::pair<const PipelineDescription, Pipeline>* pipeline;
const VulkanShader::VulkanTranslation* vertex_shader;
const VulkanShader::VulkanTranslation* pixel_shader;
VkShaderModule geometry_shader;
VkRenderPass render_pass;
};
union GeometryShaderKey {
uint32_t key;
struct {
PipelineGeometryShader type : 2;
uint32_t interpolator_count : 5;
uint32_t user_clip_plane_count : 3;
uint32_t user_clip_plane_cull : 1;
uint32_t has_vertex_kill_and : 1;
uint32_t has_point_size : 1;
uint32_t has_point_coordinates : 1;
};
GeometryShaderKey() : key(0) { static_assert_size(*this, sizeof(key)); }
struct Hasher {
size_t operator()(const GeometryShaderKey& key) const {
return std::hash<uint32_t>{}(key.key);
}
};
bool operator==(const GeometryShaderKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const GeometryShaderKey& other_key) const {
return !(*this == other_key);
}
};
// Can be called from multiple threads.
bool TranslateAnalyzedShader(SpirvShaderTranslator& translator,
VulkanShader::VulkanTranslation& translation);
void WritePipelineRenderTargetDescription(
reg::RB_BLENDCONTROL blend_control, uint32_t write_mask,
PipelineRenderTarget& render_target_out) const;
bool GetCurrentStateDescription(
const VulkanShader::VulkanTranslation* vertex_shader,
const VulkanShader::VulkanTranslation* pixel_shader,
const PrimitiveProcessor::ProcessingResult& primitive_processing_result,
reg::RB_DEPTHCONTROL normalized_depth_control,
uint32_t normalized_color_mask,
VulkanRenderTargetCache::RenderPassKey render_pass_key,
PipelineDescription& description_out) const;
// Whether the pipeline for the given description is supported by the device.
bool ArePipelineRequirementsMet(const PipelineDescription& description) const;
static bool GetGeometryShaderKey(PipelineGeometryShader geometry_shader_type,
GeometryShaderKey& key_out);
VkShaderModule GetGeometryShader(GeometryShaderKey key);
// Can be called from creation threads - all needed data must be fully set up
// at the point of the call: shaders must be translated, pipeline layout and
// render pass objects must be available.
bool EnsurePipelineCreated(
const PipelineCreationArguments& creation_arguments);
VulkanCommandProcessor& command_processor_;
const RegisterFile& register_file_;
VulkanRenderTargetCache& render_target_cache_;
VkShaderStageFlags guest_shader_vertex_stages_;
// Temporary storage for AnalyzeUcode calls on the processor thread.
StringBuffer ucode_disasm_buffer_;
// Reusable shader translator.
std::unique_ptr<ShaderTranslator> shader_translator_ = nullptr;
// Disassembler used to get the SPIRV disasm. Only used in debug.
xe::ui::spirv::SpirvDisassembler disassembler_;
// All loaded shaders mapped by their guest hash key.
std::unordered_map<uint64_t, VulkanShader*> shader_map_;
// Reusable shader translator on the command processor thread.
std::unique_ptr<SpirvShaderTranslator> shader_translator_;
// Vulkan pipeline cache, which in theory helps us out.
// This can be serialized to disk and reused, if we want.
VkPipelineCache pipeline_cache_ = nullptr;
// Layout used for all pipelines describing our uniforms, textures, and push
// constants.
VkPipelineLayout pipeline_layout_ = nullptr;
struct LayoutUID {
size_t uid;
size_t vector_span_offset;
size_t vector_span_length;
};
std::mutex layouts_mutex_;
// Texture binding layouts of different shaders, for obtaining layout UIDs.
std::vector<VulkanShader::TextureBinding> texture_binding_layouts_;
// Map of texture binding layouts used by shaders, for obtaining UIDs. Keys
// are XXH3 hashes of layouts, values need manual collision resolution using
// layout_vector_offset:layout_length of texture_binding_layouts_.
std::unordered_multimap<uint64_t, LayoutUID,
xe::hash::IdentityHasher<uint64_t>>
texture_binding_layout_map_;
// Shared geometry shaders.
struct {
VkShaderModule line_quad_list;
VkShaderModule point_list;
VkShaderModule quad_list;
VkShaderModule rect_list;
} geometry_shaders_;
// Ucode hash -> shader.
std::unordered_map<uint64_t, VulkanShader*,
xe::hash::IdentityHasher<uint64_t>>
shaders_;
// Shared dummy pixel shader.
VkShaderModule dummy_pixel_shader_;
// Geometry shaders for Xenos primitive types not supported by Vulkan.
// Stores VK_NULL_HANDLE if failed to create.
std::unordered_map<GeometryShaderKey, VkShaderModule,
GeometryShaderKey::Hasher>
geometry_shaders_;
// Hash state used to incrementally produce pipeline hashes during update.
// By the time the full update pass has run the hash will represent the
// current state in a way that can uniquely identify the produced VkPipeline.
XXH3_state_t hash_state_;
// All previously generated pipelines mapped by hash.
std::unordered_map<uint64_t, VkPipeline> cached_pipelines_;
std::unordered_map<PipelineDescription, Pipeline, PipelineDescription::Hasher>
pipelines_;
// Previously used pipeline. This matches our current state settings
// and allows us to quickly(ish) reuse the pipeline if no registers have
// changed.
VkPipeline current_pipeline_ = nullptr;
private:
UpdateStatus UpdateState(VulkanShader* vertex_shader,
VulkanShader* pixel_shader,
xenos::PrimitiveType primitive_type);
UpdateStatus UpdateRenderTargetState();
UpdateStatus UpdateShaderStages(VulkanShader* vertex_shader,
VulkanShader* pixel_shader,
xenos::PrimitiveType primitive_type);
UpdateStatus UpdateVertexInputState(VulkanShader* vertex_shader);
UpdateStatus UpdateInputAssemblyState(xenos::PrimitiveType primitive_type);
UpdateStatus UpdateViewportState();
UpdateStatus UpdateRasterizationState(xenos::PrimitiveType primitive_type);
UpdateStatus UpdateMultisampleState();
UpdateStatus UpdateDepthStencilState();
UpdateStatus UpdateColorBlendState();
bool SetShadowRegister(uint32_t* dest, uint32_t register_name);
bool SetShadowRegister(float* dest, uint32_t register_name);
bool SetShadowRegisterArray(uint32_t* dest, uint32_t num,
uint32_t register_name);
struct UpdateRenderTargetsRegisters {
uint32_t rb_modecontrol;
reg::RB_SURFACE_INFO rb_surface_info;
reg::RB_COLOR_INFO rb_color_info;
reg::RB_DEPTH_INFO rb_depth_info;
reg::RB_COLOR_INFO rb_color1_info;
reg::RB_COLOR_INFO rb_color2_info;
reg::RB_COLOR_INFO rb_color3_info;
uint32_t rb_color_mask;
uint32_t rb_depthcontrol;
uint32_t rb_stencilrefmask;
UpdateRenderTargetsRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_render_targets_regs_;
struct UpdateShaderStagesRegisters {
xenos::PrimitiveType primitive_type;
uint32_t pa_su_sc_mode_cntl;
reg::SQ_PROGRAM_CNTL sq_program_cntl;
VulkanShader* vertex_shader;
VulkanShader* pixel_shader;
UpdateShaderStagesRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_shader_stages_regs_;
VkPipelineShaderStageCreateInfo update_shader_stages_info_[3];
uint32_t update_shader_stages_stage_count_ = 0;
struct UpdateVertexInputStateRegisters {
VulkanShader* vertex_shader;
UpdateVertexInputStateRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_vertex_input_state_regs_;
VkPipelineVertexInputStateCreateInfo update_vertex_input_state_info_;
VkVertexInputBindingDescription update_vertex_input_state_binding_descrs_[32];
VkVertexInputAttributeDescription
update_vertex_input_state_attrib_descrs_[96];
struct UpdateInputAssemblyStateRegisters {
xenos::PrimitiveType primitive_type;
uint32_t pa_su_sc_mode_cntl;
uint32_t multi_prim_ib_reset_index;
UpdateInputAssemblyStateRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_input_assembly_state_regs_;
VkPipelineInputAssemblyStateCreateInfo update_input_assembly_state_info_;
struct UpdateViewportStateRegisters {
// uint32_t pa_cl_clip_cntl;
uint32_t rb_surface_info;
uint32_t pa_cl_vte_cntl;
uint32_t pa_su_sc_mode_cntl;
uint32_t pa_sc_window_offset;
uint32_t pa_sc_window_scissor_tl;
uint32_t pa_sc_window_scissor_br;
float pa_cl_vport_xoffset;
float pa_cl_vport_yoffset;
float pa_cl_vport_zoffset;
float pa_cl_vport_xscale;
float pa_cl_vport_yscale;
float pa_cl_vport_zscale;
UpdateViewportStateRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_viewport_state_regs_;
VkPipelineViewportStateCreateInfo update_viewport_state_info_;
struct UpdateRasterizationStateRegisters {
xenos::PrimitiveType primitive_type;
uint32_t pa_cl_clip_cntl;
uint32_t pa_su_sc_mode_cntl;
uint32_t pa_sc_screen_scissor_tl;
uint32_t pa_sc_screen_scissor_br;
uint32_t pa_sc_viz_query;
uint32_t pa_su_poly_offset_enable;
uint32_t multi_prim_ib_reset_index;
UpdateRasterizationStateRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_rasterization_state_regs_;
VkPipelineRasterizationStateCreateInfo update_rasterization_state_info_;
struct UpdateMultisampleStateeRegisters {
uint32_t pa_sc_aa_config;
uint32_t pa_su_sc_mode_cntl;
uint32_t rb_surface_info;
UpdateMultisampleStateeRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_multisample_state_regs_;
VkPipelineMultisampleStateCreateInfo update_multisample_state_info_;
struct UpdateDepthStencilStateRegisters {
uint32_t rb_depthcontrol;
uint32_t rb_stencilrefmask;
UpdateDepthStencilStateRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_depth_stencil_state_regs_;
VkPipelineDepthStencilStateCreateInfo update_depth_stencil_state_info_;
struct UpdateColorBlendStateRegisters {
uint32_t rb_color_mask;
uint32_t rb_blendcontrol[4];
uint32_t rb_modecontrol;
UpdateColorBlendStateRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} update_color_blend_state_regs_;
VkPipelineColorBlendStateCreateInfo update_color_blend_state_info_;
VkPipelineColorBlendAttachmentState update_color_blend_attachment_states_[4];
struct SetDynamicStateRegisters {
uint32_t pa_sc_window_offset;
uint32_t pa_su_sc_mode_cntl;
uint32_t pa_sc_window_scissor_tl;
uint32_t pa_sc_window_scissor_br;
uint32_t rb_surface_info;
uint32_t pa_su_sc_vtx_cntl;
// Bias is in Vulkan units because depth format may potentially effect it.
float pa_su_poly_offset_scale;
float pa_su_poly_offset_offset;
uint32_t pa_cl_vte_cntl;
float pa_cl_vport_xoffset;
float pa_cl_vport_yoffset;
float pa_cl_vport_zoffset;
float pa_cl_vport_xscale;
float pa_cl_vport_yscale;
float pa_cl_vport_zscale;
float rb_blend_rgba[4];
uint32_t rb_stencilrefmask;
reg::SQ_PROGRAM_CNTL sq_program_cntl;
uint32_t sq_context_misc;
uint32_t rb_colorcontrol;
reg::RB_COLOR_INFO rb_color_info;
reg::RB_COLOR_INFO rb_color1_info;
reg::RB_COLOR_INFO rb_color2_info;
reg::RB_COLOR_INFO rb_color3_info;
float rb_alpha_ref;
uint32_t pa_su_point_size;
SetDynamicStateRegisters() { Reset(); }
void Reset() { std::memset(this, 0, sizeof(*this)); }
} set_dynamic_state_registers_;
// Previously used pipeline, to avoid lookups if the state wasn't changed.
const std::pair<const PipelineDescription, Pipeline>* last_pipeline_ =
nullptr;
};
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_VULKAN_PIPELINE_CACHE_H_
#endif // XENIA_GPU_VULKAN_VULKAN_PIPELINE_STATE_CACHE_H_

View File

@@ -0,0 +1,229 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/vulkan/vulkan_primitive_processor.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/gpu/vulkan/deferred_command_buffer.h"
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
namespace gpu {
namespace vulkan {
VulkanPrimitiveProcessor::~VulkanPrimitiveProcessor() { Shutdown(true); }
bool VulkanPrimitiveProcessor::Initialize() {
// TODO(Triang3l): fullDrawIndexUint32 feature check and indirect index fetch.
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const VkPhysicalDeviceFeatures& device_features = provider.device_features();
const VkPhysicalDevicePortabilitySubsetFeaturesKHR*
device_portability_subset_features =
provider.device_portability_subset_features();
if (!InitializeCommon(true,
!device_portability_subset_features ||
device_portability_subset_features->triangleFans,
false, device_features.geometryShader)) {
Shutdown();
return false;
}
frame_index_buffer_pool_ =
std::make_unique<ui::vulkan::VulkanUploadBufferPool>(
command_processor_.GetVulkanProvider(),
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
std::max(size_t(kMinRequiredConvertedIndexBufferSize),
ui::GraphicsUploadBufferPool::kDefaultPageSize));
return true;
}
void VulkanPrimitiveProcessor::Shutdown(bool from_destructor) {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
frame_index_buffers_.clear();
frame_index_buffer_pool_.reset();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
builtin_index_buffer_upload_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
builtin_index_buffer_upload_memory_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
builtin_index_buffer_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
builtin_index_buffer_memory_);
if (!from_destructor) {
ShutdownCommon();
}
}
void VulkanPrimitiveProcessor::CompletedSubmissionUpdated() {
if (builtin_index_buffer_upload_ != VK_NULL_HANDLE &&
command_processor_.GetCompletedSubmission() >=
builtin_index_buffer_upload_submission_) {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
builtin_index_buffer_upload_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
builtin_index_buffer_upload_memory_);
}
}
void VulkanPrimitiveProcessor::BeginSubmission() {
if (builtin_index_buffer_upload_ != VK_NULL_HANDLE &&
builtin_index_buffer_upload_submission_ == UINT64_MAX) {
// No need to submit deferred barriers - builtin_index_buffer_ has never
// been used yet, and builtin_index_buffer_upload_ is written before
// submitting commands reading it.
command_processor_.EndRenderPass();
DeferredCommandBuffer& command_buffer =
command_processor_.deferred_command_buffer();
VkBufferCopy* copy_region = command_buffer.CmdCopyBufferEmplace(
builtin_index_buffer_upload_, builtin_index_buffer_, 1);
copy_region->srcOffset = 0;
copy_region->dstOffset = 0;
copy_region->size = builtin_index_buffer_size_;
command_processor_.PushBufferMemoryBarrier(
builtin_index_buffer_, 0, VK_WHOLE_SIZE, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_INDEX_READ_BIT);
builtin_index_buffer_upload_submission_ =
command_processor_.GetCurrentSubmission();
}
}
void VulkanPrimitiveProcessor::BeginFrame() {
frame_index_buffer_pool_->Reclaim(command_processor_.GetCompletedFrame());
}
void VulkanPrimitiveProcessor::EndSubmission() {
frame_index_buffer_pool_->FlushWrites();
}
void VulkanPrimitiveProcessor::EndFrame() {
ClearPerFrameCache();
frame_index_buffers_.clear();
}
bool VulkanPrimitiveProcessor::InitializeBuiltin16BitIndexBuffer(
uint32_t index_count, std::function<void(uint16_t*)> fill_callback) {
assert_not_zero(index_count);
assert_true(builtin_index_buffer_ == VK_NULL_HANDLE);
assert_true(builtin_index_buffer_memory_ == VK_NULL_HANDLE);
assert_true(builtin_index_buffer_upload_ == VK_NULL_HANDLE);
assert_true(builtin_index_buffer_upload_memory_ == VK_NULL_HANDLE);
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
builtin_index_buffer_size_ = VkDeviceSize(sizeof(uint16_t) * index_count);
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
provider, builtin_index_buffer_size_,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
ui::vulkan::util::MemoryPurpose::kDeviceLocal, builtin_index_buffer_,
builtin_index_buffer_memory_)) {
XELOGE(
"Vulkan primitive processor: Failed to create the built-in index "
"buffer GPU resource with {} 16-bit indices",
index_count);
return false;
}
uint32_t upload_memory_type;
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
provider, builtin_index_buffer_size_,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
ui::vulkan::util::MemoryPurpose::kUpload,
builtin_index_buffer_upload_, builtin_index_buffer_upload_memory_,
&upload_memory_type)) {
XELOGE(
"Vulkan primitive processor: Failed to create the built-in index "
"buffer upload resource with {} 16-bit indices",
index_count);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
builtin_index_buffer_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
builtin_index_buffer_memory_);
return false;
}
void* mapping;
if (dfn.vkMapMemory(device, builtin_index_buffer_upload_memory_, 0,
VK_WHOLE_SIZE, 0, &mapping) != VK_SUCCESS) {
XELOGE(
"Vulkan primitive processor: Failed to map the built-in index buffer "
"upload resource with {} 16-bit indices",
index_count);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
builtin_index_buffer_upload_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
builtin_index_buffer_upload_memory_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
builtin_index_buffer_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
builtin_index_buffer_memory_);
return false;
}
fill_callback(reinterpret_cast<uint16_t*>(mapping));
ui::vulkan::util::FlushMappedMemoryRange(
provider, builtin_index_buffer_memory_, upload_memory_type);
dfn.vkUnmapMemory(device, builtin_index_buffer_upload_memory_);
// Schedule uploading in the first submission.
builtin_index_buffer_upload_submission_ = UINT64_MAX;
return true;
}
void* VulkanPrimitiveProcessor::RequestHostConvertedIndexBufferForCurrentFrame(
xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd,
uint32_t coalignment_original_address, size_t& backend_handle_out) {
size_t index_size = format == xenos::IndexFormat::kInt16 ? sizeof(uint16_t)
: sizeof(uint32_t);
VkBuffer buffer;
VkDeviceSize offset;
uint8_t* mapping = frame_index_buffer_pool_->Request(
command_processor_.GetCurrentFrame(),
index_size * index_count +
(coalign_for_simd ? XE_GPU_PRIMITIVE_PROCESSOR_SIMD_SIZE : 0),
index_size, buffer, offset);
if (!mapping) {
return nullptr;
}
if (coalign_for_simd) {
ptrdiff_t coalignment_offset =
GetSimdCoalignmentOffset(mapping, coalignment_original_address);
mapping += coalignment_offset;
offset = VkDeviceSize(offset + coalignment_offset);
}
backend_handle_out = frame_index_buffers_.size();
frame_index_buffers_.emplace_back(buffer, offset);
return mapping;
}
} // namespace vulkan
} // namespace gpu
} // namespace xe

View File

@@ -0,0 +1,92 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_VULKAN_PRIMITIVE_PROCESSOR_H_
#define XENIA_GPU_VULKAN_VULKAN_PRIMITIVE_PROCESSOR_H_
#include <memory>
#include <utility>
#include "xenia/base/assert.h"
#include "xenia/gpu/primitive_processor.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
namespace xe {
namespace gpu {
namespace vulkan {
class VulkanCommandProcessor;
class VulkanPrimitiveProcessor final : public PrimitiveProcessor {
public:
VulkanPrimitiveProcessor(const RegisterFile& register_file, Memory& memory,
TraceWriter& trace_writer,
SharedMemory& shared_memory,
VulkanCommandProcessor& command_processor)
: PrimitiveProcessor(register_file, memory, trace_writer, shared_memory),
command_processor_(command_processor) {}
~VulkanPrimitiveProcessor();
bool Initialize();
void Shutdown(bool from_destructor = false);
void ClearCache() { frame_index_buffer_pool_->ClearCache(); }
void CompletedSubmissionUpdated();
void BeginSubmission();
void BeginFrame();
void EndSubmission();
void EndFrame();
std::pair<VkBuffer, VkDeviceSize> GetBuiltinIndexBuffer(size_t handle) const {
assert_not_null(builtin_index_buffer_);
return std::make_pair(
builtin_index_buffer_,
VkDeviceSize(GetBuiltinIndexBufferOffsetBytes(handle)));
}
std::pair<VkBuffer, VkDeviceSize> GetConvertedIndexBuffer(
size_t handle) const {
return frame_index_buffers_[handle];
}
protected:
bool InitializeBuiltin16BitIndexBuffer(
uint32_t index_count,
std::function<void(uint16_t*)> fill_callback) override;
void* RequestHostConvertedIndexBufferForCurrentFrame(
xenos::IndexFormat format, uint32_t index_count, bool coalign_for_simd,
uint32_t coalignment_original_address,
size_t& backend_handle_out) override;
private:
VulkanCommandProcessor& command_processor_;
VkDeviceSize builtin_index_buffer_size_ = 0;
VkBuffer builtin_index_buffer_ = VK_NULL_HANDLE;
VkDeviceMemory builtin_index_buffer_memory_ = VK_NULL_HANDLE;
// Temporary buffer copied in the beginning of the first submission for
// uploading to builtin_index_buffer_, destroyed when the submission when it
// was uploaded is completed.
VkBuffer builtin_index_buffer_upload_ = VK_NULL_HANDLE;
VkDeviceMemory builtin_index_buffer_upload_memory_ = VK_NULL_HANDLE;
// UINT64_MAX means not uploaded yet and needs uploading in the first
// submission (if the upload buffer exists at all).
uint64_t builtin_index_buffer_upload_submission_ = UINT64_MAX;
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool> frame_index_buffer_pool_;
// Indexed by the backend handles.
std::deque<std::pair<VkBuffer, VkDeviceSize>> frame_index_buffers_;
};
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_VULKAN_PRIMITIVE_PROCESSOR_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,905 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_VULKAN_RENDER_TARGET_CACHE_H_
#define XENIA_GPU_VULKAN_VULKAN_RENDER_TARGET_CACHE_H_
#include <array>
#include <cstdint>
#include <cstring>
#include <functional>
#include <memory>
#include <unordered_map>
#include "xenia/base/hash.h"
#include "xenia/base/xxhash.h"
#include "xenia/gpu/render_target_cache.h"
#include "xenia/gpu/vulkan/vulkan_shared_memory.h"
#include "xenia/gpu/vulkan/vulkan_texture_cache.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/vulkan/single_layout_descriptor_set_pool.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
namespace xe {
namespace gpu {
namespace vulkan {
class VulkanCommandProcessor;
class VulkanRenderTargetCache final : public RenderTargetCache {
public:
union RenderPassKey {
struct {
// If emulating 2x as 4x, this is still 2x for simplicity of using this
// field to make guest-related decisions. Render pass objects are not very
// expensive, and their dependencies can't be shared between 2x-as-4x and
// true 4x MSAA passes (framebuffers because render target cache render
// targets are different for 2x and 4x guest MSAA, pipelines because the
// sample mask will have 2 samples excluded for 2x-as-4x).
xenos::MsaaSamples msaa_samples : xenos::kMsaaSamplesBits; // 2
// << 0 is depth, << 1...4 is color.
uint32_t depth_and_color_used : 1 + xenos::kMaxColorRenderTargets; // 7
// 0 for unused attachments.
// If VK_FORMAT_D24_UNORM_S8_UINT is not supported, this must be kD24FS8
// even for kD24S8.
xenos::DepthRenderTargetFormat depth_format
: xenos::kDepthRenderTargetFormatBits; // 8
// Linear or sRGB included if host sRGB is used.
xenos::ColorRenderTargetFormat color_0_view_format
: xenos::kColorRenderTargetFormatBits; // 12
xenos::ColorRenderTargetFormat color_1_view_format
: xenos::kColorRenderTargetFormatBits; // 16
xenos::ColorRenderTargetFormat color_2_view_format
: xenos::kColorRenderTargetFormatBits; // 20
xenos::ColorRenderTargetFormat color_3_view_format
: xenos::kColorRenderTargetFormatBits; // 24
uint32_t color_rts_use_transfer_formats : 1; // 25
};
uint32_t key = 0;
struct Hasher {
size_t operator()(const RenderPassKey& key) const {
return std::hash<uint32_t>{}(key.key);
}
};
bool operator==(const RenderPassKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const RenderPassKey& other_key) const {
return !(*this == other_key);
}
bool operator<(const RenderPassKey& other_key) const {
return key < other_key.key;
}
};
static_assert_size(RenderPassKey, sizeof(uint32_t));
struct Framebuffer {
VkFramebuffer framebuffer;
VkExtent2D host_extent;
Framebuffer(VkFramebuffer framebuffer, const VkExtent2D& host_extent)
: framebuffer(framebuffer), host_extent(host_extent) {}
};
VulkanRenderTargetCache(const RegisterFile& register_file,
const Memory& memory, TraceWriter& trace_writer,
uint32_t draw_resolution_scale_x,
uint32_t draw_resolution_scale_y,
VulkanCommandProcessor& command_processor);
~VulkanRenderTargetCache();
// Transient descriptor set layouts must be initialized in the command
// processor.
bool Initialize();
void Shutdown(bool from_destructor = false);
void ClearCache() override;
void CompletedSubmissionUpdated();
void EndSubmission();
// TODO(Triang3l): Fragment shader interlock.
Path GetPath() const override { return Path::kHostRenderTargets; }
// Performs the resolve to a shared memory area according to the current
// register values, and also clears the render targets if needed. Must be in a
// frame for calling.
bool Resolve(const Memory& memory, VulkanSharedMemory& shared_memory,
VulkanTextureCache& texture_cache, uint32_t& written_address_out,
uint32_t& written_length_out);
bool Update(bool is_rasterization_done,
reg::RB_DEPTHCONTROL normalized_depth_control,
uint32_t normalized_color_mask,
const Shader& vertex_shader) override;
// Binding information for the last successful update.
RenderPassKey last_update_render_pass_key() const {
return last_update_render_pass_key_;
}
VkRenderPass last_update_render_pass() const {
return last_update_render_pass_;
}
const Framebuffer* last_update_framebuffer() const {
return last_update_framebuffer_;
}
// Using R16G16[B16A16]_SNORM, which are -1...1, not the needed -32...32.
// Persistent data doesn't depend on this, so can be overriden by per-game
// configuration.
bool IsFixedRG16TruncatedToMinus1To1() const {
// TODO(Triang3l): Not float16 condition.
return GetPath() == Path::kHostRenderTargets &&
!cvars::snorm16_render_target_full_range;
}
bool IsFixedRGBA16TruncatedToMinus1To1() const {
// TODO(Triang3l): Not float16 condition.
return GetPath() == Path::kHostRenderTargets &&
!cvars::snorm16_render_target_full_range;
}
bool depth_unorm24_vulkan_format_supported() const {
return depth_unorm24_vulkan_format_supported_;
}
bool depth_float24_round() const { return depth_float24_round_; }
bool msaa_2x_attachments_supported() const {
return msaa_2x_attachments_supported_;
}
bool msaa_2x_no_attachments_supported() const {
return msaa_2x_no_attachments_supported_;
}
bool IsMsaa2xSupported(bool subpass_has_attachments) const {
return subpass_has_attachments ? msaa_2x_attachments_supported_
: msaa_2x_no_attachments_supported_;
}
// Returns the render pass object, or VK_NULL_HANDLE if failed to create.
// A render pass managed by the render target cache may be ended and resumed
// at any time (to allow for things like copying and texture loading).
VkRenderPass GetRenderPass(RenderPassKey key);
VkFormat GetDepthVulkanFormat(xenos::DepthRenderTargetFormat format) const;
VkFormat GetColorVulkanFormat(xenos::ColorRenderTargetFormat format) const;
VkFormat GetColorOwnershipTransferVulkanFormat(
xenos::ColorRenderTargetFormat format,
bool* is_integer_out = nullptr) const;
protected:
uint32_t GetMaxRenderTargetWidth() const override;
uint32_t GetMaxRenderTargetHeight() const override;
RenderTarget* CreateRenderTarget(RenderTargetKey key) override;
bool IsHostDepthEncodingDifferent(
xenos::DepthRenderTargetFormat format) const override;
private:
enum class EdramBufferUsage {
// There's no need for combined fragment and compute usages.
// With host render targets, the usual usage sequence is as follows:
// - Optionally compute writes - host depth copy storing for EDRAM range
// ownership transfers.
// - Optionally fragment reads - host depth copy storing for EDRAM range
// ownership transfers.
// - Compute writes - copying from host render targets during resolving.
// - Compute reads - writing to the shared memory during resolving.
// With the render backend implementation based on fragment shader
// interlocks, it's:
// - Fragment reads and writes - depth / stencil and color operations.
// - Compute reads - writing to the shared memory during resolving.
// So, fragment reads and compute reads normally don't follow each other,
// and there's no need to amortize the cost of a read > read barrier in an
// exceptional situation by using a wider barrier in the normal scenario.
// Host depth copy storing.
kFragmentRead,
// Fragment shader interlock depth / stencil and color operations.
kFragmentReadWrite,
// Resolve - copying to the shared memory.
kComputeRead,
// Resolve - copying from host render targets.
kComputeWrite,
// Trace recording.
kTransferRead,
// Trace playback.
kTransferWrite,
};
enum class EdramBufferModificationStatus {
// The values are ordered by how strong the barrier conditions are.
// No uncommitted shader writes.
kUnmodified,
// Need to commit before the next fragment shader interlock usage with
// overlap.
kViaFragmentShaderInterlock,
// Need to commit before any next fragment shader interlock usage.
kViaUnordered,
};
enum ResolveCopyDescriptorSet : uint32_t {
// Never changes.
kResolveCopyDescriptorSetEdram,
// Shared memory or a region in it.
kResolveCopyDescriptorSetDest,
kResolveCopyDescriptorSetCount,
};
struct ResolveCopyShaderCode {
const uint32_t* unscaled;
size_t unscaled_size_bytes;
const uint32_t* scaled;
size_t scaled_size_bytes;
};
static void GetEdramBufferUsageMasks(EdramBufferUsage usage,
VkPipelineStageFlags& stage_mask_out,
VkAccessFlags& access_mask_out);
void UseEdramBuffer(EdramBufferUsage new_usage);
void MarkEdramBufferModified(
EdramBufferModificationStatus modification_status =
EdramBufferModificationStatus::kViaUnordered);
void CommitEdramBufferShaderWrites(
EdramBufferModificationStatus commit_status =
EdramBufferModificationStatus::kViaFragmentShaderInterlock);
VulkanCommandProcessor& command_processor_;
TraceWriter& trace_writer_;
// Accessible in fragment and compute shaders.
VkDescriptorSetLayout descriptor_set_layout_storage_buffer_ = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptor_set_layout_sampled_image_ = VK_NULL_HANDLE;
VkDescriptorSetLayout descriptor_set_layout_sampled_image_x2_ =
VK_NULL_HANDLE;
std::unique_ptr<ui::vulkan::SingleLayoutDescriptorSetPool>
descriptor_set_pool_sampled_image_;
std::unique_ptr<ui::vulkan::SingleLayoutDescriptorSetPool>
descriptor_set_pool_sampled_image_x2_;
VkDeviceMemory edram_buffer_memory_ = VK_NULL_HANDLE;
VkBuffer edram_buffer_ = VK_NULL_HANDLE;
EdramBufferUsage edram_buffer_usage_;
EdramBufferModificationStatus edram_buffer_modification_status_ =
EdramBufferModificationStatus::kUnmodified;
VkDescriptorPool edram_storage_buffer_descriptor_pool_ = VK_NULL_HANDLE;
VkDescriptorSet edram_storage_buffer_descriptor_set_;
VkPipelineLayout resolve_copy_pipeline_layout_ = VK_NULL_HANDLE;
static const ResolveCopyShaderCode
kResolveCopyShaders[size_t(draw_util::ResolveCopyShaderIndex::kCount)];
std::array<VkPipeline, size_t(draw_util::ResolveCopyShaderIndex::kCount)>
resolve_copy_pipelines_{};
// RenderPassKey::key -> VkRenderPass.
// VK_NULL_HANDLE if failed to create.
std::unordered_map<uint32_t, VkRenderPass> render_passes_;
// For host render targets.
// Can only be destroyed when framebuffers referencing it are destroyed!
class VulkanRenderTarget final : public RenderTarget {
public:
static constexpr VkPipelineStageFlags kColorDrawStageMask =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
static constexpr VkAccessFlags kColorDrawAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
static constexpr VkImageLayout kColorDrawLayout =
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
static constexpr VkPipelineStageFlags kDepthDrawStageMask =
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
static constexpr VkAccessFlags kDepthDrawAccessMask =
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
static constexpr VkImageLayout kDepthDrawLayout =
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
// Takes ownership of the Vulkan objects passed to the constructor.
VulkanRenderTarget(RenderTargetKey key,
VulkanRenderTargetCache& render_target_cache,
VkImage image, VkDeviceMemory memory,
VkImageView view_depth_color,
VkImageView view_depth_stencil, VkImageView view_stencil,
VkImageView view_srgb,
VkImageView view_color_transfer_separate,
size_t descriptor_set_index_transfer_source)
: RenderTarget(key),
render_target_cache_(render_target_cache),
image_(image),
memory_(memory),
view_depth_color_(view_depth_color),
view_depth_stencil_(view_depth_stencil),
view_stencil_(view_stencil),
view_srgb_(view_srgb),
view_color_transfer_separate_(view_color_transfer_separate),
descriptor_set_index_transfer_source_(
descriptor_set_index_transfer_source) {}
~VulkanRenderTarget();
VkImage image() const { return image_; }
VkImageView view_depth_color() const { return view_depth_color_; }
VkImageView view_depth_stencil() const { return view_depth_stencil_; }
VkImageView view_color_transfer_separate() const {
return view_color_transfer_separate_;
}
VkImageView view_color_transfer() const {
return view_color_transfer_separate_ != VK_NULL_HANDLE
? view_color_transfer_separate_
: view_depth_color_;
}
VkDescriptorSet GetDescriptorSetTransferSource() const {
ui::vulkan::SingleLayoutDescriptorSetPool& descriptor_set_pool =
key().is_depth
? *render_target_cache_.descriptor_set_pool_sampled_image_x2_
: *render_target_cache_.descriptor_set_pool_sampled_image_;
return descriptor_set_pool.Get(descriptor_set_index_transfer_source_);
}
static void GetDrawUsage(bool is_depth,
VkPipelineStageFlags* stage_mask_out,
VkAccessFlags* access_mask_out,
VkImageLayout* layout_out) {
if (stage_mask_out) {
*stage_mask_out = is_depth ? kDepthDrawStageMask : kColorDrawStageMask;
}
if (access_mask_out) {
*access_mask_out =
is_depth ? kDepthDrawAccessMask : kColorDrawAccessMask;
}
if (layout_out) {
*layout_out = is_depth ? kDepthDrawLayout : kColorDrawLayout;
}
}
void GetDrawUsage(VkPipelineStageFlags* stage_mask_out,
VkAccessFlags* access_mask_out,
VkImageLayout* layout_out) const {
GetDrawUsage(key().is_depth, stage_mask_out, access_mask_out, layout_out);
}
VkPipelineStageFlags current_stage_mask() const {
return current_stage_mask_;
}
VkAccessFlags current_access_mask() const { return current_access_mask_; }
VkImageLayout current_layout() const { return current_layout_; }
void SetUsage(VkPipelineStageFlags stage_mask, VkAccessFlags access_mask,
VkImageLayout layout) {
current_stage_mask_ = stage_mask;
current_access_mask_ = access_mask;
current_layout_ = layout;
}
uint32_t temporary_sort_index() const { return temporary_sort_index_; }
void SetTemporarySortIndex(uint32_t index) {
temporary_sort_index_ = index;
}
private:
VulkanRenderTargetCache& render_target_cache_;
VkImage image_;
VkDeviceMemory memory_;
// TODO(Triang3l): Per-format drawing views for mutable formats with EDRAM
// aliasing without transfers.
VkImageView view_depth_color_;
// Optional views.
VkImageView view_depth_stencil_;
VkImageView view_stencil_;
VkImageView view_srgb_;
VkImageView view_color_transfer_separate_;
// 2 sampled images for depth / stencil, 1 sampled image for color.
size_t descriptor_set_index_transfer_source_;
VkPipelineStageFlags current_stage_mask_ = 0;
VkAccessFlags current_access_mask_ = 0;
VkImageLayout current_layout_ = VK_IMAGE_LAYOUT_UNDEFINED;
// Temporary storage for indices in operations like transfers and dumps.
uint32_t temporary_sort_index_ = 0;
};
struct FramebufferKey {
RenderPassKey render_pass_key;
// Same as RenderTargetKey::pitch_tiles_at_32bpp.
uint32_t pitch_tiles_at_32bpp : 8; // 8
// [0, 2047].
uint32_t depth_base_tiles : xenos::kEdramBaseTilesBits - 1; // 19
uint32_t color_0_base_tiles : xenos::kEdramBaseTilesBits - 1; // 30
uint32_t color_1_base_tiles : xenos::kEdramBaseTilesBits - 1; // 43
uint32_t color_2_base_tiles : xenos::kEdramBaseTilesBits - 1; // 54
uint32_t color_3_base_tiles : xenos::kEdramBaseTilesBits - 1; // 75
// Including all the padding, for a stable hash.
FramebufferKey() { Reset(); }
FramebufferKey(const FramebufferKey& key) {
std::memcpy(this, &key, sizeof(*this));
}
FramebufferKey& operator=(const FramebufferKey& key) {
std::memcpy(this, &key, sizeof(*this));
return *this;
}
bool operator==(const FramebufferKey& key) const {
return std::memcmp(this, &key, sizeof(*this)) == 0;
}
using Hasher = xe::hash::XXHasher<FramebufferKey>;
void Reset() { std::memset(this, 0, sizeof(*this)); }
};
enum TransferUsedDescriptorSet : uint32_t {
// Ordered from the least to the most frequently changed.
kTransferUsedDescriptorSetHostDepthBuffer,
kTransferUsedDescriptorSetHostDepthStencilTextures,
kTransferUsedDescriptorSetDepthStencilTextures,
// Mutually exclusive with kTransferUsedDescriptorSetDepthStencilTextures.
kTransferUsedDescriptorSetColorTexture,
kTransferUsedDescriptorSetCount,
kTransferUsedDescriptorSetHostDepthBufferBit =
uint32_t(1) << kTransferUsedDescriptorSetHostDepthBuffer,
kTransferUsedDescriptorSetHostDepthStencilTexturesBit =
uint32_t(1) << kTransferUsedDescriptorSetHostDepthStencilTextures,
kTransferUsedDescriptorSetDepthStencilTexturesBit =
uint32_t(1) << kTransferUsedDescriptorSetDepthStencilTextures,
kTransferUsedDescriptorSetColorTextureBit =
uint32_t(1) << kTransferUsedDescriptorSetColorTexture,
};
// 32-bit push constants (for simplicity of size calculation and to avoid
// std140 packing issues).
enum TransferUsedPushConstantDword : uint32_t {
kTransferUsedPushConstantDwordHostDepthAddress,
kTransferUsedPushConstantDwordAddress,
// Changed 8 times per transfer.
kTransferUsedPushConstantDwordStencilMask,
kTransferUsedPushConstantDwordCount,
kTransferUsedPushConstantDwordHostDepthAddressBit =
uint32_t(1) << kTransferUsedPushConstantDwordHostDepthAddress,
kTransferUsedPushConstantDwordAddressBit =
uint32_t(1) << kTransferUsedPushConstantDwordAddress,
kTransferUsedPushConstantDwordStencilMaskBit =
uint32_t(1) << kTransferUsedPushConstantDwordStencilMask,
};
enum class TransferPipelineLayoutIndex {
kColor,
kDepth,
kColorToStencilBit,
kDepthToStencilBit,
kColorAndHostDepthTexture,
kColorAndHostDepthBuffer,
kDepthAndHostDepthTexture,
kDepthAndHostDepthBuffer,
kCount,
};
struct TransferPipelineLayoutInfo {
uint32_t used_descriptor_sets;
uint32_t used_push_constant_dwords;
};
static const TransferPipelineLayoutInfo
kTransferPipelineLayoutInfos[size_t(TransferPipelineLayoutIndex::kCount)];
enum class TransferMode : uint32_t {
kColorToDepth,
kColorToColor,
kDepthToDepth,
kDepthToColor,
kColorToStencilBit,
kDepthToStencilBit,
// Two-source modes, using the host depth if it, when converted to the guest
// format, matches what's in the owner source (not modified, keep host
// precision), or the guest data otherwise (significantly modified, possibly
// cleared). Stencil for FragStencilRef is always taken from the guest
// source.
kColorAndHostDepthToDepth,
// When using different source and destination depth formats.
kDepthAndHostDepthToDepth,
// If host depth is fetched, but it's the same image as the destination,
// it's copied to the EDRAM buffer (but since it's just a scratch buffer,
// with tiles laid out linearly with the same pitch as in the original
// render target; also no swapping of 40-sample columns as opposed to the
// host render target - this is done only for the color source) and fetched
// from there instead of the host depth texture.
kColorAndHostDepthCopyToDepth,
kDepthAndHostDepthCopyToDepth,
kCount,
};
enum class TransferOutput {
kColor,
kDepth,
kStencilBit,
};
struct TransferModeInfo {
TransferOutput output;
TransferPipelineLayoutIndex pipeline_layout;
};
static const TransferModeInfo kTransferModes[size_t(TransferMode::kCount)];
union TransferShaderKey {
uint32_t key;
struct {
xenos::MsaaSamples dest_msaa_samples : xenos::kMsaaSamplesBits;
uint32_t dest_color_rt_index : xenos::kColorRenderTargetIndexBits;
uint32_t dest_resource_format : xenos::kRenderTargetFormatBits;
xenos::MsaaSamples source_msaa_samples : xenos::kMsaaSamplesBits;
// Always 1x when the host depth is a copy from a buffer rather than an
// image, not to create the same pipeline for different MSAA sample counts
// as it doesn't matter in this case.
xenos::MsaaSamples host_depth_source_msaa_samples
: xenos::kMsaaSamplesBits;
uint32_t source_resource_format : xenos::kRenderTargetFormatBits;
// Last bits because this affects the pipeline layout - after sorting,
// only change it as fewer times as possible. Depth buffers have an
// additional stencil texture.
static_assert(size_t(TransferMode::kCount) <= (size_t(1) << 4));
TransferMode mode : 4;
};
TransferShaderKey() : key(0) { static_assert_size(*this, sizeof(key)); }
struct Hasher {
size_t operator()(const TransferShaderKey& key) const {
return std::hash<uint32_t>{}(key.key);
}
};
bool operator==(const TransferShaderKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const TransferShaderKey& other_key) const {
return !(*this == other_key);
}
bool operator<(const TransferShaderKey& other_key) const {
return key < other_key.key;
}
};
struct TransferPipelineKey {
RenderPassKey render_pass_key;
TransferShaderKey shader_key;
TransferPipelineKey(RenderPassKey render_pass_key,
TransferShaderKey shader_key)
: render_pass_key(render_pass_key), shader_key(shader_key) {}
struct Hasher {
size_t operator()(const TransferPipelineKey& key) const {
XXH3_state_t hash_state;
XXH3_64bits_reset(&hash_state);
XXH3_64bits_update(&hash_state, &key.render_pass_key,
sizeof(key.render_pass_key));
XXH3_64bits_update(&hash_state, &key.shader_key,
sizeof(key.shader_key));
return static_cast<size_t>(XXH3_64bits_digest(&hash_state));
}
};
bool operator==(const TransferPipelineKey& other_key) const {
return render_pass_key == other_key.render_pass_key &&
shader_key == other_key.shader_key;
}
bool operator!=(const TransferPipelineKey& other_key) const {
return !(*this == other_key);
}
bool operator<(const TransferPipelineKey& other_key) const {
if (render_pass_key != other_key.render_pass_key) {
return render_pass_key < other_key.render_pass_key;
}
return shader_key < other_key.shader_key;
}
};
union TransferAddressConstant {
uint32_t constant;
struct {
// All in tiles.
uint32_t dest_pitch : xenos::kEdramPitchTilesBits;
uint32_t source_pitch : xenos::kEdramPitchTilesBits;
// Safe to use 12 bits for signed difference - no ownership transfer can
// ever occur between render targets with EDRAM base >= 2048 as this would
// result in 0-length spans. 10 + 10 + 12 is exactly 32, any more bits,
// and more root 32-bit constants will be used.
// Destination base in tiles minus source base in tiles (not vice versa
// because this is a transform of the coordinate system, not addresses
// themselves).
// 0 for host_depth_source_is_copy (ignored in this case anyway as
// destination == source anyway).
int32_t source_to_dest : xenos::kEdramBaseTilesBits;
};
TransferAddressConstant() : constant(0) {
static_assert_size(*this, sizeof(constant));
}
bool operator==(const TransferAddressConstant& other_constant) const {
return constant == other_constant.constant;
}
bool operator!=(const TransferAddressConstant& other_constant) const {
return !(*this == other_constant);
}
};
struct TransferInvocation {
Transfer transfer;
TransferShaderKey shader_key;
TransferInvocation(const Transfer& transfer,
const TransferShaderKey& shader_key)
: transfer(transfer), shader_key(shader_key) {}
bool operator<(const TransferInvocation& other_invocation) {
// TODO(Triang3l): See if it may be better to sort by the source in the
// first place, especially when reading the same data multiple times (like
// to write the stencil bits after depth) for better read locality.
// Sort by the shader key primarily to reduce pipeline state (context)
// switches.
if (shader_key != other_invocation.shader_key) {
return shader_key < other_invocation.shader_key;
}
// Host depth render targets are changed rarely if they exist, won't save
// many binding changes, ignore them for simplicity (their existence is
// caught by the shader key change).
assert_not_null(transfer.source);
assert_not_null(other_invocation.transfer.source);
uint32_t source_index =
static_cast<const VulkanRenderTarget*>(transfer.source)
->temporary_sort_index();
uint32_t other_source_index = static_cast<const VulkanRenderTarget*>(
other_invocation.transfer.source)
->temporary_sort_index();
if (source_index != other_source_index) {
return source_index < other_source_index;
}
return transfer.start_tiles < other_invocation.transfer.start_tiles;
}
bool CanBeMergedIntoOneDraw(
const TransferInvocation& other_invocation) const {
return shader_key == other_invocation.shader_key &&
transfer.AreSourcesSame(other_invocation.transfer);
}
};
union DumpPipelineKey {
uint32_t key;
struct {
xenos::MsaaSamples msaa_samples : 2;
uint32_t resource_format : 4;
// Last bit because this affects the pipeline - after sorting, only change
// it at most once. Depth buffers have an additional stencil SRV.
uint32_t is_depth : 1;
};
DumpPipelineKey() : key(0) { static_assert_size(*this, sizeof(key)); }
struct Hasher {
size_t operator()(const DumpPipelineKey& key) const {
return std::hash<uint32_t>{}(key.key);
}
};
bool operator==(const DumpPipelineKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const DumpPipelineKey& other_key) const {
return !(*this == other_key);
}
bool operator<(const DumpPipelineKey& other_key) const {
return key < other_key.key;
}
xenos::ColorRenderTargetFormat GetColorFormat() const {
assert_false(is_depth);
return xenos::ColorRenderTargetFormat(resource_format);
}
xenos::DepthRenderTargetFormat GetDepthFormat() const {
assert_true(is_depth);
return xenos::DepthRenderTargetFormat(resource_format);
}
};
// There's no strict dependency on the group size in dumping, for simplicity
// calculations especially with resolution scaling, dividing manually (as the
// group size is not unlimited). The only restriction is that an integer
// multiple of it must be 80x16 samples (and no larger than that) for 32bpp,
// or 40x16 samples for 64bpp (because only a half of the pair of tiles may
// need to be dumped). Using 8x16 since that's 128 - the minimum required
// group size on Vulkan, and the maximum number of lanes in a subgroup on
// Vulkan.
static constexpr uint32_t kDumpSamplesPerGroupX = 8;
static constexpr uint32_t kDumpSamplesPerGroupY = 16;
union DumpPitches {
uint32_t pitches;
struct {
// Both in tiles.
uint32_t dest_pitch : xenos::kEdramPitchTilesBits;
uint32_t source_pitch : xenos::kEdramPitchTilesBits;
};
DumpPitches() : pitches(0) { static_assert_size(*this, sizeof(pitches)); }
bool operator==(const DumpPitches& other_pitches) const {
return pitches == other_pitches.pitches;
}
bool operator!=(const DumpPitches& other_pitches) const {
return !(*this == other_pitches);
}
};
union DumpOffsets {
uint32_t offsets;
struct {
uint32_t dispatch_first_tile : xenos::kEdramBaseTilesBits;
uint32_t source_base_tiles : xenos::kEdramBaseTilesBits;
};
DumpOffsets() : offsets(0) { static_assert_size(*this, sizeof(offsets)); }
bool operator==(const DumpOffsets& other_offsets) const {
return offsets == other_offsets.offsets;
}
bool operator!=(const DumpOffsets& other_offsets) const {
return !(*this == other_offsets);
}
};
enum DumpDescriptorSet : uint32_t {
// Never changes. Same in both color and depth pipeline layouts, keep the
// first for pipeline layout compatibility, to only have to set it once.
kDumpDescriptorSetEdram,
// One resolve may need multiple sources. Different descriptor set layouts
// for color and depth.
kDumpDescriptorSetSource,
kDumpDescriptorSetCount,
};
enum DumpPushConstant : uint32_t {
// May be different for different sources.
kDumpPushConstantPitches,
// May be changed multiple times for the same source.
kDumpPushConstantOffsets,
kDumpPushConstantCount,
};
struct DumpInvocation {
ResolveCopyDumpRectangle rectangle;
DumpPipelineKey pipeline_key;
DumpInvocation(const ResolveCopyDumpRectangle& rectangle,
const DumpPipelineKey& pipeline_key)
: rectangle(rectangle), pipeline_key(pipeline_key) {}
bool operator<(const DumpInvocation& other_invocation) {
// Sort by the pipeline key primarily to reduce pipeline state (context)
// switches.
if (pipeline_key != other_invocation.pipeline_key) {
return pipeline_key < other_invocation.pipeline_key;
}
assert_not_null(rectangle.render_target);
uint32_t render_target_index =
static_cast<const VulkanRenderTarget*>(rectangle.render_target)
->temporary_sort_index();
const ResolveCopyDumpRectangle& other_rectangle =
other_invocation.rectangle;
uint32_t other_render_target_index =
static_cast<const VulkanRenderTarget*>(other_rectangle.render_target)
->temporary_sort_index();
if (render_target_index != other_render_target_index) {
return render_target_index < other_render_target_index;
}
if (rectangle.row_first != other_rectangle.row_first) {
return rectangle.row_first < other_rectangle.row_first;
}
return rectangle.row_first_start < other_rectangle.row_first_start;
}
};
// Returns the framebuffer object, or VK_NULL_HANDLE if failed to create.
const Framebuffer* GetFramebuffer(
RenderPassKey render_pass_key, uint32_t pitch_tiles_at_32bpp,
const RenderTarget* const* depth_and_color_render_targets);
VkShaderModule GetTransferShader(TransferShaderKey key);
// With sample-rate shading, returns a pointer to one pipeline. Without
// sample-rate shading, returns a pointer to as many pipelines as there are
// samples. If there was a failure to create a pipeline, returns nullptr.
VkPipeline const* GetTransferPipelines(TransferPipelineKey key);
// Do ownership transfers for render targets - each render target / vector may
// be null / empty in case there's nothing to do for them.
// resolve_clear_rectangle is expected to be provided by
// PrepareHostRenderTargetsResolveClear which should do all the needed size
// bound checks.
void PerformTransfersAndResolveClears(
uint32_t render_target_count, RenderTarget* const* render_targets,
const std::vector<Transfer>* render_target_transfers,
const uint64_t* render_target_resolve_clear_values = nullptr,
const Transfer::Rectangle* resolve_clear_rectangle = nullptr);
VkPipeline GetDumpPipeline(DumpPipelineKey key);
// Writes contents of host render targets within rectangles from
// ResolveInfo::GetCopyEdramTileSpan to edram_buffer_.
void DumpRenderTargets(uint32_t dump_base, uint32_t dump_row_length_used,
uint32_t dump_rows, uint32_t dump_pitch);
bool gamma_render_target_as_srgb_ = false;
bool depth_unorm24_vulkan_format_supported_ = false;
bool depth_float24_round_ = false;
bool msaa_2x_attachments_supported_ = false;
bool msaa_2x_no_attachments_supported_ = false;
std::unordered_map<FramebufferKey, Framebuffer, FramebufferKey::Hasher>
framebuffers_;
RenderPassKey last_update_render_pass_key_;
VkRenderPass last_update_render_pass_ = VK_NULL_HANDLE;
uint32_t last_update_framebuffer_pitch_tiles_at_32bpp_ = 0;
const RenderTarget* const*
last_update_framebuffer_attachments_[1 + xenos::kMaxColorRenderTargets] =
{};
const Framebuffer* last_update_framebuffer_ = VK_NULL_HANDLE;
// Set 0 - EDRAM storage buffer, set 1 - source depth sampled image (and
// unused stencil from the transfer descriptor set), HostDepthStoreConstants
// passed via push constants.
VkPipelineLayout host_depth_store_pipeline_layout_ = VK_NULL_HANDLE;
VkPipeline host_depth_store_pipelines_[size_t(xenos::MsaaSamples::k4X) + 1] =
{};
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool>
transfer_vertex_buffer_pool_;
VkShaderModule transfer_passthrough_vertex_shader_ = VK_NULL_HANDLE;
VkPipelineLayout transfer_pipeline_layouts_[size_t(
TransferPipelineLayoutIndex::kCount)] = {};
// VK_NULL_HANDLE if failed to create.
std::unordered_map<TransferShaderKey, VkShaderModule,
TransferShaderKey::Hasher>
transfer_shaders_;
// With sample-rate shading, one pipeline per entry. Without sample-rate
// shading, one pipeline per sample per entry. VK_NULL_HANDLE if failed to
// create.
std::unordered_map<TransferPipelineKey, std::array<VkPipeline, 4>,
TransferPipelineKey::Hasher>
transfer_pipelines_;
VkPipelineLayout dump_pipeline_layout_color_ = VK_NULL_HANDLE;
VkPipelineLayout dump_pipeline_layout_depth_ = VK_NULL_HANDLE;
// Compute pipelines for copying host render target contents to the EDRAM
// buffer. VK_NULL_HANDLE if failed to create.
std::unordered_map<DumpPipelineKey, VkPipeline, DumpPipelineKey::Hasher>
dump_pipelines_;
// Temporary storage for Resolve.
std::vector<Transfer> clear_transfers_[2];
// Temporary storage for PerformTransfersAndResolveClears.
std::vector<TransferInvocation> current_transfer_invocations_;
// Temporary storage for DumpRenderTargets.
std::vector<ResolveCopyDumpRectangle> dump_rectangles_;
std::vector<DumpInvocation> dump_invocations_;
};
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_VULKAN_RENDER_TARGET_CACHE_H_

View File

@@ -2,24 +2,59 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/vulkan/vulkan_shader.h"
#include "third_party/fmt/include/fmt/format.h"
#include "xenia/base/assert.h"
#include <cstdint>
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/vulkan/vulkan_util.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace gpu {
namespace vulkan {
using xe::ui::vulkan::util::CheckResult;
VulkanShader::VulkanTranslation::~VulkanTranslation() {
if (shader_module_) {
const ui::vulkan::VulkanProvider& provider =
static_cast<const VulkanShader&>(shader()).provider_;
provider.dfn().vkDestroyShaderModule(provider.device(), shader_module_,
nullptr);
}
}
VkShaderModule VulkanShader::VulkanTranslation::GetOrCreateShaderModule() {
if (!is_valid()) {
return VK_NULL_HANDLE;
}
if (shader_module_ != VK_NULL_HANDLE) {
return shader_module_;
}
const ui::vulkan::VulkanProvider& provider =
static_cast<const VulkanShader&>(shader()).provider_;
VkShaderModuleCreateInfo shader_module_create_info;
shader_module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shader_module_create_info.pNext = nullptr;
shader_module_create_info.flags = 0;
shader_module_create_info.codeSize = translated_binary().size();
shader_module_create_info.pCode =
reinterpret_cast<const uint32_t*>(translated_binary().data());
if (provider.dfn().vkCreateShaderModule(provider.device(),
&shader_module_create_info, nullptr,
&shader_module_) != VK_SUCCESS) {
XELOGE(
"VulkanShader::VulkanTranslation: Failed to create a Vulkan shader "
"module for shader {:016X} modification {:016X}",
shader().ucode_data_hash(), modification());
MakeInvalid();
return VK_NULL_HANDLE;
}
return shader_module_;
}
VulkanShader::VulkanShader(const ui::vulkan::VulkanProvider& provider,
xenos::ShaderType shader_type,
@@ -27,60 +62,10 @@ VulkanShader::VulkanShader(const ui::vulkan::VulkanProvider& provider,
const uint32_t* ucode_dwords,
size_t ucode_dword_count,
std::endian ucode_source_endian)
: Shader(shader_type, ucode_data_hash, ucode_dwords, ucode_dword_count,
ucode_source_endian),
: SpirvShader(shader_type, ucode_data_hash, ucode_dwords, ucode_dword_count,
ucode_source_endian),
provider_(provider) {}
VulkanShader::VulkanTranslation::~VulkanTranslation() {
if (shader_module_) {
const ui::vulkan::VulkanProvider& provider =
static_cast<VulkanShader&>(shader()).provider_;
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
dfn.vkDestroyShaderModule(device, shader_module_, nullptr);
shader_module_ = nullptr;
}
}
bool VulkanShader::VulkanTranslation::Prepare() {
assert_null(shader_module_);
assert_true(is_valid());
const VulkanShader& vulkan_shader = static_cast<VulkanShader&>(shader());
const ui::vulkan::VulkanProvider& provider = vulkan_shader.provider_;
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
// Create the shader module.
VkShaderModuleCreateInfo shader_info;
shader_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shader_info.pNext = nullptr;
shader_info.flags = 0;
shader_info.codeSize = translated_binary().size();
shader_info.pCode =
reinterpret_cast<const uint32_t*>(translated_binary().data());
auto status =
dfn.vkCreateShaderModule(device, &shader_info, nullptr, &shader_module_);
CheckResult(status, "vkCreateShaderModule");
char type_char;
switch (vulkan_shader.type()) {
case xenos::ShaderType::kVertex:
type_char = 'v';
break;
case xenos::ShaderType::kPixel:
type_char = 'p';
break;
default:
type_char = 'u';
}
provider.SetDeviceObjectName(
VK_OBJECT_TYPE_SHADER_MODULE, uint64_t(shader_module_),
fmt::format("S({}): {:016X}", type_char, vulkan_shader.ucode_data_hash())
.c_str());
return status == VK_SUCCESS;
}
Shader::Translation* VulkanShader::CreateTranslationInstance(
uint64_t modification) {
return new VulkanTranslation(*this, modification);

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -10,42 +10,68 @@
#ifndef XENIA_GPU_VULKAN_VULKAN_SHADER_H_
#define XENIA_GPU_VULKAN_VULKAN_SHADER_H_
#include <string>
#include <cstdint>
#include "xenia/gpu/shader.h"
#include "xenia/gpu/spirv_shader.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace gpu {
namespace vulkan {
class VulkanShader : public Shader {
class VulkanShader : public SpirvShader {
public:
class VulkanTranslation : public Translation {
class VulkanTranslation : public SpirvTranslation {
public:
VulkanTranslation(VulkanShader& shader, uint64_t modification)
: Translation(shader, modification) {}
explicit VulkanTranslation(VulkanShader& shader, uint64_t modification)
: SpirvTranslation(shader, modification) {}
~VulkanTranslation() override;
bool Prepare();
// Available only if the translation is_valid and has been prepared.
VkShaderModule GetOrCreateShaderModule();
VkShaderModule shader_module() const { return shader_module_; }
private:
VkShaderModule shader_module_ = nullptr;
VkShaderModule shader_module_ = VK_NULL_HANDLE;
};
VulkanShader(const ui::vulkan::VulkanProvider& provider,
xenos::ShaderType shader_type, uint64_t ucode_data_hash,
const uint32_t* ucode_dwords, size_t ucode_dword_count,
std::endian ucode_source_endian = std::endian::big);
explicit VulkanShader(const ui::vulkan::VulkanProvider& provider,
xenos::ShaderType shader_type, uint64_t ucode_data_hash,
const uint32_t* ucode_dwords, size_t ucode_dword_count,
std::endian ucode_source_endian = std::endian::big);
// For owning subsystem like the pipeline cache, accessors for unique
// identifiers (used instead of hashes to make sure collisions can't happen)
// of binding layouts used by the shader, for invalidation if a shader with an
// incompatible layout has been bound.
size_t GetTextureBindingLayoutUserUID() const {
return texture_binding_layout_user_uid_;
}
size_t GetSamplerBindingLayoutUserUID() const {
return sampler_binding_layout_user_uid_;
}
// Modifications of the same shader can be translated on different threads.
// The "set" function must only be called if "enter" returned true - these are
// set up only once.
bool EnterBindingLayoutUserUIDSetup() {
return !binding_layout_user_uids_set_up_.test_and_set();
}
void SetTextureBindingLayoutUserUID(size_t uid) {
texture_binding_layout_user_uid_ = uid;
}
void SetSamplerBindingLayoutUserUID(size_t uid) {
sampler_binding_layout_user_uid_ = uid;
}
protected:
Translation* CreateTranslationInstance(uint64_t modification) override;
private:
const ui::vulkan::VulkanProvider& provider_;
std::atomic_flag binding_layout_user_uids_set_up_ = ATOMIC_FLAG_INIT;
size_t texture_binding_layout_user_uid_ = 0;
size_t sampler_binding_layout_user_uid_ = 0;
};
} // namespace vulkan

View File

@@ -0,0 +1,499 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/gpu/vulkan/vulkan_shared_memory.h"
#include <algorithm>
#include <cstring>
#include <utility>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/gpu/vulkan/deferred_command_buffer.h"
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
#include "xenia/ui/vulkan/vulkan_util.h"
DEFINE_bool(vulkan_sparse_shared_memory, true,
"Enable sparse binding for shared memory emulation. Disabling it "
"increases video memory usage - a 512 MB buffer is created - but "
"allows graphics debuggers that don't support sparse binding to "
"work.",
"Vulkan");
namespace xe {
namespace gpu {
namespace vulkan {
VulkanSharedMemory::VulkanSharedMemory(
VulkanCommandProcessor& command_processor, Memory& memory,
TraceWriter& trace_writer,
VkPipelineStageFlags guest_shader_pipeline_stages)
: SharedMemory(memory),
command_processor_(command_processor),
trace_writer_(trace_writer),
guest_shader_pipeline_stages_(guest_shader_pipeline_stages) {}
VulkanSharedMemory::~VulkanSharedMemory() { Shutdown(true); }
bool VulkanSharedMemory::Initialize() {
InitializeCommon();
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
const VkPhysicalDeviceFeatures& device_features = provider.device_features();
const VkBufferCreateFlags sparse_flags =
VK_BUFFER_CREATE_SPARSE_BINDING_BIT |
VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT;
// Try to create a sparse buffer.
VkBufferCreateInfo buffer_create_info;
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.pNext = nullptr;
buffer_create_info.flags = sparse_flags;
buffer_create_info.size = kBufferSize;
buffer_create_info.usage =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
buffer_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
buffer_create_info.queueFamilyIndexCount = 0;
buffer_create_info.pQueueFamilyIndices = nullptr;
if (cvars::vulkan_sparse_shared_memory &&
provider.IsSparseBindingSupported() &&
device_features.sparseResidencyBuffer) {
if (dfn.vkCreateBuffer(device, &buffer_create_info, nullptr, &buffer_) ==
VK_SUCCESS) {
VkMemoryRequirements buffer_memory_requirements;
dfn.vkGetBufferMemoryRequirements(device, buffer_,
&buffer_memory_requirements);
if (xe::bit_scan_forward(buffer_memory_requirements.memoryTypeBits &
provider.memory_types_device_local(),
&buffer_memory_type_)) {
uint32_t allocation_size_log2;
xe::bit_scan_forward(
std::max(uint64_t(buffer_memory_requirements.alignment),
uint64_t(1)),
&allocation_size_log2);
if (allocation_size_log2 < kBufferSizeLog2) {
// Maximum of 1024 allocations in the worst case for all of the
// buffer because of the overall 4096 allocation count limit on
// Windows drivers.
InitializeSparseHostGpuMemory(
std::max(allocation_size_log2,
std::max(kHostGpuMemoryOptimalSparseAllocationLog2,
kBufferSizeLog2 - uint32_t(10))));
} else {
// Shouldn't happen on any real platform, but no point allocating the
// buffer sparsely.
dfn.vkDestroyBuffer(device, buffer_, nullptr);
buffer_ = VK_NULL_HANDLE;
}
} else {
XELOGE(
"Shared memory: Failed to get a device-local Vulkan memory type "
"for the sparse buffer");
dfn.vkDestroyBuffer(device, buffer_, nullptr);
buffer_ = VK_NULL_HANDLE;
}
} else {
XELOGE("Shared memory: Failed to create the {} MB Vulkan sparse buffer",
kBufferSize >> 20);
}
}
// Create a non-sparse buffer if there were issues with the sparse buffer.
if (buffer_ == VK_NULL_HANDLE) {
XELOGGPU(
"Vulkan sparse binding is not used for shared memory emulation - video "
"memory usage may increase significantly because a full {} MB buffer "
"will be created",
kBufferSize >> 20);
buffer_create_info.flags &= ~sparse_flags;
if (dfn.vkCreateBuffer(device, &buffer_create_info, nullptr, &buffer_) !=
VK_SUCCESS) {
XELOGE("Shared memory: Failed to create the {} MB Vulkan buffer",
kBufferSize >> 20);
Shutdown();
return false;
}
VkMemoryRequirements buffer_memory_requirements;
dfn.vkGetBufferMemoryRequirements(device, buffer_,
&buffer_memory_requirements);
if (!xe::bit_scan_forward(buffer_memory_requirements.memoryTypeBits &
provider.memory_types_device_local(),
&buffer_memory_type_)) {
XELOGE(
"Shared memory: Failed to get a device-local Vulkan memory type for "
"the buffer");
Shutdown();
return false;
}
VkMemoryAllocateInfo buffer_memory_allocate_info;
VkMemoryAllocateInfo* buffer_memory_allocate_info_last =
&buffer_memory_allocate_info;
buffer_memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
buffer_memory_allocate_info.pNext = nullptr;
buffer_memory_allocate_info.allocationSize =
buffer_memory_requirements.size;
buffer_memory_allocate_info.memoryTypeIndex = buffer_memory_type_;
VkMemoryDedicatedAllocateInfoKHR buffer_memory_dedicated_allocate_info;
if (provider.device_extensions().khr_dedicated_allocation) {
buffer_memory_allocate_info_last->pNext =
&buffer_memory_dedicated_allocate_info;
buffer_memory_allocate_info_last =
reinterpret_cast<VkMemoryAllocateInfo*>(
&buffer_memory_dedicated_allocate_info);
buffer_memory_dedicated_allocate_info.sType =
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
buffer_memory_dedicated_allocate_info.pNext = nullptr;
buffer_memory_dedicated_allocate_info.image = VK_NULL_HANDLE;
buffer_memory_dedicated_allocate_info.buffer = buffer_;
}
VkDeviceMemory buffer_memory;
if (dfn.vkAllocateMemory(device, &buffer_memory_allocate_info, nullptr,
&buffer_memory) != VK_SUCCESS) {
XELOGE(
"Shared memory: Failed to allocate {} MB of memory for the Vulkan "
"buffer",
kBufferSize >> 20);
Shutdown();
return false;
}
buffer_memory_.push_back(buffer_memory);
if (dfn.vkBindBufferMemory(device, buffer_, buffer_memory, 0) !=
VK_SUCCESS) {
XELOGE("Shared memory: Failed to bind memory to the Vulkan buffer");
Shutdown();
return false;
}
}
// The first usage will likely be uploading.
last_usage_ = Usage::kTransferDestination;
last_written_range_ = std::make_pair<uint32_t, uint32_t>(0, 0);
upload_buffer_pool_ = std::make_unique<ui::vulkan::VulkanUploadBufferPool>(
provider, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
xe::align(ui::vulkan::VulkanUploadBufferPool::kDefaultPageSize,
size_t(1) << page_size_log2()));
return true;
}
void VulkanSharedMemory::Shutdown(bool from_destructor) {
ResetTraceDownload();
upload_buffer_pool_.reset();
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device, buffer_);
for (VkDeviceMemory memory : buffer_memory_) {
dfn.vkFreeMemory(device, memory, nullptr);
}
buffer_memory_.clear();
// If calling from the destructor, the SharedMemory destructor will call
// ShutdownCommon.
if (!from_destructor) {
ShutdownCommon();
}
}
void VulkanSharedMemory::CompletedSubmissionUpdated() {
upload_buffer_pool_->Reclaim(command_processor_.GetCompletedSubmission());
}
void VulkanSharedMemory::EndSubmission() { upload_buffer_pool_->FlushWrites(); }
void VulkanSharedMemory::Use(Usage usage,
std::pair<uint32_t, uint32_t> written_range) {
written_range.first = std::min(written_range.first, kBufferSize);
written_range.second =
std::min(written_range.second, kBufferSize - written_range.first);
assert_true(usage != Usage::kRead || !written_range.second);
if (last_usage_ != usage || last_written_range_.second) {
VkPipelineStageFlags src_stage_mask, dst_stage_mask;
VkAccessFlags src_access_mask, dst_access_mask;
GetUsageMasks(last_usage_, src_stage_mask, src_access_mask);
GetUsageMasks(usage, dst_stage_mask, dst_access_mask);
VkDeviceSize offset, size;
if (last_usage_ == usage) {
// Committing the previous write, while not changing the access mask
// (passing false as whether to skip the barrier if no masks are changed
// for this reason).
offset = VkDeviceSize(last_written_range_.first);
size = VkDeviceSize(last_written_range_.second);
} else {
// Changing the stage and access mask - all preceding writes must be
// available not only to the source stage, but to the destination as well.
offset = 0;
size = VK_WHOLE_SIZE;
last_usage_ = usage;
}
command_processor_.PushBufferMemoryBarrier(
buffer_, offset, size, src_stage_mask, dst_stage_mask, src_access_mask,
dst_access_mask, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED,
false);
}
last_written_range_ = written_range;
}
bool VulkanSharedMemory::InitializeTraceSubmitDownloads() {
ResetTraceDownload();
PrepareForTraceDownload();
uint32_t download_page_count = trace_download_page_count();
if (!download_page_count) {
return false;
}
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
if (!ui::vulkan::util::CreateDedicatedAllocationBuffer(
provider, download_page_count << page_size_log2(),
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
ui::vulkan::util::MemoryPurpose::kReadback, trace_download_buffer_,
trace_download_buffer_memory_)) {
XELOGE(
"Shared memory: Failed to create a {} KB GPU-written memory download "
"buffer for frame tracing",
download_page_count << page_size_log2() >> 10);
ResetTraceDownload();
return false;
}
Use(Usage::kRead);
command_processor_.SubmitBarriers(true);
DeferredCommandBuffer& command_buffer =
command_processor_.deferred_command_buffer();
size_t download_range_count = trace_download_ranges().size();
VkBufferCopy* download_regions = command_buffer.CmdCopyBufferEmplace(
buffer_, trace_download_buffer_, uint32_t(download_range_count));
VkDeviceSize download_buffer_offset = 0;
for (size_t i = 0; i < download_range_count; ++i) {
VkBufferCopy& download_region = download_regions[i];
const std::pair<uint32_t, uint32_t>& download_range =
trace_download_ranges()[i];
download_region.srcOffset = download_range.first;
download_region.dstOffset = download_buffer_offset;
download_region.size = download_range.second;
download_buffer_offset += download_range.second;
}
command_processor_.PushBufferMemoryBarrier(
trace_download_buffer_, 0, VK_WHOLE_SIZE, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_HOST_READ_BIT);
return true;
}
void VulkanSharedMemory::InitializeTraceCompleteDownloads() {
if (!trace_download_buffer_memory_) {
return;
}
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
void* download_mapping;
if (dfn.vkMapMemory(device, trace_download_buffer_memory_, 0, VK_WHOLE_SIZE,
0, &download_mapping) == VK_SUCCESS) {
uint32_t download_buffer_offset = 0;
for (const auto& download_range : trace_download_ranges()) {
trace_writer_.WriteMemoryRead(
download_range.first, download_range.second,
reinterpret_cast<const uint8_t*>(download_mapping) +
download_buffer_offset);
}
dfn.vkUnmapMemory(device, trace_download_buffer_memory_);
} else {
XELOGE(
"Shared memory: Failed to map the GPU-written memory download buffer "
"for frame tracing");
}
ResetTraceDownload();
}
bool VulkanSharedMemory::AllocateSparseHostGpuMemoryRange(
uint32_t offset_allocations, uint32_t length_allocations) {
if (!length_allocations) {
return true;
}
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
VkMemoryAllocateInfo memory_allocate_info;
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = nullptr;
memory_allocate_info.allocationSize =
length_allocations << host_gpu_memory_sparse_granularity_log2();
memory_allocate_info.memoryTypeIndex = buffer_memory_type_;
VkDeviceMemory memory;
if (dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) !=
VK_SUCCESS) {
XELOGE("Shared memory: Failed to allocate sparse buffer memory");
return false;
}
buffer_memory_.push_back(memory);
VkSparseMemoryBind bind;
bind.resourceOffset = offset_allocations
<< host_gpu_memory_sparse_granularity_log2();
bind.size = memory_allocate_info.allocationSize;
bind.memory = memory;
bind.memoryOffset = 0;
bind.flags = 0;
VkPipelineStageFlags bind_wait_stage_mask =
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
if (provider.device_features().tessellationShader) {
bind_wait_stage_mask |=
VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
}
command_processor_.SparseBindBuffer(buffer_, 1, &bind, bind_wait_stage_mask);
return true;
}
bool VulkanSharedMemory::UploadRanges(
const std::vector<std::pair<uint32_t, uint32_t>>& upload_page_ranges) {
if (upload_page_ranges.empty()) {
return true;
}
// upload_page_ranges are sorted, use them to determine the range for the
// ordering barrier.
Use(Usage::kTransferDestination,
std::make_pair(
upload_page_ranges.front().first << page_size_log2(),
(upload_page_ranges.back().first + upload_page_ranges.back().second -
upload_page_ranges.front().first)
<< page_size_log2()));
command_processor_.SubmitBarriers(true);
DeferredCommandBuffer& command_buffer =
command_processor_.deferred_command_buffer();
uint64_t submission_current = command_processor_.GetCurrentSubmission();
bool successful = true;
upload_regions_.clear();
VkBuffer upload_buffer_previous = VK_NULL_HANDLE;
for (auto upload_range : upload_page_ranges) {
uint32_t upload_range_start = upload_range.first;
uint32_t upload_range_length = upload_range.second;
trace_writer_.WriteMemoryRead(upload_range_start << page_size_log2(),
upload_range_length << page_size_log2());
while (upload_range_length) {
VkBuffer upload_buffer;
VkDeviceSize upload_buffer_offset, upload_buffer_size;
uint8_t* upload_buffer_mapping = upload_buffer_pool_->RequestPartial(
submission_current, upload_range_length << page_size_log2(),
size_t(1) << page_size_log2(), upload_buffer, upload_buffer_offset,
upload_buffer_size);
if (upload_buffer_mapping == nullptr) {
XELOGE("Shared memory: Failed to get a Vulkan upload buffer");
successful = false;
break;
}
MakeRangeValid(upload_range_start << page_size_log2(),
uint32_t(upload_buffer_size), false, false);
std::memcpy(
upload_buffer_mapping,
memory().TranslatePhysical(upload_range_start << page_size_log2()),
upload_buffer_size);
if (upload_buffer_previous != upload_buffer && !upload_regions_.empty()) {
assert_true(upload_buffer_previous != VK_NULL_HANDLE);
command_buffer.CmdVkCopyBuffer(upload_buffer_previous, buffer_,
uint32_t(upload_regions_.size()),
upload_regions_.data());
upload_regions_.clear();
}
upload_buffer_previous = upload_buffer;
VkBufferCopy& upload_region = upload_regions_.emplace_back();
upload_region.srcOffset = upload_buffer_offset;
upload_region.dstOffset =
VkDeviceSize(upload_range_start << page_size_log2());
upload_region.size = upload_buffer_size;
uint32_t upload_buffer_pages =
uint32_t(upload_buffer_size >> page_size_log2());
upload_range_start += upload_buffer_pages;
upload_range_length -= upload_buffer_pages;
}
if (!successful) {
break;
}
}
if (!upload_regions_.empty()) {
assert_true(upload_buffer_previous != VK_NULL_HANDLE);
command_buffer.CmdVkCopyBuffer(upload_buffer_previous, buffer_,
uint32_t(upload_regions_.size()),
upload_regions_.data());
upload_regions_.clear();
}
return successful;
}
void VulkanSharedMemory::GetUsageMasks(Usage usage,
VkPipelineStageFlags& stage_mask,
VkAccessFlags& access_mask) const {
switch (usage) {
case Usage::kComputeWrite:
stage_mask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
access_mask = VK_ACCESS_SHADER_READ_BIT;
return;
case Usage::kTransferDestination:
stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
return;
default:
break;
}
stage_mask =
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | guest_shader_pipeline_stages_;
access_mask = VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_SHADER_READ_BIT;
switch (usage) {
case Usage::kRead:
stage_mask |=
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
access_mask |= VK_ACCESS_TRANSFER_READ_BIT;
break;
case Usage::kGuestDrawReadWrite:
access_mask |= VK_ACCESS_SHADER_WRITE_BIT;
break;
default:
assert_unhandled_case(usage);
}
}
void VulkanSharedMemory::ResetTraceDownload() {
const ui::vulkan::VulkanProvider& provider =
command_processor_.GetVulkanProvider();
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
VkDevice device = provider.device();
ui::vulkan::util::DestroyAndNullHandle(dfn.vkDestroyBuffer, device,
trace_download_buffer_);
ui::vulkan::util::DestroyAndNullHandle(dfn.vkFreeMemory, device,
trace_download_buffer_memory_);
ReleaseTraceDownloadRanges();
}
} // namespace vulkan
} // namespace gpu
} // namespace xe

View File

@@ -0,0 +1,97 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_VULKAN_SHARED_MEMORY_H_
#define XENIA_GPU_VULKAN_VULKAN_SHARED_MEMORY_H_
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "xenia/gpu/shared_memory.h"
#include "xenia/gpu/trace_writer.h"
#include "xenia/memory.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
namespace xe {
namespace gpu {
namespace vulkan {
class VulkanCommandProcessor;
class VulkanSharedMemory : public SharedMemory {
public:
VulkanSharedMemory(VulkanCommandProcessor& command_processor, Memory& memory,
TraceWriter& trace_writer,
VkPipelineStageFlags guest_shader_pipeline_stages);
~VulkanSharedMemory() override;
bool Initialize();
void Shutdown(bool from_destructor = false);
void CompletedSubmissionUpdated();
void EndSubmission();
enum class Usage {
// Index buffer, vfetch, compute read, transfer source.
kRead,
// Index buffer, vfetch, memexport.
kGuestDrawReadWrite,
kComputeWrite,
kTransferDestination,
};
// Inserts a pipeline barrier for the target usage, also ensuring consecutive
// read-write accesses are ordered with each other.
void Use(Usage usage, std::pair<uint32_t, uint32_t> written_range = {});
VkBuffer buffer() const { return buffer_; }
// Returns true if any downloads were submitted to the command processor.
bool InitializeTraceSubmitDownloads();
void InitializeTraceCompleteDownloads();
protected:
bool AllocateSparseHostGpuMemoryRange(uint32_t offset_allocations,
uint32_t length_allocations) override;
bool UploadRanges(const std::vector<std::pair<uint32_t, uint32_t>>&
upload_page_ranges) override;
private:
void GetUsageMasks(Usage usage, VkPipelineStageFlags& stage_mask,
VkAccessFlags& access_mask) const;
VulkanCommandProcessor& command_processor_;
TraceWriter& trace_writer_;
VkPipelineStageFlags guest_shader_pipeline_stages_;
VkBuffer buffer_ = VK_NULL_HANDLE;
uint32_t buffer_memory_type_;
// Single for non-sparse, every allocation so far for sparse.
std::vector<VkDeviceMemory> buffer_memory_;
Usage last_usage_;
std::pair<uint32_t, uint32_t> last_written_range_;
std::unique_ptr<ui::vulkan::VulkanUploadBufferPool> upload_buffer_pool_;
std::vector<VkBufferCopy> upload_regions_;
// Created temporarily, only for downloading.
VkBuffer trace_download_buffer_ = VK_NULL_HANDLE;
VkDeviceMemory trace_download_buffer_memory_ = VK_NULL_HANDLE;
void ResetTraceDownload();
};
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_VULKAN_SHARED_MEMORY_H_

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -10,22 +10,15 @@
#ifndef XENIA_GPU_VULKAN_VULKAN_TEXTURE_CACHE_H_
#define XENIA_GPU_VULKAN_VULKAN_TEXTURE_CACHE_H_
#include <algorithm>
#include <list>
#include <array>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "xenia/base/mutex.h"
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/sampler_info.h"
#include "xenia/gpu/shader.h"
#include "xenia/gpu/texture_conversion.h"
#include "xenia/gpu/texture_info.h"
#include "xenia/gpu/trace_writer.h"
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
#include "xenia/gpu/xenos.h"
#include "xenia/ui/vulkan/circular_buffer.h"
#include "xenia/ui/vulkan/fenced_pools.h"
#include "xenia/base/hash.h"
#include "xenia/gpu/texture_cache.h"
#include "xenia/gpu/vulkan/vulkan_shader.h"
#include "xenia/gpu/vulkan/vulkan_shared_memory.h"
#include "xenia/ui/vulkan/vulkan_mem_alloc.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
@@ -33,205 +26,334 @@ namespace xe {
namespace gpu {
namespace vulkan {
//
class VulkanTextureCache {
class VulkanCommandProcessor;
class VulkanTextureCache final : public TextureCache {
public:
struct TextureView;
// This represents an uploaded Vulkan texture.
struct Texture {
TextureInfo texture_info;
std::vector<std::unique_ptr<TextureView>> views;
VkFormat format;
VkImage image;
VkImageLayout image_layout;
VmaAllocation alloc;
VmaAllocationInfo alloc_info;
VkFramebuffer framebuffer; // Blit target frame buffer.
VkImageUsageFlags usage_flags;
bool is_watched;
bool pending_invalidation;
// Pointer to the latest usage fence.
VkFence in_flight_fence;
};
struct TextureView {
Texture* texture;
VkImageView view;
union {
uint16_t swizzle;
struct {
// FIXME: This only applies on little-endian platforms!
uint16_t swiz_x : 3;
uint16_t swiz_y : 3;
uint16_t swiz_z : 3;
uint16_t swiz_w : 3;
uint16_t : 4;
};
// Sampler parameters that can be directly converted to a host sampler or used
// for checking whether samplers bindings are up to date.
union SamplerParameters {
uint32_t value;
struct {
xenos::ClampMode clamp_x : 3; // 3
xenos::ClampMode clamp_y : 3; // 6
xenos::ClampMode clamp_z : 3; // 9
xenos::BorderColor border_color : 2; // 11
uint32_t mag_linear : 1; // 12
uint32_t min_linear : 1; // 13
uint32_t mip_linear : 1; // 14
xenos::AnisoFilter aniso_filter : 3; // 17
uint32_t mip_min_level : 4; // 21
uint32_t mip_base_map : 1; // 22
// Maximum mip level is in the texture resource itself, but mip_base_map
// can be used to limit fetching to mip_min_level.
};
SamplerParameters() : value(0) { static_assert_size(*this, sizeof(value)); }
struct Hasher {
size_t operator()(const SamplerParameters& parameters) const {
return std::hash<uint32_t>{}(parameters.value);
}
};
bool operator==(const SamplerParameters& parameters) const {
return value == parameters.value;
}
bool operator!=(const SamplerParameters& parameters) const {
return value != parameters.value;
}
};
VulkanTextureCache(Memory* memory, RegisterFile* register_file,
TraceWriter* trace_writer,
ui::vulkan::VulkanProvider& provider);
~VulkanTextureCache();
VkResult Initialize();
void Shutdown();
// Descriptor set layout containing all possible texture bindings.
// The set contains one descriptor for each texture sampler [0-31].
VkDescriptorSetLayout texture_descriptor_set_layout() const {
return texture_descriptor_set_layout_;
// Transient descriptor set layouts must be initialized in the command
// processor.
static std::unique_ptr<VulkanTextureCache> Create(
const RegisterFile& register_file, VulkanSharedMemory& shared_memory,
uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y,
VulkanCommandProcessor& command_processor,
VkPipelineStageFlags guest_shader_pipeline_stages) {
std::unique_ptr<VulkanTextureCache> texture_cache(new VulkanTextureCache(
register_file, shared_memory, draw_resolution_scale_x,
draw_resolution_scale_y, command_processor,
guest_shader_pipeline_stages));
if (!texture_cache->Initialize()) {
return nullptr;
}
return std::move(texture_cache);
}
// Prepares a descriptor set containing the samplers and images for all
// bindings. The textures will be uploaded/converted/etc as needed.
// Requires a fence to be provided that will be signaled when finished
// using the returned descriptor set.
VkDescriptorSet PrepareTextureSet(
VkCommandBuffer setup_command_buffer, VkFence completion_fence,
const std::vector<Shader::TextureBinding>& vertex_bindings,
const std::vector<Shader::TextureBinding>& pixel_bindings);
~VulkanTextureCache();
// TODO(benvanik): ReadTexture.
void BeginSubmission(uint64_t new_submission_index) override;
Texture* Lookup(const TextureInfo& texture_info);
// Must be called within a frame - creates and untiles textures needed by
// shaders, and enqueues transitioning them into the sampled usage. This may
// bind compute pipelines (notifying the command processor about that), and
// also since it may insert deferred barriers, before flushing the barriers
// preceding host GPU work.
void RequestTextures(uint32_t used_texture_mask) override;
// Looks for a texture either containing or matching these parameters.
// Caller is responsible for checking if the texture returned is an exact
// match or just contains the texture given by the parameters.
// If offset_x and offset_y are not null, this may return a texture that
// contains this address at an offset.
Texture* LookupAddress(uint32_t guest_address, uint32_t width,
uint32_t height, xenos::TextureFormat format,
VkOffset2D* out_offset = nullptr);
VkImageView GetActiveBindingOrNullImageView(uint32_t fetch_constant_index,
xenos::FetchOpDimension dimension,
bool is_signed) const;
TextureView* DemandView(Texture* texture, uint16_t swizzle);
SamplerParameters GetSamplerParameters(
const VulkanShader::SamplerBinding& binding) const;
// Demands a texture for the purpose of resolving from EDRAM. This either
// creates a new texture or returns a previously created texture.
Texture* DemandResolveTexture(const TextureInfo& texture_info);
// Must be called for every used sampler at least once in a single submission,
// and a submission must be open for this to be callable.
// Returns:
// - The sampler, if obtained successfully - and increases its last usage
// submission index - and has_overflown_out = false.
// - VK_NULL_HANDLE and has_overflown_out = true if there's a total sampler
// count overflow in a submission that potentially hasn't completed yet.
// - VK_NULL_HANDLE and has_overflown_out = false in case of a general failure
// to create a sampler.
VkSampler UseSampler(SamplerParameters parameters, bool& has_overflown_out);
// Returns the submission index to await (may be the current submission in
// case of an overflow within a single submission - in this case, it must be
// ended, and a new one must be started) in case of sampler count overflow, so
// samplers may be freed, and UseSamplers may take their slots.
uint64_t GetSubmissionToAwaitOnSamplerOverflow(
uint32_t overflowed_sampler_count) const;
// Clears all cached content.
void ClearCache();
// Returns the 2D view of the front buffer texture (for fragment shader
// reading - the barrier will be pushed in the command processor if needed),
// or VK_NULL_HANDLE in case of failure. May call LoadTextureData.
VkImageView RequestSwapTexture(uint32_t& width_scaled_out,
uint32_t& height_scaled_out,
xenos::TextureFormat& format_out);
// Frees any unused resources
void Scavenge();
protected:
bool IsSignedVersionSeparateForFormat(TextureKey key) const override;
uint32_t GetHostFormatSwizzle(TextureKey key) const override;
uint32_t GetMaxHostTextureWidthHeight(
xenos::DataDimension dimension) const override;
uint32_t GetMaxHostTextureDepthOrArraySize(
xenos::DataDimension dimension) const override;
std::unique_ptr<Texture> CreateTexture(TextureKey key) override;
bool LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base,
bool load_mips) override;
void UpdateTextureBindingsImpl(uint32_t fetch_constant_mask) override;
private:
struct UpdateSetInfo;
enum LoadDescriptorSetIndex {
kLoadDescriptorSetIndexDestination,
kLoadDescriptorSetIndexSource,
kLoadDescriptorSetIndexConstants,
kLoadDescriptorSetCount,
};
struct HostFormat {
LoadShaderIndex load_shader;
// Do NOT add integer formats to this - they are not filterable, can only be
// read with ImageFetch, not ImageSample! If any game is seen using
// num_format 1 for fixed-point formats (for floating-point, it's normally
// set to 1 though), add a constant buffer containing multipliers for the
// textures and multiplication to the tfetch implementation.
VkFormat format;
// Whether the format is block-compressed on the host (the host block size
// matches the guest format block size in this case), and isn't decompressed
// on load.
bool block_compressed;
// Set up dynamically based on what's supported by the device.
bool linear_filterable;
};
struct HostFormatPair {
HostFormat format_unsigned;
HostFormat format_signed;
// Mapping of Xenos swizzle components to Vulkan format components.
uint32_t swizzle;
// Whether the unsigned and the signed formats are compatible for one image
// and the same image data (on a portability subset device, this should also
// take imageViewFormatReinterpretation into account).
bool unsigned_signed_compatible;
};
class VulkanTexture final : public Texture {
public:
enum class Usage {
kUndefined,
kTransferDestination,
kGuestShaderSampled,
kSwapSampled,
};
// Takes ownership of the image and its memory.
explicit VulkanTexture(VulkanTextureCache& texture_cache,
const TextureKey& key, VkImage image,
VmaAllocation allocation);
~VulkanTexture();
VkImage image() const { return image_; }
// Doesn't transition (the caller must insert the barrier).
Usage SetUsage(Usage new_usage) {
Usage old_usage = usage_;
usage_ = new_usage;
return old_usage;
}
VkImageView GetView(bool is_signed, uint32_t host_swizzle,
bool is_array = true);
private:
union ViewKey {
uint32_t key;
struct {
uint32_t is_signed_separate_view : 1;
uint32_t host_swizzle : 12;
uint32_t is_array : 1;
};
ViewKey() : key(0) { static_assert_size(*this, sizeof(key)); }
struct Hasher {
size_t operator()(const ViewKey& key) const {
return std::hash<decltype(key.key)>{}(key.key);
}
};
bool operator==(const ViewKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const ViewKey& other_key) const {
return !(*this == other_key);
}
};
static constexpr VkComponentSwizzle GetComponentSwizzle(
uint32_t texture_swizzle, uint32_t component_index) {
xenos::XE_GPU_TEXTURE_SWIZZLE texture_component_swizzle =
xenos::XE_GPU_TEXTURE_SWIZZLE(
(texture_swizzle >> (3 * component_index)) & 0b111);
if (texture_component_swizzle ==
xenos::XE_GPU_TEXTURE_SWIZZLE(component_index)) {
// The portability subset requires all swizzles to be IDENTITY, return
// IDENTITY specifically, not R, G, B, A.
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
switch (texture_component_swizzle) {
case xenos::XE_GPU_TEXTURE_SWIZZLE_R:
return VK_COMPONENT_SWIZZLE_R;
case xenos::XE_GPU_TEXTURE_SWIZZLE_G:
return VK_COMPONENT_SWIZZLE_G;
case xenos::XE_GPU_TEXTURE_SWIZZLE_B:
return VK_COMPONENT_SWIZZLE_B;
case xenos::XE_GPU_TEXTURE_SWIZZLE_A:
return VK_COMPONENT_SWIZZLE_A;
case xenos::XE_GPU_TEXTURE_SWIZZLE_0:
return VK_COMPONENT_SWIZZLE_ZERO;
case xenos::XE_GPU_TEXTURE_SWIZZLE_1:
return VK_COMPONENT_SWIZZLE_ONE;
default:
// An invalid value.
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
}
VkImage image_;
VmaAllocation allocation_;
Usage usage_ = Usage::kUndefined;
std::unordered_map<ViewKey, VkImageView, ViewKey::Hasher> views_;
};
struct VulkanTextureBinding {
VkImageView image_view_unsigned;
VkImageView image_view_signed;
VulkanTextureBinding() { Reset(); }
void Reset() {
image_view_unsigned = VK_NULL_HANDLE;
image_view_signed = VK_NULL_HANDLE;
}
};
// Cached Vulkan sampler.
struct Sampler {
SamplerInfo sampler_info;
VkSampler sampler;
uint64_t last_usage_submission;
std::pair<const SamplerParameters, Sampler>* used_previous;
std::pair<const SamplerParameters, Sampler>* used_next;
};
struct WatchedTexture {
Texture* texture;
bool is_mip;
};
static constexpr bool AreDimensionsCompatible(
xenos::FetchOpDimension binding_dimension,
xenos::DataDimension resource_dimension) {
switch (binding_dimension) {
case xenos::FetchOpDimension::k1D:
case xenos::FetchOpDimension::k2D:
return resource_dimension == xenos::DataDimension::k1D ||
resource_dimension == xenos::DataDimension::k2DOrStacked;
case xenos::FetchOpDimension::k3DOrStacked:
return resource_dimension == xenos::DataDimension::k3D;
case xenos::FetchOpDimension::kCube:
return resource_dimension == xenos::DataDimension::kCube;
default:
return false;
}
}
// Allocates a new texture and memory to back it on the GPU.
Texture* AllocateTexture(const TextureInfo& texture_info,
VkFormatFeatureFlags required_flags =
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
bool FreeTexture(Texture* texture);
explicit VulkanTextureCache(
const RegisterFile& register_file, VulkanSharedMemory& shared_memory,
uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y,
VulkanCommandProcessor& command_processor,
VkPipelineStageFlags guest_shader_pipeline_stages);
void WatchTexture(Texture* texture);
void TextureTouched(Texture* texture);
std::pair<uint32_t, uint32_t> MemoryInvalidationCallback(
uint32_t physical_address_start, uint32_t length, bool exact_range);
static std::pair<uint32_t, uint32_t> MemoryInvalidationCallbackThunk(
void* context_ptr, uint32_t physical_address_start, uint32_t length,
bool exact_range);
bool Initialize();
// Demands a texture. If command_buffer is null and the texture hasn't been
// uploaded to graphics memory already, we will return null and bail.
Texture* Demand(const TextureInfo& texture_info,
VkCommandBuffer command_buffer = nullptr,
VkFence completion_fence = nullptr);
Sampler* Demand(const SamplerInfo& sampler_info);
const HostFormatPair& GetHostFormatPair(TextureKey key) const;
void FlushPendingCommands(VkCommandBuffer command_buffer,
VkFence completion_fence);
void GetTextureUsageMasks(VulkanTexture::Usage usage,
VkPipelineStageFlags& stage_mask,
VkAccessFlags& access_mask, VkImageLayout& layout);
bool ConvertTexture(uint8_t* dest, VkBufferImageCopy* copy_region,
uint32_t mip, const TextureInfo& src);
xenos::ClampMode NormalizeClampMode(xenos::ClampMode clamp_mode) const;
static const FormatInfo* GetFormatInfo(xenos::TextureFormat format);
static texture_conversion::CopyBlockCallback GetFormatCopyBlock(
xenos::TextureFormat format);
static TextureExtent GetMipExtent(const TextureInfo& src, uint32_t mip);
static uint32_t ComputeMipStorage(const FormatInfo* format_info,
uint32_t width, uint32_t height,
uint32_t depth, uint32_t mip);
static uint32_t ComputeMipStorage(const TextureInfo& src, uint32_t mip);
static uint32_t ComputeTextureStorage(const TextureInfo& src);
VulkanCommandProcessor& command_processor_;
VkPipelineStageFlags guest_shader_pipeline_stages_;
// Writes a texture back into guest memory. This call is (mostly) asynchronous
// but the texture must not be flagged for destruction.
void WritebackTexture(Texture* texture);
// Using the Vulkan Memory Allocator because texture count in games is
// naturally pretty much unbounded, while Vulkan implementations, especially
// on Windows versions before 10, may have an allocation count limit as low as
// 4096.
VmaAllocator vma_allocator_ = VK_NULL_HANDLE;
// Queues commands to upload a texture from system memory, applying any
// conversions necessary. This may flush the command buffer to the GPU if we
// run out of staging memory.
bool UploadTexture(VkCommandBuffer command_buffer, VkFence completion_fence,
Texture* dest, const TextureInfo& src);
static const HostFormatPair kBestHostFormats[64];
static const HostFormatPair kHostFormatGBGRUnaligned;
static const HostFormatPair kHostFormatBGRGUnaligned;
HostFormatPair host_formats_[64];
void HashTextureBindings(XXH3_state_t* hash_state, uint32_t& fetch_mask,
const std::vector<Shader::TextureBinding>& bindings);
bool SetupTextureBindings(
VkCommandBuffer command_buffer, VkFence completion_fence,
UpdateSetInfo* update_set_info,
const std::vector<Shader::TextureBinding>& bindings);
bool SetupTextureBinding(VkCommandBuffer command_buffer,
VkFence completion_fence,
UpdateSetInfo* update_set_info,
const Shader::TextureBinding& binding);
VkPipelineLayout load_pipeline_layout_ = VK_NULL_HANDLE;
std::array<VkPipeline, kLoadShaderCount> load_pipelines_{};
std::array<VkPipeline, kLoadShaderCount> load_pipelines_scaled_{};
// Removes invalidated textures from the cache, queues them for delete.
void RemoveInvalidatedTextures();
// If both images can be placed in the same allocation, it's one allocation,
// otherwise it's two separate.
std::array<VkDeviceMemory, 2> null_images_memory_{};
VkImage null_image_2d_array_cube_ = VK_NULL_HANDLE;
VkImage null_image_3d_ = VK_NULL_HANDLE;
VkImageView null_image_view_2d_array_ = VK_NULL_HANDLE;
VkImageView null_image_view_cube_ = VK_NULL_HANDLE;
VkImageView null_image_view_3d_ = VK_NULL_HANDLE;
bool null_images_cleared_ = false;
Memory* memory_ = nullptr;
std::array<VulkanTextureBinding, xenos::kTextureFetchConstantCount>
vulkan_texture_bindings_;
RegisterFile* register_file_ = nullptr;
TraceWriter* trace_writer_ = nullptr;
ui::vulkan::VulkanProvider& provider_;
uint32_t sampler_max_count_;
std::unique_ptr<xe::ui::vulkan::CommandBufferPool> wb_command_pool_ = nullptr;
std::unique_ptr<xe::ui::vulkan::DescriptorPool> descriptor_pool_ = nullptr;
std::unordered_map<uint64_t, VkDescriptorSet> texture_sets_;
VkDescriptorSetLayout texture_descriptor_set_layout_ = nullptr;
xenos::AnisoFilter max_anisotropy_;
VmaAllocator mem_allocator_ = nullptr;
ui::vulkan::CircularBuffer staging_buffer_;
ui::vulkan::CircularBuffer wb_staging_buffer_;
std::unordered_map<uint64_t, Texture*> textures_;
std::unordered_map<uint64_t, Sampler*> samplers_;
std::list<Texture*> pending_delete_textures_;
void* memory_invalidation_callback_handle_ = nullptr;
xe::global_critical_region global_critical_region_;
std::list<WatchedTexture> watched_textures_;
std::unordered_set<Texture*>* invalidated_textures_;
std::unordered_set<Texture*> invalidated_textures_sets_[2];
struct UpdateSetInfo {
// Bitmap of all 32 fetch constants and whether they have been setup yet.
// This prevents duplication across the vertex and pixel shader.
uint32_t has_setup_fetch_mask;
uint32_t image_write_count = 0;
VkWriteDescriptorSet image_writes[32];
VkDescriptorImageInfo image_infos[32];
} update_set_info_;
std::unordered_map<SamplerParameters, Sampler, SamplerParameters::Hasher>
samplers_;
std::pair<const SamplerParameters, Sampler>* sampler_used_first_ = nullptr;
std::pair<const SamplerParameters, Sampler>* sampler_used_last_ = nullptr;
};
} // namespace vulkan

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -19,8 +19,6 @@ namespace xe {
namespace gpu {
namespace vulkan {
using namespace xe::gpu::xenos;
class VulkanTraceViewer final : public TraceViewer {
public:
static std::unique_ptr<WindowedApp> Create(
@@ -35,36 +33,21 @@ class VulkanTraceViewer final : public TraceViewer {
uintptr_t GetColorRenderTarget(
uint32_t pitch, xenos::MsaaSamples samples, uint32_t base,
xenos::ColorRenderTargetFormat format) override {
auto command_processor = static_cast<VulkanCommandProcessor*>(
graphics_system()->command_processor());
// return command_processor->GetColorRenderTarget(pitch, samples, base,
// format);
// TODO(Triang3l): EDRAM viewer.
return 0;
}
uintptr_t GetDepthRenderTarget(
uint32_t pitch, xenos::MsaaSamples samples, uint32_t base,
xenos::DepthRenderTargetFormat format) override {
auto command_processor = static_cast<VulkanCommandProcessor*>(
graphics_system()->command_processor());
// return command_processor->GetDepthRenderTarget(pitch, samples, base,
// format);
// TODO(Triang3l): EDRAM viewer.
return 0;
}
uintptr_t GetTextureEntry(const TextureInfo& texture_info,
const SamplerInfo& sampler_info) override {
auto command_processor = static_cast<VulkanCommandProcessor*>(
graphics_system()->command_processor());
// auto entry_view =
// command_processor->texture_cache()->Demand(texture_info,
// sampler_info);
// if (!entry_view) {
// return 0;
//}
// auto texture = entry_view->texture;
// return static_cast<uintptr_t>(texture->handle);
// TODO(Triang3l): Textures, but from a fetch constant rather than
// TextureInfo/SamplerInfo which are going away.
return 0;
}