commit
41a9004976
|
@ -78,7 +78,7 @@ std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() {
|
|||
std::unique_ptr<gpu::GraphicsSystem> best;
|
||||
|
||||
best = std::unique_ptr<gpu::GraphicsSystem>(
|
||||
new xe::gpu::gl4::GL4GraphicsSystem());
|
||||
new xe::gpu::vulkan::VulkanGraphicsSystem());
|
||||
if (best) {
|
||||
return best;
|
||||
}
|
||||
|
|
|
@ -87,13 +87,12 @@ bool MMIOHandler::CheckStore(uint32_t virtual_address, uint32_t value) {
|
|||
return false;
|
||||
}
|
||||
|
||||
uintptr_t MMIOHandler::AddPhysicalWriteWatch(uint32_t guest_address,
|
||||
size_t length,
|
||||
WriteWatchCallback callback,
|
||||
void* callback_context,
|
||||
void* callback_data) {
|
||||
uint32_t base_address = guest_address;
|
||||
assert_true(base_address < 0x1FFFFFFF);
|
||||
uintptr_t MMIOHandler::AddPhysicalAccessWatch(uint32_t guest_address,
|
||||
size_t length, WatchType type,
|
||||
AccessWatchCallback callback,
|
||||
void* callback_context,
|
||||
void* callback_data) {
|
||||
uint32_t base_address = guest_address & 0x1FFFFFFF;
|
||||
|
||||
// Can only protect sizes matching system page size.
|
||||
// This means we need to round up, which will cause spurious access
|
||||
|
@ -103,32 +102,45 @@ uintptr_t MMIOHandler::AddPhysicalWriteWatch(uint32_t guest_address,
|
|||
xe::memory::page_size());
|
||||
base_address = base_address - (base_address % xe::memory::page_size());
|
||||
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
// Add to table. The slot reservation may evict a previous watch, which
|
||||
// could include our target, so we do it first.
|
||||
auto entry = new WriteWatchEntry();
|
||||
auto entry = new AccessWatchEntry();
|
||||
entry->address = base_address;
|
||||
entry->length = uint32_t(length);
|
||||
entry->callback = callback;
|
||||
entry->callback_context = callback_context;
|
||||
entry->callback_data = callback_data;
|
||||
global_critical_region_.mutex().lock();
|
||||
write_watches_.push_back(entry);
|
||||
global_critical_region_.mutex().unlock();
|
||||
access_watches_.push_back(entry);
|
||||
|
||||
// Make the desired range read only under all address spaces.
|
||||
auto page_access = memory::PageAccess::kNoAccess;
|
||||
switch (type) {
|
||||
case kWatchWrite:
|
||||
page_access = memory::PageAccess::kReadOnly;
|
||||
break;
|
||||
case kWatchReadWrite:
|
||||
page_access = memory::PageAccess::kNoAccess;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(type);
|
||||
break;
|
||||
}
|
||||
|
||||
// Protect the range under all address spaces
|
||||
memory::Protect(physical_membase_ + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadOnly, nullptr);
|
||||
page_access, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xA0000000 + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadOnly, nullptr);
|
||||
page_access, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xC0000000 + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadOnly, nullptr);
|
||||
page_access, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xE0000000 + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadOnly, nullptr);
|
||||
page_access, nullptr);
|
||||
|
||||
return reinterpret_cast<uintptr_t>(entry);
|
||||
}
|
||||
|
||||
void MMIOHandler::ClearWriteWatch(WriteWatchEntry* entry) {
|
||||
void MMIOHandler::ClearAccessWatch(AccessWatchEntry* entry) {
|
||||
memory::Protect(physical_membase_ + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xA0000000 + entry->address, entry->length,
|
||||
|
@ -139,19 +151,20 @@ void MMIOHandler::ClearWriteWatch(WriteWatchEntry* entry) {
|
|||
xe::memory::PageAccess::kReadWrite, nullptr);
|
||||
}
|
||||
|
||||
void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
|
||||
auto entry = reinterpret_cast<WriteWatchEntry*>(watch_handle);
|
||||
void MMIOHandler::CancelAccessWatch(uintptr_t watch_handle) {
|
||||
auto entry = reinterpret_cast<AccessWatchEntry*>(watch_handle);
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
// Allow access to the range again.
|
||||
ClearWriteWatch(entry);
|
||||
ClearAccessWatch(entry);
|
||||
|
||||
// Remove from table.
|
||||
global_critical_region_.mutex().lock();
|
||||
auto it = std::find(write_watches_.begin(), write_watches_.end(), entry);
|
||||
if (it != write_watches_.end()) {
|
||||
write_watches_.erase(it);
|
||||
auto it = std::find(access_watches_.begin(), access_watches_.end(), entry);
|
||||
assert_false(it == access_watches_.end());
|
||||
|
||||
if (it != access_watches_.end()) {
|
||||
access_watches_.erase(it);
|
||||
}
|
||||
global_critical_region_.mutex().unlock();
|
||||
|
||||
delete entry;
|
||||
}
|
||||
|
@ -159,18 +172,19 @@ void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
|
|||
void MMIOHandler::InvalidateRange(uint32_t physical_address, size_t length) {
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
for (auto it = write_watches_.begin(); it != write_watches_.end();) {
|
||||
for (auto it = access_watches_.begin(); it != access_watches_.end();) {
|
||||
auto entry = *it;
|
||||
if ((entry->address <= physical_address &&
|
||||
entry->address + entry->length > physical_address) ||
|
||||
(entry->address >= physical_address &&
|
||||
entry->address < physical_address + length)) {
|
||||
// This watch lies within the range. End it.
|
||||
ClearWriteWatch(entry);
|
||||
ClearAccessWatch(entry);
|
||||
entry->callback(entry->callback_context, entry->callback_data,
|
||||
entry->address);
|
||||
|
||||
it = write_watches_.erase(it);
|
||||
it = access_watches_.erase(it);
|
||||
delete entry;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -178,50 +192,49 @@ void MMIOHandler::InvalidateRange(uint32_t physical_address, size_t length) {
|
|||
}
|
||||
}
|
||||
|
||||
bool MMIOHandler::CheckWriteWatch(uint64_t fault_address) {
|
||||
uint32_t physical_address = uint32_t(fault_address);
|
||||
if (physical_address > 0x1FFFFFFF) {
|
||||
physical_address &= 0x1FFFFFFF;
|
||||
}
|
||||
std::list<WriteWatchEntry*> pending_invalidates;
|
||||
global_critical_region_.mutex().lock();
|
||||
// Now that we hold the lock, recheck and see if the pages are still
|
||||
// protected.
|
||||
memory::PageAccess cur_access;
|
||||
size_t page_length = memory::page_size();
|
||||
memory::QueryProtect((void*)fault_address, page_length, cur_access);
|
||||
if (cur_access != memory::PageAccess::kReadOnly &&
|
||||
cur_access != memory::PageAccess::kNoAccess) {
|
||||
// Another thread has cleared this write watch. Abort.
|
||||
global_critical_region_.mutex().unlock();
|
||||
return true;
|
||||
bool MMIOHandler::IsRangeWatched(uint32_t physical_address, size_t length) {
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
for (auto it = access_watches_.begin(); it != access_watches_.end(); ++it) {
|
||||
auto entry = *it;
|
||||
if ((entry->address <= physical_address &&
|
||||
entry->address + entry->length > physical_address) ||
|
||||
(entry->address >= physical_address &&
|
||||
entry->address < physical_address + length)) {
|
||||
// This watch lies within the range.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = write_watches_.begin(); it != write_watches_.end();) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MMIOHandler::CheckAccessWatch(uint32_t physical_address) {
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
bool hit = false;
|
||||
for (auto it = access_watches_.begin(); it != access_watches_.end();) {
|
||||
auto entry = *it;
|
||||
if (entry->address <= physical_address &&
|
||||
entry->address + entry->length > physical_address) {
|
||||
// Hit! Remove the writewatch.
|
||||
pending_invalidates.push_back(entry);
|
||||
// Hit! Remove the watch.
|
||||
hit = true;
|
||||
ClearAccessWatch(entry);
|
||||
entry->callback(entry->callback_context, entry->callback_data,
|
||||
physical_address);
|
||||
|
||||
ClearWriteWatch(entry);
|
||||
it = write_watches_.erase(it);
|
||||
it = access_watches_.erase(it);
|
||||
delete entry;
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
global_critical_region_.mutex().unlock();
|
||||
if (pending_invalidates.empty()) {
|
||||
|
||||
if (!hit) {
|
||||
// Rethrow access violation - range was not being watched.
|
||||
return false;
|
||||
}
|
||||
while (!pending_invalidates.empty()) {
|
||||
auto entry = pending_invalidates.back();
|
||||
pending_invalidates.pop_back();
|
||||
entry->callback(entry->callback_context, entry->callback_data,
|
||||
physical_address);
|
||||
delete entry;
|
||||
}
|
||||
|
||||
// Range was watched, so lets eat this access violation.
|
||||
return true;
|
||||
}
|
||||
|
@ -414,9 +427,33 @@ bool MMIOHandler::ExceptionCallback(Exception* ex) {
|
|||
}
|
||||
}
|
||||
if (!range) {
|
||||
auto fault_address = reinterpret_cast<uint8_t*>(ex->fault_address());
|
||||
uint32_t guest_address = 0;
|
||||
if (fault_address >= virtual_membase_ &&
|
||||
fault_address < physical_membase_) {
|
||||
// Faulting on a virtual address.
|
||||
guest_address = static_cast<uint32_t>(ex->fault_address()) & 0x1FFFFFFF;
|
||||
} else {
|
||||
// Faulting on a physical address.
|
||||
guest_address = static_cast<uint32_t>(ex->fault_address());
|
||||
}
|
||||
|
||||
// HACK: Recheck if the pages are still protected (race condition - another
|
||||
// thread clears the writewatch we just hit)
|
||||
// Do this under the lock so we don't introduce another race condition.
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
memory::PageAccess cur_access;
|
||||
size_t page_length = memory::page_size();
|
||||
memory::QueryProtect((void*)fault_address, page_length, cur_access);
|
||||
if (cur_access != memory::PageAccess::kReadOnly &&
|
||||
cur_access != memory::PageAccess::kNoAccess) {
|
||||
// Another thread has cleared this write watch. Abort.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Access is not found within any range, so fail and let the caller handle
|
||||
// it (likely by aborting).
|
||||
return CheckWriteWatch(ex->fault_address());
|
||||
return CheckAccessWatch(guest_address);
|
||||
}
|
||||
|
||||
auto rip = ex->pc();
|
||||
|
|
|
@ -28,9 +28,8 @@ typedef uint32_t (*MMIOReadCallback)(void* ppc_context, void* callback_context,
|
|||
uint32_t addr);
|
||||
typedef void (*MMIOWriteCallback)(void* ppc_context, void* callback_context,
|
||||
uint32_t addr, uint32_t value);
|
||||
|
||||
typedef void (*WriteWatchCallback)(void* context_ptr, void* data_ptr,
|
||||
uint32_t address);
|
||||
typedef void (*AccessWatchCallback)(void* context_ptr, void* data_ptr,
|
||||
uint32_t address);
|
||||
|
||||
struct MMIORange {
|
||||
uint32_t address;
|
||||
|
@ -46,6 +45,12 @@ class MMIOHandler {
|
|||
public:
|
||||
virtual ~MMIOHandler();
|
||||
|
||||
enum WatchType {
|
||||
kWatchInvalid = 0,
|
||||
kWatchWrite = 1,
|
||||
kWatchReadWrite = 2,
|
||||
};
|
||||
|
||||
static std::unique_ptr<MMIOHandler> Install(uint8_t* virtual_membase,
|
||||
uint8_t* physical_membase,
|
||||
uint8_t* membase_end);
|
||||
|
@ -59,17 +64,24 @@ class MMIOHandler {
|
|||
bool CheckLoad(uint32_t virtual_address, uint32_t* out_value);
|
||||
bool CheckStore(uint32_t virtual_address, uint32_t value);
|
||||
|
||||
uintptr_t AddPhysicalWriteWatch(uint32_t guest_address, size_t length,
|
||||
WriteWatchCallback callback,
|
||||
void* callback_context, void* callback_data);
|
||||
void CancelWriteWatch(uintptr_t watch_handle);
|
||||
// Memory watches: These are one-shot alarms that fire a callback (in the
|
||||
// context of the thread that caused the callback) when a memory range is
|
||||
// either written to or read from, depending on the watch type. These fire as
|
||||
// soon as a read/write happens, and only fire once.
|
||||
// These watches may be spuriously fired if memory is accessed nearby.
|
||||
uintptr_t AddPhysicalAccessWatch(uint32_t guest_address, size_t length,
|
||||
WatchType type, AccessWatchCallback callback,
|
||||
void* callback_context, void* callback_data);
|
||||
void CancelAccessWatch(uintptr_t watch_handle);
|
||||
void InvalidateRange(uint32_t physical_address, size_t length);
|
||||
bool IsRangeWatched(uint32_t physical_address, size_t length);
|
||||
|
||||
protected:
|
||||
struct WriteWatchEntry {
|
||||
struct AccessWatchEntry {
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
WriteWatchCallback callback;
|
||||
WatchType type;
|
||||
AccessWatchCallback callback;
|
||||
void* callback_context;
|
||||
void* callback_data;
|
||||
};
|
||||
|
@ -83,8 +95,8 @@ class MMIOHandler {
|
|||
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
||||
bool ExceptionCallback(Exception* ex);
|
||||
|
||||
void ClearWriteWatch(WriteWatchEntry* entry);
|
||||
bool CheckWriteWatch(uint64_t fault_address);
|
||||
void ClearAccessWatch(AccessWatchEntry* entry);
|
||||
bool CheckAccessWatch(uint32_t guest_address);
|
||||
|
||||
uint8_t* virtual_membase_;
|
||||
uint8_t* physical_membase_;
|
||||
|
@ -94,7 +106,7 @@ class MMIOHandler {
|
|||
|
||||
xe::global_critical_region global_critical_region_;
|
||||
// TODO(benvanik): data structure magic.
|
||||
std::list<WriteWatchEntry*> write_watches_;
|
||||
std::list<AccessWatchEntry*> access_watches_;
|
||||
|
||||
static MMIOHandler* global_handler_;
|
||||
};
|
||||
|
|
|
@ -84,9 +84,9 @@ class CommandProcessor {
|
|||
swap_request_handler_ = fn;
|
||||
}
|
||||
|
||||
void RequestFrameTrace(const std::wstring& root_path);
|
||||
void BeginTracing(const std::wstring& root_path);
|
||||
void EndTracing();
|
||||
virtual void RequestFrameTrace(const std::wstring& root_path);
|
||||
virtual void BeginTracing(const std::wstring& root_path);
|
||||
virtual void EndTracing();
|
||||
|
||||
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count);
|
||||
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size);
|
||||
|
|
|
@ -427,7 +427,7 @@ TextureCache::TextureEntry* TextureCache::LookupOrInsertTexture(
|
|||
// Not found, create.
|
||||
auto entry = std::make_unique<TextureEntry>();
|
||||
entry->texture_info = texture_info;
|
||||
entry->write_watch_handle = 0;
|
||||
entry->access_watch_handle = 0;
|
||||
entry->pending_invalidation = false;
|
||||
entry->handle = 0;
|
||||
|
||||
|
@ -442,6 +442,7 @@ TextureCache::TextureEntry* TextureCache::LookupOrInsertTexture(
|
|||
// Found! Acquire the handle and remove the readbuffer entry.
|
||||
read_buffer_textures_.erase(it);
|
||||
entry->handle = read_buffer_entry->handle;
|
||||
entry->access_watch_handle = read_buffer_entry->access_watch_handle;
|
||||
delete read_buffer_entry;
|
||||
// TODO(benvanik): set more texture properties? swizzle/etc?
|
||||
auto entry_ptr = entry.get();
|
||||
|
@ -495,14 +496,15 @@ TextureCache::TextureEntry* TextureCache::LookupOrInsertTexture(
|
|||
// Add a write watch. If any data in the given range is touched we'll get a
|
||||
// callback and evict the texture. We could reuse the storage, though the
|
||||
// driver is likely in a better position to pool that kind of stuff.
|
||||
entry->write_watch_handle = memory_->AddPhysicalWriteWatch(
|
||||
entry->access_watch_handle = memory_->AddPhysicalAccessWatch(
|
||||
texture_info.guest_address, texture_info.input_length,
|
||||
cpu::MMIOHandler::kWatchWrite,
|
||||
[](void* context_ptr, void* data_ptr, uint32_t address) {
|
||||
auto self = reinterpret_cast<TextureCache*>(context_ptr);
|
||||
auto touched_entry = reinterpret_cast<TextureEntry*>(data_ptr);
|
||||
// Clear watch handle first so we don't redundantly
|
||||
// remove.
|
||||
touched_entry->write_watch_handle = 0;
|
||||
touched_entry->access_watch_handle = 0;
|
||||
touched_entry->pending_invalidation = true;
|
||||
// Add to pending list so Scavenge will clean it up.
|
||||
self->invalidated_textures_mutex_.lock();
|
||||
|
@ -574,14 +576,27 @@ GLuint TextureCache::ConvertTexture(Blitter* blitter, uint32_t guest_address,
|
|||
dest_rect, GL_LINEAR, swap_channels);
|
||||
}
|
||||
|
||||
// HACK: remove texture from write watch list so readback won't kill us.
|
||||
// Not needed now, as readback is disabled.
|
||||
/*
|
||||
if (texture_entry->write_watch_handle) {
|
||||
memory_->CancelWriteWatch(texture_entry->write_watch_handle);
|
||||
texture_entry->write_watch_handle = 0;
|
||||
// Setup a read/write access watch. If the game tries to touch the memory
|
||||
// we were supposed to populate with this texture, then we'll actually
|
||||
// populate it.
|
||||
if (texture_entry->access_watch_handle) {
|
||||
memory_->CancelAccessWatch(texture_entry->access_watch_handle);
|
||||
texture_entry->access_watch_handle = 0;
|
||||
}
|
||||
//*/
|
||||
|
||||
texture_entry->access_watch_handle = memory_->AddPhysicalAccessWatch(
|
||||
guest_address, texture_entry->texture_info.input_length,
|
||||
cpu::MMIOHandler::kWatchReadWrite,
|
||||
[](void* context, void* data, uint32_t address) {
|
||||
auto touched_entry = reinterpret_cast<TextureEntry*>(data);
|
||||
touched_entry->access_watch_handle = 0;
|
||||
|
||||
// This happens. RDR resolves to a texture then upsizes it, BF1943
|
||||
// writes to a resolved texture.
|
||||
// TODO (for Vulkan): Copy this texture back into system memory.
|
||||
// assert_always();
|
||||
},
|
||||
nullptr, texture_entry);
|
||||
|
||||
return texture_entry->handle;
|
||||
}
|
||||
|
@ -618,6 +633,20 @@ GLuint TextureCache::ConvertTexture(Blitter* blitter, uint32_t guest_address,
|
|||
entry->block_height = block_height;
|
||||
entry->format = format;
|
||||
|
||||
entry->access_watch_handle = memory_->AddPhysicalAccessWatch(
|
||||
guest_address, block_height * block_width * 4,
|
||||
cpu::MMIOHandler::kWatchReadWrite,
|
||||
[](void* context, void* data, uint32_t address) {
|
||||
auto entry = reinterpret_cast<ReadBufferTexture*>(data);
|
||||
entry->access_watch_handle = 0;
|
||||
|
||||
// This happens. RDR resolves to a texture then upsizes it, BF1943
|
||||
// writes to a resolved texture.
|
||||
// TODO (for Vulkan): Copy this texture back into system memory.
|
||||
// assert_always();
|
||||
},
|
||||
nullptr, entry.get());
|
||||
|
||||
glCreateTextures(GL_TEXTURE_2D, 1, &entry->handle);
|
||||
glTextureParameteri(entry->handle, GL_TEXTURE_BASE_LEVEL, 0);
|
||||
glTextureParameteri(entry->handle, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
|
@ -636,9 +665,9 @@ GLuint TextureCache::ConvertTexture(Blitter* blitter, uint32_t guest_address,
|
|||
}
|
||||
|
||||
void TextureCache::EvictTexture(TextureEntry* entry) {
|
||||
if (entry->write_watch_handle) {
|
||||
memory_->CancelWriteWatch(entry->write_watch_handle);
|
||||
entry->write_watch_handle = 0;
|
||||
if (entry->access_watch_handle) {
|
||||
memory_->CancelAccessWatch(entry->access_watch_handle);
|
||||
entry->access_watch_handle = 0;
|
||||
}
|
||||
|
||||
for (auto& view : entry->views) {
|
||||
|
|
|
@ -44,7 +44,7 @@ class TextureCache {
|
|||
};
|
||||
struct TextureEntry {
|
||||
TextureInfo texture_info;
|
||||
uintptr_t write_watch_handle;
|
||||
uintptr_t access_watch_handle;
|
||||
GLuint handle;
|
||||
bool pending_invalidation;
|
||||
std::vector<std::unique_ptr<TextureEntryView>> views;
|
||||
|
@ -74,8 +74,12 @@ class TextureCache {
|
|||
TextureFormat format, bool swap_channels,
|
||||
GLuint src_texture, Rect2D src_rect, Rect2D dest_rect);
|
||||
|
||||
TextureEntry* LookupAddress(uint32_t guest_address, uint32_t width,
|
||||
uint32_t height, TextureFormat format);
|
||||
|
||||
private:
|
||||
struct ReadBufferTexture {
|
||||
uintptr_t access_watch_handle;
|
||||
uint32_t guest_address;
|
||||
uint32_t logical_width;
|
||||
uint32_t logical_height;
|
||||
|
@ -90,8 +94,6 @@ class TextureCache {
|
|||
void EvictSampler(SamplerEntry* entry);
|
||||
TextureEntry* LookupOrInsertTexture(const TextureInfo& texture_info,
|
||||
uint64_t opt_hash = 0);
|
||||
TextureEntry* LookupAddress(uint32_t guest_address, uint32_t width,
|
||||
uint32_t height, TextureFormat format);
|
||||
void EvictTexture(TextureEntry* entry);
|
||||
|
||||
bool UploadTexture2D(GLuint texture, const TextureInfo& texture_info);
|
||||
|
|
|
@ -22,6 +22,8 @@ project("xenia-gpu")
|
|||
project_root.."/third_party/gflags/src",
|
||||
})
|
||||
local_platform_files()
|
||||
local_platform_files("spirv")
|
||||
local_platform_files("spirv/passes")
|
||||
|
||||
group("src")
|
||||
project("xenia-gpu-shader-compiler")
|
||||
|
|
|
@ -99,6 +99,17 @@ struct InstructionResult {
|
|||
bool has_all_writes() const {
|
||||
return write_mask[0] && write_mask[1] && write_mask[2] && write_mask[3];
|
||||
}
|
||||
// Returns number of components written
|
||||
uint32_t num_writes() const {
|
||||
uint32_t total = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (write_mask[i]) {
|
||||
total++;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
// Returns true if any non-constant components are written.
|
||||
bool stores_non_constants() const {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
|
@ -547,6 +558,9 @@ class Shader {
|
|||
// True if the shader was translated and prepared without error.
|
||||
bool is_valid() const { return is_valid_; }
|
||||
|
||||
// True if the shader has already been translated.
|
||||
bool is_translated() const { return is_translated_; }
|
||||
|
||||
// Errors that occurred during translation.
|
||||
const std::vector<Error>& errors() const { return errors_; }
|
||||
|
||||
|
@ -591,6 +605,7 @@ class Shader {
|
|||
bool writes_color_targets_[4] = {false, false, false, false};
|
||||
|
||||
bool is_valid_ = false;
|
||||
bool is_translated_ = false;
|
||||
std::vector<Error> errors_;
|
||||
|
||||
std::string ucode_disassembly_;
|
||||
|
|
|
@ -51,6 +51,7 @@ void ShaderTranslator::Reset() {
|
|||
ucode_disasm_buffer_.Reset();
|
||||
ucode_disasm_line_number_ = 0;
|
||||
previous_ucode_disasm_scan_offset_ = 0;
|
||||
register_count_ = 64;
|
||||
total_attrib_count_ = 0;
|
||||
vertex_bindings_.clear();
|
||||
texture_bindings_.clear();
|
||||
|
@ -95,9 +96,21 @@ bool ShaderTranslator::GatherAllBindingInformation(Shader* shader) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ShaderTranslator::Translate(Shader* shader,
|
||||
xenos::xe_gpu_program_cntl_t cntl) {
|
||||
Reset();
|
||||
register_count_ = shader->type() == ShaderType::kVertex ? cntl.vs_regs + 1
|
||||
: cntl.ps_regs + 1;
|
||||
|
||||
return TranslateInternal(shader);
|
||||
}
|
||||
|
||||
bool ShaderTranslator::Translate(Shader* shader) {
|
||||
Reset();
|
||||
return TranslateInternal(shader);
|
||||
}
|
||||
|
||||
bool ShaderTranslator::TranslateInternal(Shader* shader) {
|
||||
shader_type_ = shader->type();
|
||||
ucode_dwords_ = shader->ucode_dwords();
|
||||
ucode_dword_count_ = shader->ucode_dword_count();
|
||||
|
@ -155,6 +168,7 @@ bool ShaderTranslator::Translate(Shader* shader) {
|
|||
}
|
||||
|
||||
shader->is_valid_ = true;
|
||||
shader->is_translated_ = true;
|
||||
for (const auto& error : shader->errors_) {
|
||||
if (error.is_fatal) {
|
||||
shader->is_valid_ = false;
|
||||
|
@ -369,9 +383,9 @@ bool ShaderTranslator::TranslateBlocks() {
|
|||
AddControlFlowTargetLabel(cf_a, &label_addresses);
|
||||
AddControlFlowTargetLabel(cf_b, &label_addresses);
|
||||
|
||||
PreProcessControlFlowInstruction(cf_index);
|
||||
PreProcessControlFlowInstruction(cf_index, cf_a);
|
||||
++cf_index;
|
||||
PreProcessControlFlowInstruction(cf_index);
|
||||
PreProcessControlFlowInstruction(cf_index, cf_b);
|
||||
++cf_index;
|
||||
}
|
||||
|
||||
|
@ -672,11 +686,11 @@ void ShaderTranslator::TranslateExecInstructions(
|
|||
static_cast<FetchOpcode>(ucode_dwords_[instr_offset * 3] & 0x1F);
|
||||
if (fetch_opcode == FetchOpcode::kVertexFetch) {
|
||||
auto& op = *reinterpret_cast<const VertexFetchInstruction*>(
|
||||
ucode_dwords_ + instr_offset * 3);
|
||||
ucode_dwords_ + instr_offset * 3);
|
||||
TranslateVertexFetchInstruction(op);
|
||||
} else {
|
||||
auto& op = *reinterpret_cast<const TextureFetchInstruction*>(
|
||||
ucode_dwords_ + instr_offset * 3);
|
||||
ucode_dwords_ + instr_offset * 3);
|
||||
TranslateTextureFetchInstruction(op);
|
||||
}
|
||||
} else {
|
||||
|
@ -986,16 +1000,19 @@ void ShaderTranslator::TranslateAluInstruction(const AluInstruction& op) {
|
|||
return;
|
||||
}
|
||||
|
||||
ParsedAluInstruction instr;
|
||||
if (op.has_vector_op()) {
|
||||
const auto& opcode_info =
|
||||
alu_vector_opcode_infos_[static_cast<int>(op.vector_opcode())];
|
||||
ParseAluVectorInstruction(op, opcode_info);
|
||||
ParseAluVectorInstruction(op, opcode_info, instr);
|
||||
ProcessAluInstruction(instr);
|
||||
}
|
||||
|
||||
if (op.has_scalar_op()) {
|
||||
const auto& opcode_info =
|
||||
alu_scalar_opcode_infos_[static_cast<int>(op.scalar_opcode())];
|
||||
ParseAluScalarInstruction(op, opcode_info);
|
||||
ParseAluScalarInstruction(op, opcode_info, instr);
|
||||
ProcessAluInstruction(instr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1044,9 +1061,8 @@ void ParseAluInstructionOperand(const AluInstruction& op, int i,
|
|||
uint32_t a = swizzle & 0x3;
|
||||
out_op->components[0] = GetSwizzleFromComponentIndex(a);
|
||||
} else if (swizzle_component_count == 2) {
|
||||
swizzle >>= 4;
|
||||
uint32_t a = ((swizzle >> 2) + 3) & 0x3;
|
||||
uint32_t b = (swizzle + 2) & 0x3;
|
||||
uint32_t a = ((swizzle >> 6) + 3) & 0x3;
|
||||
uint32_t b = ((swizzle >> 0) + 0) & 0x3;
|
||||
out_op->components[0] = GetSwizzleFromComponentIndex(a);
|
||||
out_op->components[1] = GetSwizzleFromComponentIndex(b);
|
||||
} else {
|
||||
|
@ -1088,8 +1104,8 @@ void ParseAluInstructionOperandSpecial(const AluInstruction& op,
|
|||
}
|
||||
|
||||
void ShaderTranslator::ParseAluVectorInstruction(
|
||||
const AluInstruction& op, const AluOpcodeInfo& opcode_info) {
|
||||
ParsedAluInstruction i;
|
||||
const AluInstruction& op, const AluOpcodeInfo& opcode_info,
|
||||
ParsedAluInstruction& i) {
|
||||
i.dword_index = 0;
|
||||
i.type = ParsedAluInstruction::Type::kVector;
|
||||
i.vector_opcode = op.vector_opcode();
|
||||
|
@ -1126,6 +1142,10 @@ void ShaderTranslator::ParseAluVectorInstruction(
|
|||
} else {
|
||||
// Unimplemented.
|
||||
// assert_always();
|
||||
XELOGE(
|
||||
"ShaderTranslator::ParseAluVectorInstruction: Unsupported write "
|
||||
"to export %d",
|
||||
dest_num);
|
||||
i.result.storage_target = InstructionStorageTarget::kNone;
|
||||
i.result.storage_index = 0;
|
||||
}
|
||||
|
@ -1203,13 +1223,11 @@ void ShaderTranslator::ParseAluVectorInstruction(
|
|||
}
|
||||
|
||||
i.Disassemble(&ucode_disasm_buffer_);
|
||||
|
||||
ProcessAluInstruction(i);
|
||||
}
|
||||
|
||||
void ShaderTranslator::ParseAluScalarInstruction(
|
||||
const AluInstruction& op, const AluOpcodeInfo& opcode_info) {
|
||||
ParsedAluInstruction i;
|
||||
const AluInstruction& op, const AluOpcodeInfo& opcode_info,
|
||||
ParsedAluInstruction& i) {
|
||||
i.dword_index = 0;
|
||||
i.type = ParsedAluInstruction::Type::kScalar;
|
||||
i.scalar_opcode = op.scalar_opcode();
|
||||
|
@ -1319,8 +1337,6 @@ void ShaderTranslator::ParseAluScalarInstruction(
|
|||
}
|
||||
|
||||
i.Disassemble(&ucode_disasm_buffer_);
|
||||
|
||||
ProcessAluInstruction(i);
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
|
|
|
@ -30,6 +30,7 @@ class ShaderTranslator {
|
|||
// DEPRECATED(benvanik): remove this when shader cache is removed.
|
||||
bool GatherAllBindingInformation(Shader* shader);
|
||||
|
||||
bool Translate(Shader* shader, xenos::xe_gpu_program_cntl_t cntl);
|
||||
bool Translate(Shader* shader);
|
||||
|
||||
protected:
|
||||
|
@ -38,6 +39,8 @@ class ShaderTranslator {
|
|||
// Resets translator state before beginning translation.
|
||||
virtual void Reset();
|
||||
|
||||
// Register count.
|
||||
uint32_t register_count() const { return register_count_; }
|
||||
// True if the current shader is a vertex shader.
|
||||
bool is_vertex_shader() const { return shader_type_ == ShaderType::kVertex; }
|
||||
// True if the current shader is a pixel shader.
|
||||
|
@ -79,7 +82,8 @@ class ShaderTranslator {
|
|||
}
|
||||
|
||||
// Pre-process a control-flow instruction before anything else.
|
||||
virtual void PreProcessControlFlowInstruction(uint32_t cf_index) {}
|
||||
virtual void PreProcessControlFlowInstruction(
|
||||
uint32_t cf_index, const ucode::ControlFlowInstruction& instr) {}
|
||||
|
||||
// Handles translation for control flow label addresses.
|
||||
// This is triggered once for each label required (due to control flow
|
||||
|
@ -131,6 +135,8 @@ class ShaderTranslator {
|
|||
int src_swizzle_component_count;
|
||||
};
|
||||
|
||||
bool TranslateInternal(Shader* shader);
|
||||
|
||||
void MarkUcodeInstruction(uint32_t dword_offset);
|
||||
void AppendUcodeDisasm(char c);
|
||||
void AppendUcodeDisasm(const char* value);
|
||||
|
@ -173,14 +179,18 @@ class ShaderTranslator {
|
|||
|
||||
void TranslateAluInstruction(const ucode::AluInstruction& op);
|
||||
void ParseAluVectorInstruction(const ucode::AluInstruction& op,
|
||||
const AluOpcodeInfo& opcode_info);
|
||||
const AluOpcodeInfo& opcode_info,
|
||||
ParsedAluInstruction& instr);
|
||||
void ParseAluScalarInstruction(const ucode::AluInstruction& op,
|
||||
const AluOpcodeInfo& opcode_info);
|
||||
const AluOpcodeInfo& opcode_info,
|
||||
ParsedAluInstruction& instr);
|
||||
|
||||
// Input shader metadata and microcode.
|
||||
ShaderType shader_type_;
|
||||
const uint32_t* ucode_dwords_;
|
||||
size_t ucode_dword_count_;
|
||||
xenos::xe_gpu_program_cntl_t program_cntl_;
|
||||
uint32_t register_count_;
|
||||
|
||||
// Accumulated translation errors.
|
||||
std::vector<Shader::Error> errors_;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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/spirv/compiler.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace spirv {
|
||||
|
||||
Compiler::Compiler() {}
|
||||
|
||||
void Compiler::AddPass(std::unique_ptr<CompilerPass> pass) {
|
||||
compiler_passes_.push_back(std::move(pass));
|
||||
}
|
||||
|
||||
bool Compiler::Compile(spv::Module* module) {
|
||||
for (auto& pass : compiler_passes_) {
|
||||
if (!pass->Run(module)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Compiler::Reset() { compiler_passes_.clear(); }
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_SPIRV_COMPILER_H_
|
||||
#define XENIA_GPU_SPIRV_COMPILER_H_
|
||||
|
||||
#include "xenia/base/arena.h"
|
||||
#include "xenia/gpu/spirv/compiler_pass.h"
|
||||
|
||||
#include "third_party/glslang-spirv/SpvBuilder.h"
|
||||
#include "third_party/spirv/GLSL.std.450.hpp11"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace spirv {
|
||||
|
||||
// SPIR-V Compiler. Designed to optimize SPIR-V code before feeding it into the
|
||||
// drivers.
|
||||
class Compiler {
|
||||
public:
|
||||
Compiler();
|
||||
|
||||
void AddPass(std::unique_ptr<CompilerPass> pass);
|
||||
void Reset();
|
||||
bool Compile(spv::Module* module);
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<CompilerPass>> compiler_passes_;
|
||||
};
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_SPIRV_COMPILER_H_
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_SPIRV_COMPILER_PASS_H_
|
||||
#define XENIA_GPU_SPIRV_COMPILER_PASS_H_
|
||||
|
||||
#include "xenia/base/arena.h"
|
||||
|
||||
#include "third_party/glslang-spirv/SpvBuilder.h"
|
||||
#include "third_party/spirv/GLSL.std.450.hpp11"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace spirv {
|
||||
|
||||
class CompilerPass {
|
||||
public:
|
||||
CompilerPass() = default;
|
||||
virtual ~CompilerPass() {}
|
||||
|
||||
virtual bool Run(spv::Module* module) = 0;
|
||||
|
||||
private:
|
||||
xe::Arena ir_arena_;
|
||||
};
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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/spirv/passes/control_flow_analysis_pass.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace spirv {
|
||||
|
||||
ControlFlowAnalysisPass::ControlFlowAnalysisPass() {}
|
||||
|
||||
bool ControlFlowAnalysisPass::Run(spv::Module* module) {
|
||||
for (auto function : module->getFunctions()) {
|
||||
// For each OpBranchConditional, see if we can find a point where control
|
||||
// flow converges and then append an OpSelectionMerge.
|
||||
// Potential problems: while loops constructed from branch instructions
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_SPIRV_PASSES_CONTROL_FLOW_ANALYSIS_PASS_H_
|
||||
#define XENIA_GPU_SPIRV_PASSES_CONTROL_FLOW_ANALYSIS_PASS_H_
|
||||
|
||||
#include "xenia/gpu/spirv/compiler_pass.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace spirv {
|
||||
|
||||
// Control-flow analysis pass. Runs through control-flow and adds merge opcodes
|
||||
// where necessary.
|
||||
class ControlFlowAnalysisPass : public CompilerPass {
|
||||
public:
|
||||
ControlFlowAnalysisPass();
|
||||
|
||||
bool Run(spv::Module* module) override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_SPIRV_PASSES_CONTROL_FLOW_ANALYSIS_PASS_H_
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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/spirv/passes/control_flow_simplification_pass.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace spirv {
|
||||
|
||||
ControlFlowSimplificationPass::ControlFlowSimplificationPass() {}
|
||||
|
||||
bool ControlFlowSimplificationPass::Run(spv::Module* module) {
|
||||
for (auto function : module->getFunctions()) {
|
||||
// Walk through the blocks in the function and merge any blocks which are
|
||||
// unconditionally dominated.
|
||||
for (auto it = function->getBlocks().end() - 1;
|
||||
it != function->getBlocks().begin() - 1;) {
|
||||
auto block = *it;
|
||||
if (!block->isUnreachable() && block->getPredecessors().size() == 1) {
|
||||
auto prev_block = block->getPredecessors()[0];
|
||||
auto last_instr =
|
||||
prev_block->getInstruction(prev_block->getInstructionCount() - 1);
|
||||
if (last_instr->getOpCode() == spv::Op::OpBranch) {
|
||||
if (prev_block->getSuccessors().size() == 1 &&
|
||||
prev_block->getSuccessors()[0] == block) {
|
||||
// We're dominated by this block. Merge into it.
|
||||
prev_block->merge(block);
|
||||
block->setUnreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
--it;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_SPIRV_PASSES_CONTROL_FLOW_SIMPLIFICATION_PASS_H_
|
||||
#define XENIA_GPU_SPIRV_PASSES_CONTROL_FLOW_SIMPLIFICATION_PASS_H_
|
||||
|
||||
#include "xenia/gpu/spirv/compiler_pass.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace spirv {
|
||||
|
||||
// Control-flow simplification pass. Combines adjacent blocks and marks
|
||||
// any unreachable blocks.
|
||||
class ControlFlowSimplificationPass : public CompilerPass {
|
||||
public:
|
||||
ControlFlowSimplificationPass();
|
||||
|
||||
bool Run(spv::Module* module) override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_GPU_SPIRV_PASSES_CONTROL_FLOW_SIMPLIFICATION_PASS_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2016 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -17,7 +17,9 @@
|
|||
#include "third_party/glslang-spirv/SpvBuilder.h"
|
||||
#include "third_party/spirv/GLSL.std.450.hpp11"
|
||||
#include "xenia/gpu/shader_translator.h"
|
||||
#include "xenia/gpu/spirv/compiler.h"
|
||||
#include "xenia/ui/spirv/spirv_disassembler.h"
|
||||
#include "xenia/ui/spirv/spirv_validator.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
@ -54,7 +56,8 @@ class SpirvShaderTranslator : public ShaderTranslator {
|
|||
std::vector<uint8_t> CompleteTranslation() override;
|
||||
void PostTranslation(Shader* shader) override;
|
||||
|
||||
void PreProcessControlFlowInstruction(uint32_t cf_index) override;
|
||||
void PreProcessControlFlowInstruction(
|
||||
uint32_t cf_index, const ucode::ControlFlowInstruction& instr) override;
|
||||
void ProcessLabel(uint32_t cf_index) override;
|
||||
void ProcessControlFlowInstructionBegin(uint32_t cf_index) override;
|
||||
void ProcessControlFlowInstructionEnd(uint32_t cf_index) override;
|
||||
|
@ -91,10 +94,16 @@ class SpirvShaderTranslator : public ShaderTranslator {
|
|||
// Stores a value based on the specified result information.
|
||||
// The value will be transformed into the appropriate form for the result and
|
||||
// the proper components will be selected.
|
||||
void StoreToResult(spv::Id source_value_id, const InstructionResult& result,
|
||||
spv::Id predicate_cond = 0);
|
||||
void StoreToResult(spv::Id source_value_id, const InstructionResult& result);
|
||||
|
||||
xe::ui::spirv::SpirvDisassembler disassembler_;
|
||||
xe::ui::spirv::SpirvValidator validator_;
|
||||
xe::gpu::spirv::Compiler compiler_;
|
||||
|
||||
// True if there's an open predicated block
|
||||
bool open_predicated_block_ = false;
|
||||
bool predicated_block_cond_ = false;
|
||||
spv::Block* predicated_block_end_ = nullptr;
|
||||
|
||||
// TODO(benvanik): replace with something better, make reusable, etc.
|
||||
std::unique_ptr<spv::Builder> builder_;
|
||||
|
@ -104,11 +113,10 @@ class SpirvShaderTranslator : public ShaderTranslator {
|
|||
spv::Function* translated_main_ = 0;
|
||||
|
||||
// Types.
|
||||
spv::Id float_type_ = 0, bool_type_ = 0, int_type_ = 0;
|
||||
spv::Id float_type_ = 0, bool_type_ = 0, int_type_ = 0, uint_type_ = 0;
|
||||
spv::Id vec2_float_type_ = 0, vec3_float_type_ = 0, vec4_float_type_ = 0;
|
||||
spv::Id vec4_uint_type_ = 0;
|
||||
spv::Id vec4_bool_type_ = 0;
|
||||
spv::Id sampled_image_type_ = 0;
|
||||
|
||||
// Constants.
|
||||
spv::Id vec4_float_zero_ = 0, vec4_float_one_ = 0;
|
||||
|
@ -121,13 +129,19 @@ class SpirvShaderTranslator : public ShaderTranslator {
|
|||
spv::Id pos_ = 0;
|
||||
spv::Id push_consts_ = 0;
|
||||
spv::Id interpolators_ = 0;
|
||||
spv::Id frag_outputs_ = 0;
|
||||
spv::Id vertex_id_ = 0;
|
||||
spv::Id frag_outputs_ = 0, frag_depth_ = 0;
|
||||
spv::Id samplers_ = 0;
|
||||
spv::Id img_[4] = {0}; // Images {1D, 2D, 3D, Cube}
|
||||
spv::Id tex_[4] = {0}; // Images {1D, 2D, 3D, Cube}
|
||||
|
||||
// Map of {binding -> {offset -> spv input}}
|
||||
std::map<uint32_t, std::map<uint32_t, spv::Id>> vertex_binding_map_;
|
||||
std::map<uint32_t, spv::Block*> cf_blocks_;
|
||||
|
||||
struct CFBlock {
|
||||
spv::Block* block = nullptr;
|
||||
bool prev_dominates = true;
|
||||
};
|
||||
std::map<uint32_t, CFBlock> cf_blocks_;
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
|
|
|
@ -88,6 +88,66 @@ enum class TextureFormat : uint32_t {
|
|||
kUnknown = 0xFFFFFFFFu,
|
||||
};
|
||||
|
||||
inline size_t GetTexelSize(TextureFormat format) {
|
||||
switch (format) {
|
||||
case TextureFormat::k_1_5_5_5:
|
||||
return 2;
|
||||
break;
|
||||
case TextureFormat::k_2_10_10_10:
|
||||
return 4;
|
||||
break;
|
||||
case TextureFormat::k_4_4_4_4:
|
||||
return 2;
|
||||
break;
|
||||
case TextureFormat::k_5_6_5:
|
||||
return 2;
|
||||
break;
|
||||
case TextureFormat::k_8:
|
||||
return 1;
|
||||
break;
|
||||
case TextureFormat::k_8_8:
|
||||
return 2;
|
||||
break;
|
||||
case TextureFormat::k_8_8_8_8:
|
||||
return 4;
|
||||
break;
|
||||
case TextureFormat::k_16:
|
||||
return 4;
|
||||
break;
|
||||
case TextureFormat::k_16_FLOAT:
|
||||
return 4;
|
||||
break;
|
||||
case TextureFormat::k_16_16:
|
||||
return 4;
|
||||
break;
|
||||
case TextureFormat::k_16_16_FLOAT:
|
||||
return 4;
|
||||
break;
|
||||
case TextureFormat::k_16_16_16_16:
|
||||
return 8;
|
||||
break;
|
||||
case TextureFormat::k_16_16_16_16_FLOAT:
|
||||
return 8;
|
||||
break;
|
||||
case TextureFormat::k_32_FLOAT:
|
||||
return 4;
|
||||
break;
|
||||
case TextureFormat::k_32_32_FLOAT:
|
||||
return 8;
|
||||
break;
|
||||
case TextureFormat::k_32_32_32_32_FLOAT:
|
||||
return 16;
|
||||
break;
|
||||
case TextureFormat::k_10_11_11:
|
||||
case TextureFormat::k_11_11_10:
|
||||
return 4;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(format);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline TextureFormat ColorFormatToTextureFormat(ColorFormat color_format) {
|
||||
return static_cast<TextureFormat>(color_format);
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ void TracePlayer::SeekFrame(int target_frame) {
|
|||
|
||||
assert_true(frame->start_ptr <= frame->end_ptr);
|
||||
PlayTrace(frame->start_ptr, frame->end_ptr - frame->start_ptr,
|
||||
TracePlaybackMode::kBreakOnSwap);
|
||||
TracePlaybackMode::kBreakOnSwap, false);
|
||||
}
|
||||
|
||||
void TracePlayer::SeekCommand(int target_command) {
|
||||
|
@ -71,11 +71,11 @@ void TracePlayer::SeekCommand(int target_command) {
|
|||
const auto& previous_command = frame->commands[previous_command_index];
|
||||
PlayTrace(previous_command.end_ptr,
|
||||
command.end_ptr - previous_command.end_ptr,
|
||||
TracePlaybackMode::kBreakOnSwap);
|
||||
TracePlaybackMode::kBreakOnSwap, false);
|
||||
} else {
|
||||
// Full playback from frame start.
|
||||
PlayTrace(frame->start_ptr, command.end_ptr - frame->start_ptr,
|
||||
TracePlaybackMode::kBreakOnSwap);
|
||||
TracePlaybackMode::kBreakOnSwap, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,19 +84,25 @@ void TracePlayer::WaitOnPlayback() {
|
|||
}
|
||||
|
||||
void TracePlayer::PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode) {
|
||||
graphics_system_->command_processor()->CallInThread(
|
||||
[this, trace_data, trace_size, playback_mode]() {
|
||||
PlayTraceOnThread(trace_data, trace_size, playback_mode);
|
||||
});
|
||||
TracePlaybackMode playback_mode,
|
||||
bool clear_caches) {
|
||||
playing_trace_ = true;
|
||||
graphics_system_->command_processor()->CallInThread([=]() {
|
||||
PlayTraceOnThread(trace_data, trace_size, playback_mode, clear_caches);
|
||||
});
|
||||
}
|
||||
|
||||
void TracePlayer::PlayTraceOnThread(const uint8_t* trace_data,
|
||||
size_t trace_size,
|
||||
TracePlaybackMode playback_mode) {
|
||||
TracePlaybackMode playback_mode,
|
||||
bool clear_caches) {
|
||||
auto memory = graphics_system_->memory();
|
||||
auto command_processor = graphics_system_->command_processor();
|
||||
|
||||
if (clear_caches) {
|
||||
command_processor->ClearCaches();
|
||||
}
|
||||
|
||||
command_processor->set_swap_mode(SwapMode::kIgnored);
|
||||
playback_percent_ = 0;
|
||||
auto trace_end = trace_data + trace_size;
|
||||
|
|
|
@ -50,9 +50,9 @@ class TracePlayer : public TraceReader {
|
|||
|
||||
private:
|
||||
void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode);
|
||||
TracePlaybackMode playback_mode, bool clear_caches);
|
||||
void PlayTraceOnThread(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode);
|
||||
TracePlaybackMode playback_mode, bool clear_caches);
|
||||
|
||||
xe::ui::Loop* loop_;
|
||||
GraphicsSystem* graphics_system_;
|
||||
|
|
|
@ -75,6 +75,10 @@ void TraceReader::ParseTrace() {
|
|||
const uint8_t* packet_start_ptr = nullptr;
|
||||
const uint8_t* last_ptr = trace_ptr;
|
||||
bool pending_break = false;
|
||||
auto current_command_buffer = new CommandBuffer();
|
||||
current_frame.command_tree =
|
||||
std::unique_ptr<CommandBuffer>(current_command_buffer);
|
||||
|
||||
while (trace_ptr < trace_data_ + trace_size_) {
|
||||
++current_frame.command_count;
|
||||
auto type = static_cast<TraceCommandType>(xe::load<uint32_t>(trace_ptr));
|
||||
|
@ -94,11 +98,29 @@ void TraceReader::ParseTrace() {
|
|||
auto cmd =
|
||||
reinterpret_cast<const IndirectBufferStartCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
|
||||
// Traverse down a level.
|
||||
auto sub_command_buffer = new CommandBuffer();
|
||||
sub_command_buffer->parent = current_command_buffer;
|
||||
current_command_buffer->commands.push_back(
|
||||
CommandBuffer::Command(sub_command_buffer));
|
||||
current_command_buffer = sub_command_buffer;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kIndirectBufferEnd: {
|
||||
auto cmd = reinterpret_cast<const IndirectBufferEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
|
||||
// IB packet is wrapped in a kPacketStart/kPacketEnd. Skip the end.
|
||||
auto end_cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
|
||||
assert_true(end_cmd->type == TraceCommandType::kPacketEnd);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
|
||||
// Go back up a level. If parent is null, this frame started in an
|
||||
// indirect buffer.
|
||||
if (current_command_buffer->parent) {
|
||||
current_command_buffer = current_command_buffer->parent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPacketStart: {
|
||||
|
@ -125,6 +147,8 @@ void TraceReader::ParseTrace() {
|
|||
command.end_ptr = trace_ptr;
|
||||
current_frame.commands.push_back(std::move(command));
|
||||
last_ptr = trace_ptr;
|
||||
current_command_buffer->commands.push_back(CommandBuffer::Command(
|
||||
uint32_t(current_frame.commands.size() - 1)));
|
||||
break;
|
||||
}
|
||||
case PacketCategory::kSwap:
|
||||
|
@ -136,6 +160,9 @@ void TraceReader::ParseTrace() {
|
|||
if (pending_break) {
|
||||
current_frame.end_ptr = trace_ptr;
|
||||
frames_.push_back(std::move(current_frame));
|
||||
current_command_buffer = new CommandBuffer();
|
||||
current_frame.command_tree =
|
||||
std::unique_ptr<CommandBuffer>(current_command_buffer);
|
||||
current_frame.start_ptr = trace_ptr;
|
||||
current_frame.end_ptr = nullptr;
|
||||
current_frame.command_count = 0;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#define XENIA_GPU_TRACE_READER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/mapped_memory.h"
|
||||
#include "xenia/gpu/trace_protocol.h"
|
||||
|
@ -51,6 +52,42 @@ namespace gpu {
|
|||
|
||||
class TraceReader {
|
||||
public:
|
||||
struct CommandBuffer {
|
||||
struct Command {
|
||||
enum class Type {
|
||||
kCommand,
|
||||
kBuffer,
|
||||
};
|
||||
|
||||
Command() {}
|
||||
Command(Command&& other) {
|
||||
type = other.type;
|
||||
command_id = other.command_id;
|
||||
command_subtree = std::move(other.command_subtree);
|
||||
}
|
||||
Command(CommandBuffer* buf) {
|
||||
type = Type::kBuffer;
|
||||
command_subtree = std::unique_ptr<CommandBuffer>(buf);
|
||||
}
|
||||
Command(uint32_t id) {
|
||||
type = Type::kCommand;
|
||||
command_id = id;
|
||||
}
|
||||
~Command() = default;
|
||||
|
||||
Type type;
|
||||
uint32_t command_id = -1;
|
||||
std::unique_ptr<CommandBuffer> command_subtree = nullptr;
|
||||
};
|
||||
|
||||
CommandBuffer() {}
|
||||
~CommandBuffer() {}
|
||||
|
||||
// Parent command buffer, if one exists.
|
||||
CommandBuffer* parent = nullptr;
|
||||
std::vector<Command> commands;
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
struct Command {
|
||||
enum class Type {
|
||||
|
@ -74,7 +111,12 @@ class TraceReader {
|
|||
const uint8_t* start_ptr = nullptr;
|
||||
const uint8_t* end_ptr = nullptr;
|
||||
int command_count = 0;
|
||||
|
||||
// Flat list of all commands in this frame.
|
||||
std::vector<Command> commands;
|
||||
|
||||
// Tree of all command buffers
|
||||
std::unique_ptr<CommandBuffer> command_tree;
|
||||
};
|
||||
|
||||
TraceReader() = default;
|
||||
|
|
|
@ -390,6 +390,66 @@ void TraceViewer::DrawPacketDisassemblerUI() {
|
|||
ImGui::End();
|
||||
}
|
||||
|
||||
int TraceViewer::RecursiveDrawCommandBufferUI(
|
||||
const TraceReader::Frame* frame, TraceReader::CommandBuffer* buffer) {
|
||||
int selected_id = -1;
|
||||
int column_width = int(ImGui::GetContentRegionMax().x);
|
||||
|
||||
for (size_t i = 0; i < buffer->commands.size(); i++) {
|
||||
switch (buffer->commands[i].type) {
|
||||
case TraceReader::CommandBuffer::Command::Type::kBuffer: {
|
||||
auto subtree = buffer->commands[i].command_subtree.get();
|
||||
if (!subtree->commands.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui::PushID(int(i));
|
||||
if (ImGui::TreeNode((void*)0, "Indirect Buffer %d", i)) {
|
||||
ImGui::Indent();
|
||||
auto id = RecursiveDrawCommandBufferUI(
|
||||
frame, buffer->commands[i].command_subtree.get());
|
||||
ImGui::Unindent();
|
||||
ImGui::TreePop();
|
||||
|
||||
if (id != -1) {
|
||||
selected_id = id;
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
} break;
|
||||
|
||||
case TraceReader::CommandBuffer::Command::Type::kCommand: {
|
||||
uint32_t command_id = buffer->commands[i].command_id;
|
||||
|
||||
const auto& command = frame->commands[command_id];
|
||||
bool is_selected = command_id == player_->current_command_index();
|
||||
const char* label;
|
||||
switch (command.type) {
|
||||
case TraceReader::Frame::Command::Type::kDraw:
|
||||
label = "Draw";
|
||||
break;
|
||||
case TraceReader::Frame::Command::Type::kSwap:
|
||||
label = "Swap";
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::PushID(command_id);
|
||||
if (ImGui::Selectable(label, &is_selected)) {
|
||||
selected_id = command_id;
|
||||
}
|
||||
ImGui::SameLine(column_width - 60.0f);
|
||||
ImGui::Text("%d", command_id);
|
||||
ImGui::PopID();
|
||||
// if (did_seek && target_command == i) {
|
||||
// ImGui::SetScrollPosHere();
|
||||
// }
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
return selected_id;
|
||||
}
|
||||
|
||||
void TraceViewer::DrawCommandListUI() {
|
||||
ImGui::SetNextWindowPos(ImVec2(5, 70), ImGuiSetCond_FirstUseEver);
|
||||
if (!ImGui::Begin("Command List", nullptr, ImVec2(200, 640))) {
|
||||
|
@ -473,31 +533,12 @@ void TraceViewer::DrawCommandListUI() {
|
|||
ImGui::SetScrollPosHere();
|
||||
}
|
||||
|
||||
for (int i = 0; i < int(frame->commands.size()); ++i) {
|
||||
ImGui::PushID(i);
|
||||
is_selected = i == player_->current_command_index();
|
||||
const auto& command = frame->commands[i];
|
||||
const char* label;
|
||||
switch (command.type) {
|
||||
case TraceReader::Frame::Command::Type::kDraw:
|
||||
label = "Draw";
|
||||
break;
|
||||
case TraceReader::Frame::Command::Type::kSwap:
|
||||
label = "Swap";
|
||||
break;
|
||||
}
|
||||
if (ImGui::Selectable(label, &is_selected)) {
|
||||
if (!player_->is_playing_trace()) {
|
||||
player_->SeekCommand(i);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine(column_width - 60.0f);
|
||||
ImGui::Text("%d", i);
|
||||
ImGui::PopID();
|
||||
if (did_seek && target_command == i) {
|
||||
ImGui::SetScrollPosHere();
|
||||
}
|
||||
auto id = RecursiveDrawCommandBufferUI(frame, frame->command_tree.get());
|
||||
if (id != -1 && id != player_->current_command_index() &&
|
||||
!player_->is_playing_trace()) {
|
||||
player_->SeekCommand(id);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
}
|
||||
|
@ -639,8 +680,8 @@ void TraceViewer::DrawTextureInfo(
|
|||
|
||||
ImGui::Columns(2);
|
||||
ImVec2 button_size(256, 256);
|
||||
if (ImGui::ImageButton(ImTextureID(texture | ui::ImGuiDrawer::kIgnoreAlpha),
|
||||
button_size, ImVec2(0, 0), ImVec2(1, 1))) {
|
||||
if (ImGui::ImageButton(ImTextureID(texture), button_size, ImVec2(0, 0),
|
||||
ImVec2(1, 1))) {
|
||||
// show viewer
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
|
@ -1108,11 +1149,14 @@ void TraceViewer::DrawStateUI() {
|
|||
((window_scissor_br >> 16) & 0x7FFF) -
|
||||
((window_scissor_tl >> 16) & 0x7FFF));
|
||||
uint32_t surface_info = regs[XE_GPU_REG_RB_SURFACE_INFO].u32;
|
||||
uint32_t surface_actual = (surface_info >> 18) & 0x3FFF;
|
||||
uint32_t surface_pitch = surface_info & 0x3FFF;
|
||||
auto surface_msaa = (surface_info >> 16) & 0x3;
|
||||
static const char* kMsaaNames[] = {
|
||||
"1X", "2X", "4X",
|
||||
};
|
||||
ImGui::BulletText("Surface Pitch - Actual: %d - %d", surface_pitch,
|
||||
surface_actual);
|
||||
ImGui::BulletText("Surface MSAA: %s", kMsaaNames[surface_msaa]);
|
||||
uint32_t vte_control = regs[XE_GPU_REG_PA_CL_VTE_CNTL].u32;
|
||||
bool vport_xscale_enable = (vte_control & (1 << 0)) > 0;
|
||||
|
@ -1124,6 +1168,9 @@ void TraceViewer::DrawStateUI() {
|
|||
assert_true(vport_xscale_enable == vport_yscale_enable ==
|
||||
vport_zscale_enable == vport_xoffset_enable ==
|
||||
vport_yoffset_enable == vport_zoffset_enable);
|
||||
if (!vport_xscale_enable) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, kColorIgnored);
|
||||
}
|
||||
ImGui::BulletText(
|
||||
"Viewport Offset: %f, %f, %f",
|
||||
vport_xoffset_enable ? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32 : 0,
|
||||
|
@ -1134,6 +1181,10 @@ void TraceViewer::DrawStateUI() {
|
|||
vport_xscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32 : 1,
|
||||
vport_yscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32 : 1,
|
||||
vport_zscale_enable ? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32 : 1);
|
||||
if (!vport_xscale_enable) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::BulletText("Vertex Format: %s, %s, %s, %s",
|
||||
((vte_control >> 8) & 0x1) ? "x/w0" : "x",
|
||||
((vte_control >> 8) & 0x1) ? "y/w0" : "y",
|
||||
|
@ -1318,7 +1369,7 @@ void TraceViewer::DrawStateUI() {
|
|||
if (write_mask) {
|
||||
auto color_target = GetColorRenderTarget(surface_pitch, surface_msaa,
|
||||
color_base, color_format);
|
||||
tex = ImTextureID(color_target | ui::ImGuiDrawer::kIgnoreAlpha);
|
||||
tex = ImTextureID(color_target);
|
||||
if (ImGui::ImageButton(tex, button_size, ImVec2(0, 0),
|
||||
ImVec2(1, 1))) {
|
||||
// show viewer
|
||||
|
@ -1330,10 +1381,9 @@ void TraceViewer::DrawStateUI() {
|
|||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text(
|
||||
"Color Target %d (%s), base %.4X, pitch %d, msaa %d, format %d",
|
||||
i, write_mask ? "enabled" : "disabled", color_base, surface_pitch,
|
||||
surface_msaa, color_format);
|
||||
ImGui::Text("Color Target %d (%s), base %.4X, pitch %d, format %d", i,
|
||||
write_mask ? "enabled" : "disabled", color_base,
|
||||
surface_pitch, color_format);
|
||||
|
||||
if (tex) {
|
||||
ImVec2 rel_pos;
|
||||
|
@ -1407,17 +1457,19 @@ void TraceViewer::DrawStateUI() {
|
|||
|
||||
auto button_pos = ImGui::GetCursorScreenPos();
|
||||
ImVec2 button_size(256, 256);
|
||||
ImGui::ImageButton(
|
||||
ImTextureID(depth_target | ui::ImGuiDrawer::kIgnoreAlpha),
|
||||
button_size, ImVec2(0, 0), ImVec2(1, 1));
|
||||
ImGui::ImageButton(ImTextureID(depth_target), button_size, ImVec2(0, 0),
|
||||
ImVec2(1, 1));
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
|
||||
ImGui::Text("Depth Target: base %.4X, pitch %d, format %d", depth_base,
|
||||
surface_pitch, depth_format);
|
||||
|
||||
ImVec2 rel_pos;
|
||||
rel_pos.x = ImGui::GetMousePos().x - button_pos.x;
|
||||
rel_pos.y = ImGui::GetMousePos().y - button_pos.y;
|
||||
ZoomedImage(ImTextureID(depth_target | ui::ImGuiDrawer::kIgnoreAlpha),
|
||||
rel_pos, button_size, 32.f, ImVec2(256, 256));
|
||||
ZoomedImage(ImTextureID(depth_target), rel_pos, button_size, 32.f,
|
||||
ImVec2(256, 256));
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
|
|
@ -80,6 +80,8 @@ class TraceViewer {
|
|||
void DrawUI();
|
||||
void DrawControllerUI();
|
||||
void DrawPacketDisassemblerUI();
|
||||
int RecursiveDrawCommandBufferUI(const TraceReader::Frame* frame,
|
||||
TraceReader::CommandBuffer* buffer);
|
||||
void DrawCommandListUI();
|
||||
void DrawStateUI();
|
||||
|
||||
|
|
|
@ -22,98 +22,19 @@ namespace vulkan {
|
|||
|
||||
using xe::ui::vulkan::CheckResult;
|
||||
|
||||
// Space kept between tail and head when wrapping.
|
||||
constexpr VkDeviceSize kDeadZone = 4 * 1024;
|
||||
|
||||
constexpr VkDeviceSize kConstantRegisterUniformRange =
|
||||
512 * 4 * 4 + 8 * 4 + 32 * 4;
|
||||
|
||||
BufferCache::BufferCache(RegisterFile* register_file,
|
||||
ui::vulkan::VulkanDevice* device, size_t capacity)
|
||||
: register_file_(register_file),
|
||||
device_(*device),
|
||||
transient_capacity_(capacity) {
|
||||
// Uniform buffer.
|
||||
VkBufferCreateInfo uniform_buffer_info;
|
||||
uniform_buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
uniform_buffer_info.pNext = nullptr;
|
||||
uniform_buffer_info.flags = 0;
|
||||
uniform_buffer_info.size = transient_capacity_;
|
||||
uniform_buffer_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
||||
uniform_buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
uniform_buffer_info.queueFamilyIndexCount = 0;
|
||||
uniform_buffer_info.pQueueFamilyIndices = nullptr;
|
||||
auto err = vkCreateBuffer(device_, &uniform_buffer_info, nullptr,
|
||||
&transient_uniform_buffer_);
|
||||
CheckResult(err, "vkCreateBuffer");
|
||||
|
||||
// Index buffer.
|
||||
VkBufferCreateInfo index_buffer_info;
|
||||
index_buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
index_buffer_info.pNext = nullptr;
|
||||
index_buffer_info.flags = 0;
|
||||
index_buffer_info.size = transient_capacity_;
|
||||
index_buffer_info.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||
index_buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
index_buffer_info.queueFamilyIndexCount = 0;
|
||||
index_buffer_info.pQueueFamilyIndices = nullptr;
|
||||
err = vkCreateBuffer(device_, &index_buffer_info, nullptr,
|
||||
&transient_index_buffer_);
|
||||
CheckResult(err, "vkCreateBuffer");
|
||||
|
||||
// Vertex buffer.
|
||||
VkBufferCreateInfo vertex_buffer_info;
|
||||
vertex_buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
vertex_buffer_info.pNext = nullptr;
|
||||
vertex_buffer_info.flags = 0;
|
||||
vertex_buffer_info.size = transient_capacity_;
|
||||
vertex_buffer_info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
||||
vertex_buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
vertex_buffer_info.queueFamilyIndexCount = 0;
|
||||
vertex_buffer_info.pQueueFamilyIndices = nullptr;
|
||||
err = vkCreateBuffer(*device, &vertex_buffer_info, nullptr,
|
||||
&transient_vertex_buffer_);
|
||||
CheckResult(err, "vkCreateBuffer");
|
||||
|
||||
// Allocate the underlying buffer we use for all storage.
|
||||
// We query all types and take the max alignment.
|
||||
VkMemoryRequirements uniform_buffer_requirements;
|
||||
VkMemoryRequirements index_buffer_requirements;
|
||||
VkMemoryRequirements vertex_buffer_requirements;
|
||||
vkGetBufferMemoryRequirements(device_, transient_uniform_buffer_,
|
||||
&uniform_buffer_requirements);
|
||||
vkGetBufferMemoryRequirements(device_, transient_index_buffer_,
|
||||
&index_buffer_requirements);
|
||||
vkGetBufferMemoryRequirements(device_, transient_vertex_buffer_,
|
||||
&vertex_buffer_requirements);
|
||||
uniform_buffer_alignment_ = uniform_buffer_requirements.alignment;
|
||||
index_buffer_alignment_ = index_buffer_requirements.alignment;
|
||||
vertex_buffer_alignment_ = vertex_buffer_requirements.alignment;
|
||||
VkMemoryRequirements buffer_requirements;
|
||||
buffer_requirements.size = transient_capacity_;
|
||||
buffer_requirements.alignment =
|
||||
std::max(uniform_buffer_requirements.alignment,
|
||||
std::max(index_buffer_requirements.alignment,
|
||||
vertex_buffer_requirements.alignment));
|
||||
buffer_requirements.memoryTypeBits =
|
||||
uniform_buffer_requirements.memoryTypeBits |
|
||||
index_buffer_requirements.memoryTypeBits |
|
||||
vertex_buffer_requirements.memoryTypeBits;
|
||||
transient_buffer_memory_ = device->AllocateMemory(
|
||||
buffer_requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||||
|
||||
// Alias all buffers to our memory.
|
||||
vkBindBufferMemory(device_, transient_uniform_buffer_,
|
||||
transient_buffer_memory_, 0);
|
||||
vkBindBufferMemory(device_, transient_index_buffer_, transient_buffer_memory_,
|
||||
0);
|
||||
vkBindBufferMemory(device_, transient_vertex_buffer_,
|
||||
transient_buffer_memory_, 0);
|
||||
|
||||
// Map memory and keep it mapped while we use it.
|
||||
err = vkMapMemory(device_, transient_buffer_memory_, 0, VK_WHOLE_SIZE, 0,
|
||||
&transient_buffer_data_);
|
||||
CheckResult(err, "vkMapMemory");
|
||||
: register_file_(register_file), device_(*device) {
|
||||
transient_buffer_ = std::make_unique<ui::vulkan::CircularBuffer>(device);
|
||||
if (!transient_buffer_->Initialize(capacity,
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)) {
|
||||
assert_always();
|
||||
}
|
||||
|
||||
// Descriptor pool used for all of our cached descriptors.
|
||||
// In the steady state we don't allocate anything, so these are all manually
|
||||
|
@ -129,8 +50,8 @@ BufferCache::BufferCache(RegisterFile* register_file,
|
|||
pool_sizes[0].descriptorCount = 2;
|
||||
descriptor_pool_info.poolSizeCount = 1;
|
||||
descriptor_pool_info.pPoolSizes = pool_sizes;
|
||||
err = vkCreateDescriptorPool(device_, &descriptor_pool_info, nullptr,
|
||||
&descriptor_pool_);
|
||||
auto err = vkCreateDescriptorPool(device_, &descriptor_pool_info, nullptr,
|
||||
&descriptor_pool_);
|
||||
CheckResult(err, "vkCreateDescriptorPool");
|
||||
|
||||
// Create the descriptor set layout used for our uniform buffer.
|
||||
|
@ -180,7 +101,7 @@ BufferCache::BufferCache(RegisterFile* register_file,
|
|||
|
||||
// Initialize descriptor set with our buffers.
|
||||
VkDescriptorBufferInfo buffer_info;
|
||||
buffer_info.buffer = transient_uniform_buffer_;
|
||||
buffer_info.buffer = transient_buffer_->gpu_buffer();
|
||||
buffer_info.offset = 0;
|
||||
buffer_info.range = kConstantRegisterUniformRange;
|
||||
VkWriteDescriptorSet descriptor_writes[2];
|
||||
|
@ -212,25 +133,20 @@ BufferCache::~BufferCache() {
|
|||
&transient_descriptor_set_);
|
||||
vkDestroyDescriptorSetLayout(device_, descriptor_set_layout_, nullptr);
|
||||
vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr);
|
||||
vkUnmapMemory(device_, transient_buffer_memory_);
|
||||
vkFreeMemory(device_, transient_buffer_memory_, nullptr);
|
||||
vkDestroyBuffer(device_, transient_uniform_buffer_, nullptr);
|
||||
vkDestroyBuffer(device_, transient_index_buffer_, nullptr);
|
||||
vkDestroyBuffer(device_, transient_vertex_buffer_, nullptr);
|
||||
transient_buffer_->Shutdown();
|
||||
}
|
||||
|
||||
std::pair<VkDeviceSize, VkDeviceSize> BufferCache::UploadConstantRegisters(
|
||||
const Shader::ConstantRegisterMap& vertex_constant_register_map,
|
||||
const Shader::ConstantRegisterMap& pixel_constant_register_map) {
|
||||
const Shader::ConstantRegisterMap& pixel_constant_register_map,
|
||||
std::shared_ptr<ui::vulkan::Fence> fence) {
|
||||
// Fat struct, including all registers:
|
||||
// struct {
|
||||
// vec4 float[512];
|
||||
// uint bool[8];
|
||||
// uint loop[32];
|
||||
// };
|
||||
size_t total_size =
|
||||
xe::round_up(kConstantRegisterUniformRange, uniform_buffer_alignment_);
|
||||
auto offset = AllocateTransientData(uniform_buffer_alignment_, total_size);
|
||||
auto offset = AllocateTransientData(kConstantRegisterUniformRange, fence);
|
||||
if (offset == VK_WHOLE_SIZE) {
|
||||
// OOM.
|
||||
return {VK_WHOLE_SIZE, VK_WHOLE_SIZE};
|
||||
|
@ -238,8 +154,7 @@ std::pair<VkDeviceSize, VkDeviceSize> BufferCache::UploadConstantRegisters(
|
|||
|
||||
// Copy over all the registers.
|
||||
const auto& values = register_file_->values;
|
||||
uint8_t* dest_ptr =
|
||||
reinterpret_cast<uint8_t*>(transient_buffer_data_) + offset;
|
||||
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;
|
||||
|
@ -258,8 +173,8 @@ std::pair<VkDeviceSize, VkDeviceSize> BufferCache::UploadConstantRegisters(
|
|||
// constant indexing.
|
||||
#if 0
|
||||
// Allocate space in the buffer for our data.
|
||||
auto offset = AllocateTransientData(uniform_buffer_alignment_,
|
||||
constant_register_map.packed_byte_length);
|
||||
auto offset =
|
||||
AllocateTransientData(constant_register_map.packed_byte_length, fence);
|
||||
if (offset == VK_WHOLE_SIZE) {
|
||||
// OOM.
|
||||
return VK_WHOLE_SIZE;
|
||||
|
@ -304,11 +219,12 @@ std::pair<VkDeviceSize, VkDeviceSize> BufferCache::UploadConstantRegisters(
|
|||
}
|
||||
|
||||
std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadIndexBuffer(
|
||||
const void* source_ptr, size_t source_length, IndexFormat format) {
|
||||
const void* source_ptr, size_t source_length, IndexFormat format,
|
||||
std::shared_ptr<ui::vulkan::Fence> fence) {
|
||||
// TODO(benvanik): check cache.
|
||||
|
||||
// Allocate space in the buffer for our data.
|
||||
auto offset = AllocateTransientData(index_buffer_alignment_, source_length);
|
||||
auto offset = AllocateTransientData(source_length, fence);
|
||||
if (offset == VK_WHOLE_SIZE) {
|
||||
// OOM.
|
||||
return {nullptr, VK_WHOLE_SIZE};
|
||||
|
@ -319,25 +235,24 @@ std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadIndexBuffer(
|
|||
// TODO(benvanik): memcpy then use compute shaders to swap?
|
||||
if (format == IndexFormat::kInt16) {
|
||||
// Endian::k8in16, swap half-words.
|
||||
xe::copy_and_swap_16_aligned(
|
||||
reinterpret_cast<uint8_t*>(transient_buffer_data_) + offset, source_ptr,
|
||||
source_length / 2);
|
||||
xe::copy_and_swap_16_aligned(transient_buffer_->host_base() + offset,
|
||||
source_ptr, source_length / 2);
|
||||
} else if (format == IndexFormat::kInt32) {
|
||||
// Endian::k8in32, swap words.
|
||||
xe::copy_and_swap_32_aligned(
|
||||
reinterpret_cast<uint8_t*>(transient_buffer_data_) + offset, source_ptr,
|
||||
source_length / 4);
|
||||
xe::copy_and_swap_32_aligned(transient_buffer_->host_base() + offset,
|
||||
source_ptr, source_length / 4);
|
||||
}
|
||||
|
||||
return {transient_index_buffer_, offset};
|
||||
return {transient_buffer_->gpu_buffer(), offset};
|
||||
}
|
||||
|
||||
std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadVertexBuffer(
|
||||
const void* source_ptr, size_t source_length) {
|
||||
const void* source_ptr, size_t source_length, Endian endian,
|
||||
std::shared_ptr<ui::vulkan::Fence> fence) {
|
||||
// TODO(benvanik): check cache.
|
||||
|
||||
// Allocate space in the buffer for our data.
|
||||
auto offset = AllocateTransientData(vertex_buffer_alignment_, source_length);
|
||||
auto offset = AllocateTransientData(source_length, fence);
|
||||
if (offset == VK_WHOLE_SIZE) {
|
||||
// OOM.
|
||||
return {nullptr, VK_WHOLE_SIZE};
|
||||
|
@ -345,60 +260,38 @@ std::pair<VkBuffer, VkDeviceSize> BufferCache::UploadVertexBuffer(
|
|||
|
||||
// Copy data into the buffer.
|
||||
// TODO(benvanik): memcpy then use compute shaders to swap?
|
||||
// Endian::k8in32, swap words.
|
||||
xe::copy_and_swap_32_aligned(
|
||||
reinterpret_cast<uint8_t*>(transient_buffer_data_) + offset, source_ptr,
|
||||
source_length / 4);
|
||||
assert_true(endian == Endian::k8in32);
|
||||
if (endian == Endian::k8in32) {
|
||||
// Endian::k8in32, swap words.
|
||||
xe::copy_and_swap_32_aligned(transient_buffer_->host_base() + offset,
|
||||
source_ptr, source_length / 4);
|
||||
}
|
||||
|
||||
return {transient_vertex_buffer_, offset};
|
||||
return {transient_buffer_->gpu_buffer(), offset};
|
||||
}
|
||||
|
||||
VkDeviceSize BufferCache::AllocateTransientData(VkDeviceSize alignment,
|
||||
VkDeviceSize length) {
|
||||
VkDeviceSize BufferCache::AllocateTransientData(
|
||||
VkDeviceSize length, std::shared_ptr<ui::vulkan::Fence> fence) {
|
||||
// Try fast path (if we have space).
|
||||
VkDeviceSize offset = TryAllocateTransientData(alignment, length);
|
||||
VkDeviceSize offset = TryAllocateTransientData(length, fence);
|
||||
if (offset != VK_WHOLE_SIZE) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
// Ran out of easy allocations.
|
||||
// Try consuming fences before we panic.
|
||||
assert_always("Reclamation not yet implemented");
|
||||
transient_buffer_->Scavenge();
|
||||
|
||||
// Try again. It may still fail if we didn't get enough space back.
|
||||
return TryAllocateTransientData(alignment, length);
|
||||
offset = TryAllocateTransientData(length, fence);
|
||||
return offset;
|
||||
}
|
||||
|
||||
VkDeviceSize BufferCache::TryAllocateTransientData(VkDeviceSize alignment,
|
||||
VkDeviceSize length) {
|
||||
if (transient_tail_offset_ >= transient_head_offset_) {
|
||||
// Tail follows head, so things are easy:
|
||||
// | H----T |
|
||||
if (xe::round_up(transient_tail_offset_, alignment) + length <=
|
||||
transient_capacity_) {
|
||||
// Allocation fits from tail to end of buffer, so grow.
|
||||
// | H----**T |
|
||||
VkDeviceSize offset = xe::round_up(transient_tail_offset_, alignment);
|
||||
transient_tail_offset_ = offset + length;
|
||||
return offset;
|
||||
} else if (length + kDeadZone <= transient_head_offset_) {
|
||||
// Can't fit at the end, but can fit if we wrap around.
|
||||
// |**T H----....|
|
||||
VkDeviceSize offset = 0;
|
||||
transient_tail_offset_ = length;
|
||||
return offset;
|
||||
}
|
||||
} else {
|
||||
// Head follows tail, so we're reversed:
|
||||
// |----T H---|
|
||||
if (xe::round_up(transient_tail_offset_, alignment) + length + kDeadZone <=
|
||||
transient_head_offset_) {
|
||||
// Fits from tail to head.
|
||||
// |----***T H---|
|
||||
VkDeviceSize offset = xe::round_up(transient_tail_offset_, alignment);
|
||||
transient_tail_offset_ = offset + length;
|
||||
return offset;
|
||||
}
|
||||
VkDeviceSize BufferCache::TryAllocateTransientData(
|
||||
VkDeviceSize length, std::shared_ptr<ui::vulkan::Fence> fence) {
|
||||
auto alloc = transient_buffer_->Acquire(length, fence);
|
||||
if (alloc) {
|
||||
return alloc->offset;
|
||||
}
|
||||
|
||||
// No more space.
|
||||
|
@ -420,9 +313,9 @@ void BufferCache::Flush(VkCommandBuffer command_buffer) {
|
|||
VkMappedMemoryRange dirty_range;
|
||||
dirty_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||
dirty_range.pNext = nullptr;
|
||||
dirty_range.memory = transient_buffer_memory_;
|
||||
dirty_range.memory = transient_buffer_->gpu_memory();
|
||||
dirty_range.offset = 0;
|
||||
dirty_range.size = transient_capacity_;
|
||||
dirty_range.size = transient_buffer_->capacity();
|
||||
vkFlushMappedMemoryRanges(device_, 1, &dirty_range);
|
||||
}
|
||||
|
||||
|
@ -434,6 +327,8 @@ void BufferCache::ClearCache() {
|
|||
// TODO(benvanik): caching.
|
||||
}
|
||||
|
||||
void BufferCache::Scavenge() { transient_buffer_->Scavenge(); }
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "xenia/gpu/register_file.h"
|
||||
#include "xenia/gpu/shader.h"
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/ui/vulkan/circular_buffer.h"
|
||||
#include "xenia/ui/vulkan/vulkan.h"
|
||||
#include "xenia/ui/vulkan/vulkan_device.h"
|
||||
|
||||
|
@ -50,22 +51,24 @@ class BufferCache {
|
|||
// The returned offsets may alias.
|
||||
std::pair<VkDeviceSize, VkDeviceSize> UploadConstantRegisters(
|
||||
const Shader::ConstantRegisterMap& vertex_constant_register_map,
|
||||
const Shader::ConstantRegisterMap& pixel_constant_register_map);
|
||||
const Shader::ConstantRegisterMap& pixel_constant_register_map,
|
||||
std::shared_ptr<ui::vulkan::Fence> 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(const void* source_ptr,
|
||||
size_t source_length,
|
||||
IndexFormat format);
|
||||
std::pair<VkBuffer, VkDeviceSize> UploadIndexBuffer(
|
||||
const void* source_ptr, size_t source_length, IndexFormat format,
|
||||
std::shared_ptr<ui::vulkan::Fence> 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(const void* source_ptr,
|
||||
size_t source_length);
|
||||
std::pair<VkBuffer, VkDeviceSize> UploadVertexBuffer(
|
||||
const void* source_ptr, size_t source_length, Endian endian,
|
||||
std::shared_ptr<ui::vulkan::Fence> fence);
|
||||
|
||||
// Flushes all pending data to the GPU.
|
||||
// Until this is called the GPU is not guaranteed to see any data.
|
||||
|
@ -81,36 +84,26 @@ class BufferCache {
|
|||
// Clears all cached content and prevents future elision with pending data.
|
||||
void ClearCache();
|
||||
|
||||
// Wipes all data no longer needed.
|
||||
void Scavenge();
|
||||
|
||||
private:
|
||||
// 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 alignment,
|
||||
VkDeviceSize length);
|
||||
VkDeviceSize AllocateTransientData(VkDeviceSize length,
|
||||
std::shared_ptr<ui::vulkan::Fence> 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 alignment,
|
||||
VkDeviceSize length);
|
||||
VkDeviceSize TryAllocateTransientData(
|
||||
VkDeviceSize length, std::shared_ptr<ui::vulkan::Fence> fence);
|
||||
|
||||
RegisterFile* register_file_ = nullptr;
|
||||
VkDevice device_ = nullptr;
|
||||
|
||||
// Staging ringbuffer we cycle through fast. Used for data we don't
|
||||
// plan on keeping past the current frame.
|
||||
size_t transient_capacity_ = 0;
|
||||
VkBuffer transient_uniform_buffer_ = nullptr;
|
||||
VkBuffer transient_index_buffer_ = nullptr;
|
||||
VkBuffer transient_vertex_buffer_ = nullptr;
|
||||
VkDeviceMemory transient_buffer_memory_ = nullptr;
|
||||
void* transient_buffer_data_ = nullptr;
|
||||
VkDeviceSize transient_head_offset_ = 0;
|
||||
VkDeviceSize transient_tail_offset_ = 0;
|
||||
|
||||
// Required alignments for our various types.
|
||||
// All allocations must start at the appropriate alignment.
|
||||
VkDeviceSize uniform_buffer_alignment_ = 0;
|
||||
VkDeviceSize index_buffer_alignment_ = 0;
|
||||
VkDeviceSize vertex_buffer_alignment_ = 0;
|
||||
std::unique_ptr<ui::vulkan::CircularBuffer> transient_buffer_ = nullptr;
|
||||
|
||||
VkDescriptorPool descriptor_pool_ = nullptr;
|
||||
VkDescriptorSetLayout descriptor_set_layout_ = nullptr;
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#include "xenia/gpu/gpu_flags.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_gpu_flags.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace vulkan {
|
||||
|
@ -154,40 +157,19 @@ VulkanShader* PipelineCache::LoadShader(ShaderType shader_type,
|
|||
host_address, dword_count);
|
||||
shader_map_.insert({data_hash, shader});
|
||||
|
||||
// Perform translation.
|
||||
// If this fails the shader will be marked as invalid and ignored later.
|
||||
if (!shader_translator_.Translate(shader)) {
|
||||
XELOGE("Shader translation failed; marking shader as ignored");
|
||||
return shader;
|
||||
}
|
||||
|
||||
// Prepare the shader for use (creates our VkShaderModule).
|
||||
// It could still fail at this point.
|
||||
if (!shader->Prepare()) {
|
||||
XELOGE("Shader preparation failed; marking shader as ignored");
|
||||
return shader;
|
||||
}
|
||||
|
||||
if (shader->is_valid()) {
|
||||
XELOGGPU("Generated %s shader at 0x%.8X (%db):\n%s",
|
||||
shader_type == ShaderType::kVertex ? "vertex" : "pixel",
|
||||
guest_address, dword_count * 4,
|
||||
shader->ucode_disassembly().c_str());
|
||||
}
|
||||
|
||||
// Dump shader files if desired.
|
||||
if (!FLAGS_dump_shaders.empty()) {
|
||||
shader->Dump(FLAGS_dump_shaders, "vk");
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
bool PipelineCache::ConfigurePipeline(VkCommandBuffer command_buffer,
|
||||
const RenderState* render_state,
|
||||
VulkanShader* vertex_shader,
|
||||
VulkanShader* pixel_shader,
|
||||
PrimitiveType primitive_type) {
|
||||
PipelineCache::UpdateStatus PipelineCache::ConfigurePipeline(
|
||||
VkCommandBuffer command_buffer, const RenderState* render_state,
|
||||
VulkanShader* vertex_shader, VulkanShader* pixel_shader,
|
||||
PrimitiveType primitive_type, VkPipeline* pipeline_out) {
|
||||
#if FINE_GRAINED_DRAW_SCOPES
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
#endif // FINE_GRAINED_DRAW_SCOPES
|
||||
|
||||
assert_not_null(pipeline_out);
|
||||
|
||||
// Perform a pass over all registers and state updating our cached structures.
|
||||
// This will tell us if anything has changed that requires us to either build
|
||||
// a new pipeline or use an existing one.
|
||||
|
@ -208,7 +190,7 @@ bool PipelineCache::ConfigurePipeline(VkCommandBuffer command_buffer,
|
|||
// Error updating state - bail out.
|
||||
// We are in an indeterminate state, so reset things for the next attempt.
|
||||
current_pipeline_ = nullptr;
|
||||
return false;
|
||||
return update_status;
|
||||
}
|
||||
if (!pipeline) {
|
||||
// Should have a hash key produced by the UpdateState pass.
|
||||
|
@ -217,24 +199,12 @@ bool PipelineCache::ConfigurePipeline(VkCommandBuffer command_buffer,
|
|||
current_pipeline_ = pipeline;
|
||||
if (!pipeline) {
|
||||
// Unable to create pipeline.
|
||||
return false;
|
||||
return UpdateStatus::kError;
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the pipeline.
|
||||
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
|
||||
// Issue all changed dynamic state information commands.
|
||||
// TODO(benvanik): dynamic state is kept in the command buffer, so if we
|
||||
// have issued it before (regardless of pipeline) we don't need to do it now.
|
||||
// TODO(benvanik): track whether we have issued on the given command buffer.
|
||||
bool full_dynamic_state = true;
|
||||
if (!SetDynamicState(command_buffer, full_dynamic_state)) {
|
||||
// Failed to update state.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
*pipeline_out = pipeline;
|
||||
return update_status;
|
||||
}
|
||||
|
||||
void PipelineCache::ClearCache() {
|
||||
|
@ -291,16 +261,140 @@ VkPipeline PipelineCache::GetPipeline(const RenderState* render_state,
|
|||
pipeline_info.basePipelineHandle = nullptr;
|
||||
pipeline_info.basePipelineIndex = 0;
|
||||
VkPipeline pipeline = nullptr;
|
||||
auto err = vkCreateGraphicsPipelines(device_, nullptr, 1, &pipeline_info,
|
||||
nullptr, &pipeline);
|
||||
auto err = vkCreateGraphicsPipelines(device_, pipeline_cache_, 1,
|
||||
&pipeline_info, nullptr, &pipeline);
|
||||
CheckResult(err, "vkCreateGraphicsPipelines");
|
||||
|
||||
// Dump shader disassembly.
|
||||
if (FLAGS_vulkan_dump_disasm) {
|
||||
DumpShaderDisasmNV(pipeline_info);
|
||||
}
|
||||
|
||||
// Add to cache with the hash key for reuse.
|
||||
cached_pipelines_.insert({hash_key, pipeline});
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
bool PipelineCache::TranslateShader(VulkanShader* shader,
|
||||
xenos::xe_gpu_program_cntl_t cntl) {
|
||||
// Perform translation.
|
||||
// If this fails the shader will be marked as invalid and ignored later.
|
||||
if (!shader_translator_.Translate(shader, cntl)) {
|
||||
XELOGE("Shader translation failed; marking shader as ignored");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare the shader for use (creates our VkShaderModule).
|
||||
// It could still fail at this point.
|
||||
if (!shader->Prepare()) {
|
||||
XELOGE("Shader preparation failed; marking shader as ignored");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shader->is_valid()) {
|
||||
XELOGGPU("Generated %s shader (%db) - hash %.16" PRIX64 ":\n%s\n",
|
||||
shader->type() == ShaderType::kVertex ? "vertex" : "pixel",
|
||||
shader->ucode_dword_count() * 4, shader->ucode_data_hash(),
|
||||
shader->ucode_disassembly().c_str());
|
||||
}
|
||||
|
||||
// Dump shader files if desired.
|
||||
if (!FLAGS_dump_shaders.empty()) {
|
||||
shader->Dump(FLAGS_dump_shaders, "vk");
|
||||
}
|
||||
|
||||
return shader->is_valid();
|
||||
}
|
||||
|
||||
void PipelineCache::DumpShaderDisasmNV(
|
||||
const VkGraphicsPipelineCreateInfo& pipeline_info) {
|
||||
// !! HACK !!: This only works on NVidia drivers. Dumps shader disasm.
|
||||
// This code is super ugly. Update this when NVidia includes an official
|
||||
// way to dump shader disassembly.
|
||||
|
||||
VkPipelineCacheCreateInfo pipeline_cache_info;
|
||||
VkPipelineCache dummy_pipeline_cache;
|
||||
pipeline_cache_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
||||
pipeline_cache_info.pNext = nullptr;
|
||||
pipeline_cache_info.flags = 0;
|
||||
pipeline_cache_info.initialDataSize = 0;
|
||||
pipeline_cache_info.pInitialData = nullptr;
|
||||
auto err = vkCreatePipelineCache(device_, &pipeline_cache_info, nullptr,
|
||||
&dummy_pipeline_cache);
|
||||
CheckResult(err, "vkCreatePipelineCache");
|
||||
|
||||
// Create a pipeline on the dummy cache and dump it.
|
||||
VkPipeline dummy_pipeline;
|
||||
err = vkCreateGraphicsPipelines(device_, dummy_pipeline_cache, 1,
|
||||
&pipeline_info, nullptr, &dummy_pipeline);
|
||||
|
||||
std::vector<uint8_t> pipeline_data;
|
||||
size_t data_size = 0;
|
||||
err = vkGetPipelineCacheData(device_, dummy_pipeline_cache, &data_size,
|
||||
nullptr);
|
||||
if (err == VK_SUCCESS) {
|
||||
pipeline_data.resize(data_size);
|
||||
vkGetPipelineCacheData(device_, dummy_pipeline_cache, &data_size,
|
||||
pipeline_data.data());
|
||||
|
||||
// Scan the data for the disassembly.
|
||||
std::string disasm_vp, disasm_fp;
|
||||
|
||||
const char* disasm_start_vp = nullptr;
|
||||
const char* disasm_start_fp = nullptr;
|
||||
size_t search_offset = 0;
|
||||
const char* search_start =
|
||||
reinterpret_cast<const char*>(pipeline_data.data());
|
||||
while (true) {
|
||||
auto p = reinterpret_cast<const char*>(
|
||||
memchr(pipeline_data.data() + search_offset, '!',
|
||||
pipeline_data.size() - search_offset));
|
||||
if (!p) {
|
||||
break;
|
||||
}
|
||||
if (!strncmp(p, "!!NV", 4)) {
|
||||
if (!strncmp(p + 4, "vp", 2)) {
|
||||
disasm_start_vp = p;
|
||||
} else if (!strncmp(p + 4, "fp", 2)) {
|
||||
disasm_start_fp = p;
|
||||
}
|
||||
|
||||
if (disasm_start_fp && disasm_start_vp) {
|
||||
// Found all we needed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
search_offset = p - search_start;
|
||||
++search_offset;
|
||||
}
|
||||
if (disasm_start_vp) {
|
||||
disasm_vp = std::string(disasm_start_vp);
|
||||
|
||||
// For some reason there's question marks all over the code.
|
||||
disasm_vp.erase(std::remove(disasm_vp.begin(), disasm_vp.end(), '?'),
|
||||
disasm_vp.end());
|
||||
} else {
|
||||
disasm_vp = std::string("Shader disassembly not available.");
|
||||
}
|
||||
|
||||
if (disasm_start_fp) {
|
||||
disasm_fp = std::string(disasm_start_fp);
|
||||
|
||||
// For some reason there's question marks all over the code.
|
||||
disasm_fp.erase(std::remove(disasm_fp.begin(), disasm_fp.end(), '?'),
|
||||
disasm_fp.end());
|
||||
} else {
|
||||
disasm_fp = std::string("Shader disassembly not available.");
|
||||
}
|
||||
|
||||
XELOGI("%s\n=====================================\n%s\n", disasm_vp.c_str(),
|
||||
disasm_fp.c_str());
|
||||
}
|
||||
|
||||
vkDestroyPipelineCache(device_, dummy_pipeline_cache, nullptr);
|
||||
}
|
||||
|
||||
VkShaderModule PipelineCache::GetGeometryShader(PrimitiveType primitive_type,
|
||||
bool is_line_mode) {
|
||||
switch (primitive_type) {
|
||||
|
@ -334,10 +428,16 @@ VkShaderModule PipelineCache::GetGeometryShader(PrimitiveType primitive_type,
|
|||
|
||||
bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer,
|
||||
bool full_update) {
|
||||
#if FINE_GRAINED_DRAW_SCOPES
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
#endif // FINE_GRAINED_DRAW_SCOPES
|
||||
|
||||
auto& regs = set_dynamic_state_registers_;
|
||||
|
||||
bool window_offset_dirty = SetShadowRegister(®s.pa_sc_window_offset,
|
||||
XE_GPU_REG_PA_SC_WINDOW_OFFSET);
|
||||
window_offset_dirty |= SetShadowRegister(®s.pa_su_sc_mode_cntl,
|
||||
XE_GPU_REG_PA_SU_SC_MODE_CNTL);
|
||||
|
||||
// Window parameters.
|
||||
// http://ftp.tku.edu.tw/NetBSD/NetBSD-current/xsrc/external/mit/xf86-video-ati/dist/src/r600_reg_auto_r6xx.h
|
||||
|
@ -397,22 +497,21 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer,
|
|||
viewport_state_dirty |= SetShadowRegister(®s.pa_cl_vport_zscale,
|
||||
XE_GPU_REG_PA_CL_VPORT_ZSCALE);
|
||||
if (viewport_state_dirty) {
|
||||
// HACK: no clue where to get these values.
|
||||
// RB_SURFACE_INFO
|
||||
auto surface_msaa =
|
||||
static_cast<MsaaSamples>((regs.rb_surface_info >> 16) & 0x3);
|
||||
// TODO(benvanik): ??
|
||||
|
||||
// Apply a multiplier to emulate MSAA.
|
||||
float window_width_scalar = 1;
|
||||
float window_height_scalar = 1;
|
||||
switch (surface_msaa) {
|
||||
case MsaaSamples::k1X:
|
||||
break;
|
||||
case MsaaSamples::k2X:
|
||||
window_width_scalar = 2;
|
||||
window_height_scalar = 2;
|
||||
break;
|
||||
case MsaaSamples::k4X:
|
||||
window_width_scalar = 2;
|
||||
window_height_scalar = 2;
|
||||
window_width_scalar = window_height_scalar = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -429,10 +528,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer,
|
|||
vport_yoffset_enable == vport_zoffset_enable);
|
||||
|
||||
VkViewport viewport_rect;
|
||||
viewport_rect.x = 0;
|
||||
viewport_rect.y = 0;
|
||||
viewport_rect.width = 100;
|
||||
viewport_rect.height = 100;
|
||||
std::memset(&viewport_rect, 0, sizeof(VkViewport));
|
||||
viewport_rect.minDepth = 0;
|
||||
viewport_rect.maxDepth = 1;
|
||||
|
||||
|
@ -443,6 +539,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer,
|
|||
float voy = vport_yoffset_enable ? regs.pa_cl_vport_yoffset : 0;
|
||||
float vsx = vport_xscale_enable ? regs.pa_cl_vport_xscale : 1;
|
||||
float vsy = vport_yscale_enable ? regs.pa_cl_vport_yscale : 1;
|
||||
|
||||
window_width_scalar = window_height_scalar = 1;
|
||||
float vpw = 2 * window_width_scalar * vsx;
|
||||
float vph = -2 * window_height_scalar * vsy;
|
||||
|
@ -490,25 +587,25 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer,
|
|||
vkCmdSetBlendConstants(command_buffer, regs.rb_blend_rgba);
|
||||
}
|
||||
|
||||
// VK_DYNAMIC_STATE_LINE_WIDTH
|
||||
vkCmdSetLineWidth(command_buffer, 1.0f);
|
||||
if (full_update) {
|
||||
// VK_DYNAMIC_STATE_LINE_WIDTH
|
||||
vkCmdSetLineWidth(command_buffer, 1.0f);
|
||||
|
||||
// VK_DYNAMIC_STATE_DEPTH_BIAS
|
||||
vkCmdSetDepthBias(command_buffer, 0.0f, 0.0f, 0.0f);
|
||||
// VK_DYNAMIC_STATE_DEPTH_BIAS
|
||||
vkCmdSetDepthBias(command_buffer, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
// VK_DYNAMIC_STATE_DEPTH_BOUNDS
|
||||
vkCmdSetDepthBounds(command_buffer, 0.0f, 1.0f);
|
||||
// VK_DYNAMIC_STATE_DEPTH_BOUNDS
|
||||
vkCmdSetDepthBounds(command_buffer, 0.0f, 1.0f);
|
||||
|
||||
// VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK
|
||||
vkCmdSetStencilCompareMask(command_buffer, VK_STENCIL_FRONT_AND_BACK, 0);
|
||||
// VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK
|
||||
vkCmdSetStencilCompareMask(command_buffer, VK_STENCIL_FRONT_AND_BACK, 0);
|
||||
|
||||
// VK_DYNAMIC_STATE_STENCIL_REFERENCE
|
||||
vkCmdSetStencilReference(command_buffer, VK_STENCIL_FRONT_AND_BACK, 0);
|
||||
// VK_DYNAMIC_STATE_STENCIL_REFERENCE
|
||||
vkCmdSetStencilReference(command_buffer, VK_STENCIL_FRONT_AND_BACK, 0);
|
||||
|
||||
// VK_DYNAMIC_STATE_STENCIL_WRITE_MASK
|
||||
vkCmdSetStencilWriteMask(command_buffer, VK_STENCIL_FRONT_AND_BACK, 0);
|
||||
|
||||
// TODO(benvanik): push constants.
|
||||
// VK_DYNAMIC_STATE_STENCIL_WRITE_MASK
|
||||
vkCmdSetStencilWriteMask(command_buffer, VK_STENCIL_FRONT_AND_BACK, 0);
|
||||
}
|
||||
|
||||
bool push_constants_dirty = full_update || viewport_state_dirty;
|
||||
push_constants_dirty |=
|
||||
|
@ -539,7 +636,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer,
|
|||
push_constants.window_scale[1] = -1.0f;
|
||||
} else {
|
||||
push_constants.window_scale[0] = 1.0f / 2560.0f;
|
||||
push_constants.window_scale[1] = -1.0f / 2560.0f;
|
||||
push_constants.window_scale[1] = 1.0f / 2560.0f;
|
||||
}
|
||||
|
||||
// http://www.x.org/docs/AMD/old/evergreen_3D_registers_v2.pdf
|
||||
|
@ -558,7 +655,7 @@ bool PipelineCache::SetDynamicState(VkCommandBuffer command_buffer,
|
|||
push_constants.vtx_fmt[3] = vtx_w0_fmt;
|
||||
|
||||
// Alpha testing -- ALPHAREF, ALPHAFUNC, ALPHATESTENABLE
|
||||
// Deprecated in Vulkan, implemented in shader.
|
||||
// Emulated in shader.
|
||||
// if(ALPHATESTENABLE && frag_out.a [<=/ALPHAFUNC] ALPHAREF) discard;
|
||||
// ALPHATESTENABLE
|
||||
push_constants.alpha_test[0] =
|
||||
|
@ -657,16 +754,32 @@ PipelineCache::UpdateStatus PipelineCache::UpdateShaderStages(
|
|||
bool dirty = false;
|
||||
dirty |= SetShadowRegister(®s.pa_su_sc_mode_cntl,
|
||||
XE_GPU_REG_PA_SU_SC_MODE_CNTL);
|
||||
dirty |= SetShadowRegister(®s.sq_program_cntl, XE_GPU_REG_SQ_PROGRAM_CNTL);
|
||||
dirty |= regs.vertex_shader != vertex_shader;
|
||||
dirty |= regs.pixel_shader != pixel_shader;
|
||||
dirty |= regs.primitive_type != primitive_type;
|
||||
regs.vertex_shader = vertex_shader;
|
||||
regs.pixel_shader = pixel_shader;
|
||||
regs.primitive_type = primitive_type;
|
||||
XXH64_update(&hash_state_, ®s, sizeof(regs));
|
||||
if (!dirty) {
|
||||
return UpdateStatus::kCompatible;
|
||||
}
|
||||
regs.vertex_shader = vertex_shader;
|
||||
regs.pixel_shader = pixel_shader;
|
||||
regs.primitive_type = primitive_type;
|
||||
|
||||
xenos::xe_gpu_program_cntl_t sq_program_cntl;
|
||||
sq_program_cntl.dword_0 = regs.sq_program_cntl;
|
||||
|
||||
if (!vertex_shader->is_translated() &&
|
||||
!TranslateShader(vertex_shader, sq_program_cntl)) {
|
||||
XELOGE("Failed to translate the vertex shader!");
|
||||
return UpdateStatus::kError;
|
||||
}
|
||||
|
||||
if (!pixel_shader->is_translated() &&
|
||||
!TranslateShader(pixel_shader, sq_program_cntl)) {
|
||||
XELOGE("Failed to translate the pixel shader!");
|
||||
return UpdateStatus::kError;
|
||||
}
|
||||
|
||||
update_shader_stages_stage_count_ = 0;
|
||||
|
||||
|
@ -723,11 +836,11 @@ PipelineCache::UpdateStatus PipelineCache::UpdateVertexInputState(
|
|||
|
||||
bool dirty = false;
|
||||
dirty |= vertex_shader != regs.vertex_shader;
|
||||
regs.vertex_shader = vertex_shader;
|
||||
XXH64_update(&hash_state_, ®s, sizeof(regs));
|
||||
if (!dirty) {
|
||||
return UpdateStatus::kCompatible;
|
||||
}
|
||||
regs.vertex_shader = vertex_shader;
|
||||
|
||||
state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
state_info.pNext = nullptr;
|
||||
|
@ -765,11 +878,14 @@ PipelineCache::UpdateStatus PipelineCache::UpdateVertexInputState(
|
|||
: VK_FORMAT_A2R10G10B10_UNORM_PACK32;
|
||||
break;
|
||||
case VertexFormat::k_10_11_11:
|
||||
assert_always("unsupported?");
|
||||
assert_true(is_signed);
|
||||
vertex_attrib_descr.format = VK_FORMAT_B10G11R11_UFLOAT_PACK32;
|
||||
break;
|
||||
case VertexFormat::k_11_11_10:
|
||||
assert_true(is_signed);
|
||||
// Converted in-shader.
|
||||
// TODO(DrChat)
|
||||
assert_always();
|
||||
// vertex_attrib_descr.format = VK_FORMAT_R32_UINT;
|
||||
vertex_attrib_descr.format = VK_FORMAT_B10G11R11_UFLOAT_PACK32;
|
||||
break;
|
||||
case VertexFormat::k_16_16:
|
||||
|
@ -802,19 +918,19 @@ PipelineCache::UpdateStatus PipelineCache::UpdateVertexInputState(
|
|||
is_signed ? VK_FORMAT_R32G32B32A32_SINT : VK_FORMAT_R32_UINT;
|
||||
break;
|
||||
case VertexFormat::k_32_FLOAT:
|
||||
assert_true(is_signed);
|
||||
// assert_true(is_signed);
|
||||
vertex_attrib_descr.format = VK_FORMAT_R32_SFLOAT;
|
||||
break;
|
||||
case VertexFormat::k_32_32_FLOAT:
|
||||
assert_true(is_signed);
|
||||
// assert_true(is_signed);
|
||||
vertex_attrib_descr.format = VK_FORMAT_R32G32_SFLOAT;
|
||||
break;
|
||||
case VertexFormat::k_32_32_32_FLOAT:
|
||||
assert_true(is_signed);
|
||||
// assert_true(is_signed);
|
||||
vertex_attrib_descr.format = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
break;
|
||||
case VertexFormat::k_32_32_32_32_FLOAT:
|
||||
assert_true(is_signed);
|
||||
// assert_true(is_signed);
|
||||
vertex_attrib_descr.format = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
break;
|
||||
default:
|
||||
|
@ -843,11 +959,11 @@ PipelineCache::UpdateStatus PipelineCache::UpdateInputAssemblyState(
|
|||
XE_GPU_REG_PA_SU_SC_MODE_CNTL);
|
||||
dirty |= SetShadowRegister(®s.multi_prim_ib_reset_index,
|
||||
XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX);
|
||||
regs.primitive_type = primitive_type;
|
||||
XXH64_update(&hash_state_, ®s, sizeof(regs));
|
||||
if (!dirty) {
|
||||
return UpdateStatus::kCompatible;
|
||||
}
|
||||
regs.primitive_type = primitive_type;
|
||||
|
||||
state_info.sType =
|
||||
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||
|
@ -934,14 +1050,17 @@ PipelineCache::UpdateStatus PipelineCache::UpdateRasterizationState(
|
|||
auto& state_info = update_rasterization_state_info_;
|
||||
|
||||
bool dirty = false;
|
||||
dirty |= regs.primitive_type != primitive_type;
|
||||
dirty |= SetShadowRegister(®s.pa_su_sc_mode_cntl,
|
||||
XE_GPU_REG_PA_SU_SC_MODE_CNTL);
|
||||
dirty |= SetShadowRegister(®s.pa_sc_screen_scissor_tl,
|
||||
XE_GPU_REG_PA_SC_SCREEN_SCISSOR_TL);
|
||||
dirty |= SetShadowRegister(®s.pa_sc_screen_scissor_br,
|
||||
XE_GPU_REG_PA_SC_SCREEN_SCISSOR_BR);
|
||||
dirty |= SetShadowRegister(®s.pa_sc_viz_query, XE_GPU_REG_PA_SC_VIZ_QUERY);
|
||||
dirty |= SetShadowRegister(®s.multi_prim_ib_reset_index,
|
||||
XE_GPU_REG_VGT_MULTI_PRIM_IB_RESET_INDX);
|
||||
regs.primitive_type = primitive_type;
|
||||
XXH64_update(&hash_state_, ®s, sizeof(regs));
|
||||
if (!dirty) {
|
||||
return UpdateStatus::kCompatible;
|
||||
|
@ -953,10 +1072,13 @@ PipelineCache::UpdateStatus PipelineCache::UpdateRasterizationState(
|
|||
|
||||
// TODO(benvanik): right setting?
|
||||
state_info.depthClampEnable = VK_FALSE;
|
||||
|
||||
// TODO(benvanik): use in depth-only mode?
|
||||
state_info.rasterizerDiscardEnable = VK_FALSE;
|
||||
|
||||
// KILL_PIX_POST_EARLY_Z
|
||||
if (regs.pa_sc_viz_query & 0x80) {
|
||||
state_info.rasterizerDiscardEnable = VK_TRUE;
|
||||
}
|
||||
|
||||
bool poly_mode = ((regs.pa_su_sc_mode_cntl >> 3) & 0x3) != 0;
|
||||
if (poly_mode) {
|
||||
uint32_t front_poly_mode = (regs.pa_su_sc_mode_cntl >> 5) & 0x7;
|
||||
|
@ -981,6 +1103,10 @@ PipelineCache::UpdateStatus PipelineCache::UpdateRasterizationState(
|
|||
case 2:
|
||||
state_info.cullMode = VK_CULL_MODE_BACK_BIT;
|
||||
break;
|
||||
case 3:
|
||||
// Cull both sides?
|
||||
assert_always();
|
||||
break;
|
||||
}
|
||||
if (regs.pa_su_sc_mode_cntl & 0x4) {
|
||||
state_info.frontFace = VK_FRONT_FACE_CLOCKWISE;
|
||||
|
@ -1007,18 +1133,53 @@ PipelineCache::UpdateStatus PipelineCache::UpdateMultisampleState() {
|
|||
auto& regs = update_multisample_state_regs_;
|
||||
auto& state_info = update_multisample_state_info_;
|
||||
|
||||
bool dirty = false;
|
||||
dirty |= SetShadowRegister(®s.pa_sc_aa_config, XE_GPU_REG_PA_SC_AA_CONFIG);
|
||||
dirty |= SetShadowRegister(®s.pa_su_sc_mode_cntl,
|
||||
XE_GPU_REG_PA_SU_SC_MODE_CNTL);
|
||||
dirty |= SetShadowRegister(®s.rb_surface_info, XE_GPU_REG_RB_SURFACE_INFO);
|
||||
XXH64_update(&hash_state_, ®s, sizeof(regs));
|
||||
if (!dirty) {
|
||||
return UpdateStatus::kCompatible;
|
||||
}
|
||||
|
||||
state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
state_info.pNext = nullptr;
|
||||
state_info.flags = 0;
|
||||
|
||||
state_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
// PA_SC_AA_CONFIG MSAA_NUM_SAMPLES (0x7)
|
||||
// PA_SC_AA_MASK (0xFFFF)
|
||||
// PA_SU_SC_MODE_CNTL MSAA_ENABLE (0x10000)
|
||||
// If set, all samples will be sampled at set locations. Otherwise, they're
|
||||
// all sampled from the pixel center.
|
||||
if (FLAGS_vulkan_native_msaa) {
|
||||
auto msaa_num_samples =
|
||||
static_cast<MsaaSamples>((regs.rb_surface_info >> 16) & 0x3);
|
||||
switch (msaa_num_samples) {
|
||||
case MsaaSamples::k1X:
|
||||
state_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
break;
|
||||
case MsaaSamples::k2X:
|
||||
state_info.rasterizationSamples = VK_SAMPLE_COUNT_2_BIT;
|
||||
break;
|
||||
case MsaaSamples::k4X:
|
||||
state_info.rasterizationSamples = VK_SAMPLE_COUNT_4_BIT;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(msaa_num_samples);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
state_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
}
|
||||
|
||||
state_info.sampleShadingEnable = VK_FALSE;
|
||||
state_info.minSampleShading = 0;
|
||||
state_info.pSampleMask = nullptr;
|
||||
state_info.alphaToCoverageEnable = VK_FALSE;
|
||||
state_info.alphaToOneEnable = VK_FALSE;
|
||||
|
||||
return UpdateStatus::kCompatible;
|
||||
return UpdateStatus::kMismatch;
|
||||
}
|
||||
|
||||
PipelineCache::UpdateStatus PipelineCache::UpdateDepthStencilState() {
|
||||
|
@ -1038,19 +1199,60 @@ PipelineCache::UpdateStatus PipelineCache::UpdateDepthStencilState() {
|
|||
state_info.pNext = nullptr;
|
||||
state_info.flags = 0;
|
||||
|
||||
state_info.depthTestEnable = VK_FALSE;
|
||||
state_info.depthWriteEnable = VK_FALSE;
|
||||
state_info.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
||||
static const VkCompareOp compare_func_map[] = {
|
||||
/* 0 */ VK_COMPARE_OP_NEVER,
|
||||
/* 1 */ VK_COMPARE_OP_LESS,
|
||||
/* 2 */ VK_COMPARE_OP_EQUAL,
|
||||
/* 3 */ VK_COMPARE_OP_LESS_OR_EQUAL,
|
||||
/* 4 */ VK_COMPARE_OP_GREATER,
|
||||
/* 5 */ VK_COMPARE_OP_NOT_EQUAL,
|
||||
/* 6 */ VK_COMPARE_OP_GREATER_OR_EQUAL,
|
||||
/* 7 */ VK_COMPARE_OP_ALWAYS,
|
||||
};
|
||||
static const VkStencilOp stencil_op_map[] = {
|
||||
/* 0 */ VK_STENCIL_OP_KEEP,
|
||||
/* 1 */ VK_STENCIL_OP_ZERO,
|
||||
/* 2 */ VK_STENCIL_OP_REPLACE,
|
||||
/* 3 */ VK_STENCIL_OP_INCREMENT_AND_WRAP,
|
||||
/* 4 */ VK_STENCIL_OP_DECREMENT_AND_WRAP,
|
||||
/* 5 */ VK_STENCIL_OP_INVERT,
|
||||
/* 6 */ VK_STENCIL_OP_INCREMENT_AND_CLAMP,
|
||||
/* 7 */ VK_STENCIL_OP_DECREMENT_AND_CLAMP,
|
||||
};
|
||||
|
||||
// Depth state
|
||||
// TODO: EARLY_Z_ENABLE (needs to be enabled in shaders)
|
||||
state_info.depthWriteEnable = !!(regs.rb_depthcontrol & 0x4);
|
||||
state_info.depthTestEnable = !!(regs.rb_depthcontrol & 0x2);
|
||||
state_info.stencilTestEnable = !!(regs.rb_depthcontrol & 0x1);
|
||||
|
||||
state_info.depthCompareOp =
|
||||
compare_func_map[(regs.rb_depthcontrol >> 4) & 0x7];
|
||||
state_info.depthBoundsTestEnable = VK_FALSE;
|
||||
state_info.stencilTestEnable = VK_FALSE;
|
||||
state_info.front.failOp = VK_STENCIL_OP_KEEP;
|
||||
state_info.front.passOp = VK_STENCIL_OP_KEEP;
|
||||
state_info.front.depthFailOp = VK_STENCIL_OP_KEEP;
|
||||
state_info.front.compareOp = VK_COMPARE_OP_ALWAYS;
|
||||
state_info.back.failOp = VK_STENCIL_OP_KEEP;
|
||||
state_info.back.passOp = VK_STENCIL_OP_KEEP;
|
||||
state_info.back.depthFailOp = VK_STENCIL_OP_KEEP;
|
||||
state_info.back.compareOp = VK_COMPARE_OP_ALWAYS;
|
||||
|
||||
uint32_t stencil_ref = (regs.rb_stencilrefmask & 0x000000FF);
|
||||
uint32_t stencil_read_mask = (regs.rb_stencilrefmask & 0x0000FF00) >> 8;
|
||||
|
||||
// Stencil state
|
||||
state_info.front.compareOp =
|
||||
compare_func_map[(regs.rb_depthcontrol >> 8) & 0x7];
|
||||
state_info.front.failOp = stencil_op_map[(regs.rb_depthcontrol >> 11) & 0x7];
|
||||
state_info.front.passOp = stencil_op_map[(regs.rb_depthcontrol >> 14) & 0x7];
|
||||
state_info.front.depthFailOp =
|
||||
stencil_op_map[(regs.rb_depthcontrol >> 17) & 0x7];
|
||||
|
||||
// BACKFACE_ENABLE
|
||||
if (!!(regs.rb_depthcontrol & 0x80)) {
|
||||
state_info.back.compareOp =
|
||||
compare_func_map[(regs.rb_depthcontrol >> 20) & 0x7];
|
||||
state_info.back.failOp = stencil_op_map[(regs.rb_depthcontrol >> 23) & 0x7];
|
||||
state_info.back.passOp = stencil_op_map[(regs.rb_depthcontrol >> 26) & 0x7];
|
||||
state_info.back.depthFailOp =
|
||||
stencil_op_map[(regs.rb_depthcontrol >> 29) & 0x7];
|
||||
} else {
|
||||
// Back state is identical to front state.
|
||||
std::memcpy(&state_info.back, &state_info.front, sizeof(VkStencilOpState));
|
||||
}
|
||||
|
||||
// Ignored; set dynamically.
|
||||
state_info.minDepthBounds = 0;
|
||||
|
@ -1089,6 +1291,7 @@ PipelineCache::UpdateStatus PipelineCache::UpdateColorBlendState() {
|
|||
SetShadowRegister(®s.rb_blendcontrol[2], XE_GPU_REG_RB_BLENDCONTROL_2);
|
||||
dirty |=
|
||||
SetShadowRegister(®s.rb_blendcontrol[3], XE_GPU_REG_RB_BLENDCONTROL_3);
|
||||
dirty |= SetShadowRegister(®s.rb_modecontrol, XE_GPU_REG_RB_MODECONTROL);
|
||||
XXH64_update(&hash_state_, ®s, sizeof(regs));
|
||||
if (!dirty) {
|
||||
return UpdateStatus::kCompatible;
|
||||
|
@ -1101,6 +1304,8 @@ PipelineCache::UpdateStatus PipelineCache::UpdateColorBlendState() {
|
|||
state_info.logicOpEnable = VK_FALSE;
|
||||
state_info.logicOp = VK_LOGIC_OP_NO_OP;
|
||||
|
||||
auto enable_mode = static_cast<xenos::ModeControl>(regs.rb_modecontrol & 0x7);
|
||||
|
||||
static const VkBlendFactor kBlendFactorMap[] = {
|
||||
/* 0 */ VK_BLEND_FACTOR_ZERO,
|
||||
/* 1 */ VK_BLEND_FACTOR_ONE,
|
||||
|
@ -1153,7 +1358,8 @@ PipelineCache::UpdateStatus PipelineCache::UpdateColorBlendState() {
|
|||
// A2XX_RB_COLOR_MASK_WRITE_* == D3DRS_COLORWRITEENABLE
|
||||
// Lines up with VkColorComponentFlagBits, where R=bit 1, G=bit 2, etc..
|
||||
uint32_t write_mask = (regs.rb_color_mask >> (i * 4)) & 0xF;
|
||||
attachment_state.colorWriteMask = write_mask;
|
||||
attachment_state.colorWriteMask =
|
||||
enable_mode == xenos::ModeControl::kColorDepth ? write_mask : 0;
|
||||
}
|
||||
|
||||
state_info.attachmentCount = 4;
|
||||
|
|
|
@ -32,6 +32,12 @@ namespace vulkan {
|
|||
// including shaders, various blend/etc options, and input configuration.
|
||||
class PipelineCache {
|
||||
public:
|
||||
enum class UpdateStatus {
|
||||
kCompatible,
|
||||
kMismatch,
|
||||
kError,
|
||||
};
|
||||
|
||||
PipelineCache(RegisterFile* register_file, ui::vulkan::VulkanDevice* device,
|
||||
VkDescriptorSetLayout uniform_descriptor_set_layout,
|
||||
VkDescriptorSetLayout texture_descriptor_set_layout);
|
||||
|
@ -46,11 +52,17 @@ class PipelineCache {
|
|||
// 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.
|
||||
bool ConfigurePipeline(VkCommandBuffer command_buffer,
|
||||
const RenderState* render_state,
|
||||
VulkanShader* vertex_shader,
|
||||
VulkanShader* pixel_shader,
|
||||
PrimitiveType primitive_type);
|
||||
UpdateStatus ConfigurePipeline(VkCommandBuffer command_buffer,
|
||||
const RenderState* render_state,
|
||||
VulkanShader* vertex_shader,
|
||||
VulkanShader* pixel_shader,
|
||||
PrimitiveType primitive_type,
|
||||
VkPipeline* pipeline_out);
|
||||
|
||||
// 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_; }
|
||||
|
@ -63,16 +75,14 @@ class PipelineCache {
|
|||
// state.
|
||||
VkPipeline GetPipeline(const RenderState* render_state, uint64_t hash_key);
|
||||
|
||||
bool TranslateShader(VulkanShader* shader, xenos::xe_gpu_program_cntl_t cntl);
|
||||
void DumpShaderDisasmNV(const VkGraphicsPipelineCreateInfo& info);
|
||||
|
||||
// Gets a geometry shader used to emulate the given primitive type.
|
||||
// Returns nullptr if the primitive doesn't need to be emulated.
|
||||
VkShaderModule GetGeometryShader(PrimitiveType primitive_type,
|
||||
bool is_line_mode);
|
||||
|
||||
// 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);
|
||||
|
||||
RegisterFile* register_file_ = nullptr;
|
||||
VkDevice device_ = nullptr;
|
||||
|
||||
|
@ -111,12 +121,6 @@ class PipelineCache {
|
|||
VkPipeline current_pipeline_ = nullptr;
|
||||
|
||||
private:
|
||||
enum class UpdateStatus {
|
||||
kCompatible,
|
||||
kMismatch,
|
||||
kError,
|
||||
};
|
||||
|
||||
UpdateStatus UpdateState(VulkanShader* vertex_shader,
|
||||
VulkanShader* pixel_shader,
|
||||
PrimitiveType primitive_type);
|
||||
|
@ -154,6 +158,7 @@ class PipelineCache {
|
|||
struct UpdateShaderStagesRegisters {
|
||||
PrimitiveType primitive_type;
|
||||
uint32_t pa_su_sc_mode_cntl;
|
||||
uint32_t sq_program_cntl;
|
||||
VulkanShader* vertex_shader;
|
||||
VulkanShader* pixel_shader;
|
||||
|
||||
|
@ -205,11 +210,12 @@ class PipelineCache {
|
|||
VkPipelineViewportStateCreateInfo update_viewport_state_info_;
|
||||
|
||||
struct UpdateRasterizationStateRegisters {
|
||||
PrimitiveType primitive_type;
|
||||
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 multi_prim_ib_reset_index;
|
||||
PrimitiveType prim_type;
|
||||
|
||||
UpdateRasterizationStateRegisters() { Reset(); }
|
||||
void Reset() { std::memset(this, 0, sizeof(*this)); }
|
||||
|
@ -217,6 +223,10 @@ class PipelineCache {
|
|||
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_;
|
||||
|
@ -235,6 +245,7 @@ class PipelineCache {
|
|||
uint32_t rb_colorcontrol;
|
||||
uint32_t rb_color_mask;
|
||||
uint32_t rb_blendcontrol[4];
|
||||
uint32_t rb_modecontrol;
|
||||
|
||||
UpdateColorBlendStateRegisters() { Reset(); }
|
||||
void Reset() { std::memset(this, 0, sizeof(*this)); }
|
||||
|
|
|
@ -39,7 +39,7 @@ VkFormat ColorRenderTargetFormatToVkFormat(ColorRenderTargetFormat format) {
|
|||
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT_unknown:
|
||||
// WARNING: this is wrong, most likely - no float form in vulkan?
|
||||
XELOGW("Unsupported EDRAM format k_2_10_10_10_FLOAT used");
|
||||
return VK_FORMAT_A2R10G10B10_SSCALED_PACK32;
|
||||
return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
|
||||
case ColorRenderTargetFormat::k_16_16:
|
||||
return VK_FORMAT_R16G16_UNORM;
|
||||
case ColorRenderTargetFormat::k_16_16_16_16:
|
||||
|
@ -71,34 +71,6 @@ VkFormat DepthRenderTargetFormatToVkFormat(DepthRenderTargetFormat format) {
|
|||
}
|
||||
}
|
||||
|
||||
// Cached view into the EDRAM memory.
|
||||
// The image is aliased to a region of the edram_memory_ based on the tile
|
||||
// parameters.
|
||||
// TODO(benvanik): reuse VkImage's with multiple VkViews for compatible
|
||||
// formats?
|
||||
class CachedTileView {
|
||||
public:
|
||||
// Key identifying the view in the cache.
|
||||
TileViewKey key;
|
||||
// Image mapped into EDRAM.
|
||||
VkImage image = nullptr;
|
||||
// Simple view on the image matching the format.
|
||||
VkImageView image_view = nullptr;
|
||||
|
||||
CachedTileView(VkDevice device, VkDeviceMemory edram_memory,
|
||||
TileViewKey view_key);
|
||||
~CachedTileView();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice device_ = nullptr;
|
||||
};
|
||||
|
||||
// Cached framebuffer referencing tile attachments.
|
||||
// Each framebuffer is specific to a render pass. Ugh.
|
||||
class CachedFramebuffer {
|
||||
|
@ -151,9 +123,11 @@ class CachedRenderPass {
|
|||
VkDevice device_ = nullptr;
|
||||
};
|
||||
|
||||
CachedTileView::CachedTileView(VkDevice device, VkDeviceMemory edram_memory,
|
||||
CachedTileView::CachedTileView(ui::vulkan::VulkanDevice* device,
|
||||
VkCommandBuffer command_buffer,
|
||||
VkDeviceMemory edram_memory,
|
||||
TileViewKey view_key)
|
||||
: device_(device), key(std::move(view_key)) {
|
||||
: device_(*device), key(std::move(view_key)) {
|
||||
// Map format to Vulkan.
|
||||
VkFormat vulkan_format = VK_FORMAT_UNDEFINED;
|
||||
uint32_t bpp = 4;
|
||||
|
@ -175,7 +149,8 @@ CachedTileView::CachedTileView(VkDevice device, VkDeviceMemory edram_memory,
|
|||
vulkan_format = DepthRenderTargetFormatToVkFormat(edram_format);
|
||||
}
|
||||
assert_true(vulkan_format != VK_FORMAT_UNDEFINED);
|
||||
assert_true(bpp == 4);
|
||||
// FIXME(DrChat): Was this check necessary?
|
||||
// assert_true(bpp == 4);
|
||||
|
||||
// Create the image with the desired properties.
|
||||
VkImageCreateInfo image_info;
|
||||
|
@ -191,8 +166,25 @@ CachedTileView::CachedTileView(VkDevice device, VkDeviceMemory edram_memory,
|
|||
image_info.extent.depth = 1;
|
||||
image_info.mipLevels = 1;
|
||||
image_info.arrayLayers = 1;
|
||||
// TODO(benvanik): native MSAA support?
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
if (FLAGS_vulkan_native_msaa) {
|
||||
auto msaa_samples = static_cast<MsaaSamples>(key.msaa_samples);
|
||||
switch (msaa_samples) {
|
||||
case MsaaSamples::k1X:
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
break;
|
||||
case MsaaSamples::k2X:
|
||||
image_info.samples = VK_SAMPLE_COUNT_2_BIT;
|
||||
break;
|
||||
case MsaaSamples::k4X:
|
||||
image_info.samples = VK_SAMPLE_COUNT_4_BIT;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(msaa_samples);
|
||||
}
|
||||
} else {
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
}
|
||||
sample_count = image_info.samples;
|
||||
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
|
@ -203,19 +195,17 @@ CachedTileView::CachedTileView(VkDevice device, VkDeviceMemory edram_memory,
|
|||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_info.queueFamilyIndexCount = 0;
|
||||
image_info.pQueueFamilyIndices = nullptr;
|
||||
image_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
|
||||
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
auto err = vkCreateImage(device_, &image_info, nullptr, &image);
|
||||
CheckResult(err, "vkCreateImage");
|
||||
|
||||
// Verify our assumptions about memory layout are correct.
|
||||
VkDeviceSize edram_offset = key.tile_offset * 5120;
|
||||
VkMemoryRequirements memory_requirements;
|
||||
vkGetImageMemoryRequirements(device, image, &memory_requirements);
|
||||
assert_true(edram_offset + memory_requirements.size <= kEdramBufferCapacity);
|
||||
assert_true(edram_offset % memory_requirements.alignment == 0);
|
||||
vkGetImageMemoryRequirements(*device, image, &memory_requirements);
|
||||
|
||||
// Bind to the region of EDRAM we occupy.
|
||||
err = vkBindImageMemory(device_, image, edram_memory, edram_offset);
|
||||
// Bind to a newly allocated chunk.
|
||||
// TODO: Alias from a really big buffer?
|
||||
memory = device->AllocateMemory(memory_requirements, 0);
|
||||
err = vkBindImageMemory(device_, image, memory, 0);
|
||||
CheckResult(err, "vkBindImageMemory");
|
||||
|
||||
// Create the image view we'll use to attach it to a framebuffer.
|
||||
|
@ -242,11 +232,37 @@ CachedTileView::CachedTileView(VkDevice device, VkDeviceMemory edram_memory,
|
|||
CheckResult(err, "vkCreateImageView");
|
||||
|
||||
// TODO(benvanik): transition to general layout?
|
||||
VkImageMemoryBarrier image_barrier;
|
||||
image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
image_barrier.pNext = nullptr;
|
||||
image_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
|
||||
image_barrier.dstAccessMask =
|
||||
key.color_or_depth ? VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
|
||||
: VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
image_barrier.dstAccessMask |=
|
||||
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.image = image;
|
||||
image_barrier.subresourceRange.aspectMask =
|
||||
key.color_or_depth
|
||||
? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
image_barrier.subresourceRange.baseMipLevel = 0;
|
||||
image_barrier.subresourceRange.levelCount = 1;
|
||||
image_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
image_barrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &image_barrier);
|
||||
}
|
||||
|
||||
CachedTileView::~CachedTileView() {
|
||||
vkDestroyImageView(device_, image_view, nullptr);
|
||||
vkDestroyImage(device_, image, nullptr);
|
||||
vkFreeMemory(device_, memory, nullptr);
|
||||
}
|
||||
|
||||
CachedFramebuffer::CachedFramebuffer(
|
||||
|
@ -293,8 +309,15 @@ bool CachedFramebuffer::IsCompatible(
|
|||
const RenderConfiguration& desired_config) const {
|
||||
// We already know all render pass things line up, so let's verify dimensions,
|
||||
// edram offsets, etc. We need an exact match.
|
||||
if (desired_config.surface_pitch_px != width ||
|
||||
desired_config.surface_height_px != height) {
|
||||
uint32_t surface_pitch_px = desired_config.surface_msaa != MsaaSamples::k4X
|
||||
? desired_config.surface_pitch_px
|
||||
: desired_config.surface_pitch_px * 2;
|
||||
uint32_t surface_height_px = desired_config.surface_msaa == MsaaSamples::k1X
|
||||
? desired_config.surface_height_px
|
||||
: desired_config.surface_height_px * 2;
|
||||
surface_pitch_px = std::min(surface_pitch_px, 2560u);
|
||||
surface_height_px = std::min(surface_height_px, 2560u);
|
||||
if (surface_pitch_px != width || surface_height_px != height) {
|
||||
return false;
|
||||
}
|
||||
// TODO(benvanik): separate image views from images in tiles and store in fb?
|
||||
|
@ -327,13 +350,33 @@ CachedRenderPass::CachedRenderPass(VkDevice device,
|
|||
: device_(device) {
|
||||
std::memcpy(&config, &desired_config, sizeof(config));
|
||||
|
||||
VkSampleCountFlagBits sample_count;
|
||||
if (FLAGS_vulkan_native_msaa) {
|
||||
switch (desired_config.surface_msaa) {
|
||||
case MsaaSamples::k1X:
|
||||
sample_count = VK_SAMPLE_COUNT_1_BIT;
|
||||
break;
|
||||
case MsaaSamples::k2X:
|
||||
sample_count = VK_SAMPLE_COUNT_2_BIT;
|
||||
break;
|
||||
case MsaaSamples::k4X:
|
||||
sample_count = VK_SAMPLE_COUNT_4_BIT;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(desired_config.surface_msaa);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
sample_count = VK_SAMPLE_COUNT_1_BIT;
|
||||
}
|
||||
|
||||
// Initialize all attachments to default unused.
|
||||
// As we set layout(location=RT) in shaders we must always provide 4.
|
||||
VkAttachmentDescription attachments[5];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
attachments[i].flags = 0;
|
||||
attachments[i].format = VK_FORMAT_UNDEFINED;
|
||||
attachments[i].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[i].samples = sample_count;
|
||||
attachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
attachments[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
|
@ -344,7 +387,7 @@ CachedRenderPass::CachedRenderPass(VkDevice device,
|
|||
auto& depth_stencil_attachment = attachments[4];
|
||||
depth_stencil_attachment.flags = 0;
|
||||
depth_stencil_attachment.format = VK_FORMAT_UNDEFINED;
|
||||
depth_stencil_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
depth_stencil_attachment.samples = sample_count;
|
||||
depth_stencil_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
depth_stencil_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
depth_stencil_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
|
@ -409,6 +452,11 @@ CachedRenderPass::~CachedRenderPass() {
|
|||
|
||||
bool CachedRenderPass::IsCompatible(
|
||||
const RenderConfiguration& desired_config) const {
|
||||
if (config.surface_msaa != desired_config.surface_msaa &&
|
||||
FLAGS_vulkan_native_msaa) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
// TODO(benvanik): allow compatible vulkan formats.
|
||||
if (config.color[i].format != desired_config.color[i].format) {
|
||||
|
@ -423,9 +471,10 @@ bool CachedRenderPass::IsCompatible(
|
|||
|
||||
RenderCache::RenderCache(RegisterFile* register_file,
|
||||
ui::vulkan::VulkanDevice* device)
|
||||
: register_file_(register_file), device_(*device) {
|
||||
: register_file_(register_file), device_(device) {
|
||||
VkResult status = VK_SUCCESS;
|
||||
|
||||
// Create the buffer we'll bind to our memory.
|
||||
// We do this first so we can get the right memory type.
|
||||
VkBufferCreateInfo buffer_info;
|
||||
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
buffer_info.pNext = nullptr;
|
||||
|
@ -436,55 +485,39 @@ RenderCache::RenderCache(RegisterFile* register_file,
|
|||
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
buffer_info.queueFamilyIndexCount = 0;
|
||||
buffer_info.pQueueFamilyIndices = nullptr;
|
||||
auto err = vkCreateBuffer(*device, &buffer_info, nullptr, &edram_buffer_);
|
||||
CheckResult(err, "vkCreateBuffer");
|
||||
status = vkCreateBuffer(*device, &buffer_info, nullptr, &edram_buffer_);
|
||||
CheckResult(status, "vkCreateBuffer");
|
||||
|
||||
// Query requirements for the buffer.
|
||||
// It should be 1:1.
|
||||
VkMemoryRequirements buffer_requirements;
|
||||
vkGetBufferMemoryRequirements(device_, edram_buffer_, &buffer_requirements);
|
||||
vkGetBufferMemoryRequirements(*device_, edram_buffer_, &buffer_requirements);
|
||||
assert_true(buffer_requirements.size == kEdramBufferCapacity);
|
||||
|
||||
// Create a dummy image so we can see what memory bits it requires.
|
||||
// They should overlap with the buffer requirements but are likely more
|
||||
// strict.
|
||||
VkImageCreateInfo test_image_info;
|
||||
test_image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
test_image_info.pNext = nullptr;
|
||||
test_image_info.flags = 0;
|
||||
test_image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
test_image_info.format = VK_FORMAT_R8G8B8A8_UINT;
|
||||
test_image_info.extent.width = 128;
|
||||
test_image_info.extent.height = 128;
|
||||
test_image_info.extent.depth = 1;
|
||||
test_image_info.mipLevels = 1;
|
||||
test_image_info.arrayLayers = 1;
|
||||
test_image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
test_image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
test_image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
test_image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
test_image_info.queueFamilyIndexCount = 0;
|
||||
test_image_info.pQueueFamilyIndices = nullptr;
|
||||
test_image_info.initialLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
VkImage test_image = nullptr;
|
||||
err = vkCreateImage(device_, &test_image_info, nullptr, &test_image);
|
||||
CheckResult(err, "vkCreateImage");
|
||||
VkMemoryRequirements image_requirements;
|
||||
vkGetImageMemoryRequirements(device_, test_image, &image_requirements);
|
||||
vkDestroyImage(device_, test_image, nullptr);
|
||||
assert_true((image_requirements.memoryTypeBits &
|
||||
buffer_requirements.memoryTypeBits) != 0);
|
||||
|
||||
// Allocate EDRAM memory.
|
||||
VkMemoryRequirements memory_requirements;
|
||||
memory_requirements.size = buffer_requirements.size;
|
||||
memory_requirements.alignment = buffer_requirements.alignment;
|
||||
memory_requirements.memoryTypeBits = image_requirements.memoryTypeBits;
|
||||
// TODO(benvanik): do we need it host visible?
|
||||
edram_memory_ = device->AllocateMemory(memory_requirements, 0);
|
||||
edram_memory_ = device->AllocateMemory(buffer_requirements);
|
||||
assert_not_null(edram_memory_);
|
||||
|
||||
// Bind buffer to map our entire memory.
|
||||
vkBindBufferMemory(device_, edram_buffer_, edram_memory_, 0);
|
||||
status = vkBindBufferMemory(*device_, edram_buffer_, edram_memory_, 0);
|
||||
CheckResult(status, "vkBindBufferMemory");
|
||||
|
||||
if (status == VK_SUCCESS) {
|
||||
// For debugging, upload a grid into the EDRAM buffer.
|
||||
uint32_t* gpu_data = nullptr;
|
||||
status = vkMapMemory(*device_, edram_memory_, 0, buffer_requirements.size,
|
||||
0, reinterpret_cast<void**>(&gpu_data));
|
||||
CheckResult(status, "vkMapMemory");
|
||||
|
||||
if (status == VK_SUCCESS) {
|
||||
for (int i = 0; i < kEdramBufferCapacity / 4; i++) {
|
||||
gpu_data[i] = (i % 8) >= 4 ? 0xFF0000FF : 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
vkUnmapMemory(*device_, edram_memory_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RenderCache::~RenderCache() {
|
||||
|
@ -503,13 +536,36 @@ RenderCache::~RenderCache() {
|
|||
cached_tile_views_.clear();
|
||||
|
||||
// Release underlying EDRAM memory.
|
||||
vkDestroyBuffer(device_, edram_buffer_, nullptr);
|
||||
vkFreeMemory(device_, edram_memory_, nullptr);
|
||||
vkDestroyBuffer(*device_, edram_buffer_, nullptr);
|
||||
vkFreeMemory(*device_, edram_memory_, nullptr);
|
||||
}
|
||||
|
||||
bool RenderCache::dirty() const {
|
||||
auto& regs = *register_file_;
|
||||
auto& cur_regs = shadow_registers_;
|
||||
|
||||
bool dirty = false;
|
||||
dirty |= cur_regs.rb_modecontrol != regs[XE_GPU_REG_RB_MODECONTROL].u32;
|
||||
dirty |= cur_regs.rb_surface_info != regs[XE_GPU_REG_RB_SURFACE_INFO].u32;
|
||||
dirty |= cur_regs.rb_color_info != regs[XE_GPU_REG_RB_COLOR_INFO].u32;
|
||||
dirty |= cur_regs.rb_color1_info != regs[XE_GPU_REG_RB_COLOR1_INFO].u32;
|
||||
dirty |= cur_regs.rb_color2_info != regs[XE_GPU_REG_RB_COLOR2_INFO].u32;
|
||||
dirty |= cur_regs.rb_color3_info != regs[XE_GPU_REG_RB_COLOR3_INFO].u32;
|
||||
dirty |= cur_regs.rb_depth_info != regs[XE_GPU_REG_RB_DEPTH_INFO].u32;
|
||||
dirty |= cur_regs.pa_sc_window_scissor_tl !=
|
||||
regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_TL].u32;
|
||||
dirty |= cur_regs.pa_sc_window_scissor_br !=
|
||||
regs[XE_GPU_REG_PA_SC_WINDOW_SCISSOR_BR].u32;
|
||||
return dirty;
|
||||
}
|
||||
|
||||
const RenderState* RenderCache::BeginRenderPass(VkCommandBuffer command_buffer,
|
||||
VulkanShader* vertex_shader,
|
||||
VulkanShader* pixel_shader) {
|
||||
#if FINE_GRAINED_DRAW_SCOPES
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
#endif // FINE_GRAINED_DRAW_SCOPES
|
||||
|
||||
assert_null(current_command_buffer_);
|
||||
current_command_buffer_ = command_buffer;
|
||||
|
||||
|
@ -542,13 +598,34 @@ const RenderState* RenderCache::BeginRenderPass(VkCommandBuffer command_buffer,
|
|||
}
|
||||
|
||||
// Lookup or generate a new render pass and framebuffer for the new state.
|
||||
if (!ConfigureRenderPass(config, &render_pass, &framebuffer)) {
|
||||
if (!ConfigureRenderPass(command_buffer, config, &render_pass,
|
||||
&framebuffer)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
current_state_.render_pass = render_pass;
|
||||
current_state_.render_pass_handle = render_pass->handle;
|
||||
current_state_.framebuffer = framebuffer;
|
||||
current_state_.framebuffer_handle = framebuffer->handle;
|
||||
|
||||
// TODO(DrChat): Determine if we actually need an EDRAM buffer.
|
||||
/*
|
||||
// Depth
|
||||
auto depth_target = current_state_.framebuffer->depth_stencil_attachment;
|
||||
if (depth_target && current_state_.config.depth_stencil.used) {
|
||||
UpdateTileView(command_buffer, depth_target, true);
|
||||
}
|
||||
|
||||
// Color
|
||||
for (int i = 0; i < 4; i++) {
|
||||
auto target = current_state_.framebuffer->color_attachments[i];
|
||||
if (!target || !current_state_.config.color[i].used) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateTileView(command_buffer, target, true);
|
||||
}
|
||||
*/
|
||||
}
|
||||
if (!render_pass) {
|
||||
return nullptr;
|
||||
|
@ -571,6 +648,15 @@ const RenderState* RenderCache::BeginRenderPass(VkCommandBuffer command_buffer,
|
|||
render_pass_begin_info.renderArea.extent.width = config->surface_pitch_px;
|
||||
render_pass_begin_info.renderArea.extent.height = config->surface_height_px;
|
||||
|
||||
if (config->surface_msaa == MsaaSamples::k2X) {
|
||||
render_pass_begin_info.renderArea.extent.height =
|
||||
std::min(config->surface_height_px * 2, 2560u);
|
||||
} else if (config->surface_msaa == MsaaSamples::k4X) {
|
||||
render_pass_begin_info.renderArea.extent.width *= 2;
|
||||
render_pass_begin_info.renderArea.extent.height =
|
||||
std::min(config->surface_height_px * 2, 2560u);
|
||||
}
|
||||
|
||||
// Configure clear color, if clearing.
|
||||
// TODO(benvanik): enable clearing here during resolve?
|
||||
render_pass_begin_info.clearValueCount = 0;
|
||||
|
@ -601,9 +687,15 @@ bool RenderCache::ParseConfiguration(RenderConfiguration* config) {
|
|||
// Guess the height from the scissor height.
|
||||
// It's wildly inaccurate, but I've never seen it be bigger than the
|
||||
// EDRAM tiling.
|
||||
/*
|
||||
uint32_t ws_y = (regs.pa_sc_window_scissor_tl >> 16) & 0x7FFF;
|
||||
uint32_t ws_h = ((regs.pa_sc_window_scissor_br >> 16) & 0x7FFF) - ws_y;
|
||||
config->surface_height_px = std::min(2560u, xe::round_up(ws_h, 16));
|
||||
*/
|
||||
|
||||
// TODO(DrChat): Find an accurate way to get the surface height. Until we do,
|
||||
// we're going to hardcode it to 2560, as that's the absolute maximum.
|
||||
config->surface_height_px = 2560;
|
||||
|
||||
// Color attachment configuration.
|
||||
if (config->mode_control == ModeControl::kColorDepth) {
|
||||
|
@ -620,12 +712,23 @@ bool RenderCache::ParseConfiguration(RenderConfiguration* config) {
|
|||
case ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
config->color[i].format = ColorRenderTargetFormat::k_8_8_8_8;
|
||||
break;
|
||||
case ColorRenderTargetFormat::k_2_10_10_10_unknown:
|
||||
config->color[i].format = ColorRenderTargetFormat::k_2_10_10_10;
|
||||
break;
|
||||
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT_unknown:
|
||||
config->color[i].format = ColorRenderTargetFormat::k_2_10_10_10_FLOAT;
|
||||
break;
|
||||
}
|
||||
|
||||
// Make sure all unknown bits are unset.
|
||||
// RDR sets bit 0x00400000
|
||||
// assert_zero(color_info[i] & ~0x000F0FFF);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
config->color[i].edram_base = 0;
|
||||
config->color[i].format = ColorRenderTargetFormat::k_8_8_8_8;
|
||||
config->color[i].used = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,15 +738,20 @@ bool RenderCache::ParseConfiguration(RenderConfiguration* config) {
|
|||
config->depth_stencil.edram_base = regs.rb_depth_info & 0xFFF;
|
||||
config->depth_stencil.format =
|
||||
static_cast<DepthRenderTargetFormat>((regs.rb_depth_info >> 16) & 0x1);
|
||||
|
||||
// Make sure all unknown bits are unset.
|
||||
// assert_zero(regs.rb_depth_info & ~0x00010FFF);
|
||||
} else {
|
||||
config->depth_stencil.edram_base = 0;
|
||||
config->depth_stencil.format = DepthRenderTargetFormat::kD24S8;
|
||||
config->depth_stencil.used = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderCache::ConfigureRenderPass(RenderConfiguration* config,
|
||||
bool RenderCache::ConfigureRenderPass(VkCommandBuffer command_buffer,
|
||||
RenderConfiguration* config,
|
||||
CachedRenderPass** out_render_pass,
|
||||
CachedFramebuffer** out_framebuffer) {
|
||||
*out_render_pass = nullptr;
|
||||
|
@ -662,7 +770,7 @@ bool RenderCache::ConfigureRenderPass(RenderConfiguration* config,
|
|||
|
||||
// If no render pass was found in the cache create a new one.
|
||||
if (!render_pass) {
|
||||
render_pass = new CachedRenderPass(device_, *config);
|
||||
render_pass = new CachedRenderPass(*device_, *config);
|
||||
cached_render_passes_.push_back(render_pass);
|
||||
}
|
||||
|
||||
|
@ -679,16 +787,25 @@ bool RenderCache::ConfigureRenderPass(RenderConfiguration* config,
|
|||
|
||||
// If no framebuffer was found in the cache create a new one.
|
||||
if (!framebuffer) {
|
||||
uint32_t tile_width = config->surface_msaa == MsaaSamples::k4X ? 40 : 80;
|
||||
uint32_t tile_height = config->surface_msaa != MsaaSamples::k1X ? 8 : 16;
|
||||
|
||||
CachedTileView* target_color_attachments[4] = {nullptr, nullptr, nullptr,
|
||||
nullptr};
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
TileViewKey color_key;
|
||||
color_key.tile_offset = config->color[i].edram_base;
|
||||
color_key.tile_width = config->surface_pitch_px / 80;
|
||||
color_key.tile_height = config->surface_height_px / 16;
|
||||
color_key.tile_width =
|
||||
xe::round_up(config->surface_pitch_px, tile_width) / tile_width;
|
||||
// color_key.tile_height =
|
||||
// xe::round_up(config->surface_height_px, tile_height) / tile_height;
|
||||
color_key.tile_height = 160;
|
||||
color_key.color_or_depth = 1;
|
||||
color_key.msaa_samples =
|
||||
0; // static_cast<uint16_t>(config->surface_msaa);
|
||||
color_key.edram_format = static_cast<uint16_t>(config->color[i].format);
|
||||
target_color_attachments[i] = GetTileView(color_key);
|
||||
target_color_attachments[i] =
|
||||
FindOrCreateTileView(command_buffer, color_key);
|
||||
if (!target_color_attachments) {
|
||||
XELOGE("Failed to get tile view for color attachment");
|
||||
return false;
|
||||
|
@ -697,21 +814,34 @@ bool RenderCache::ConfigureRenderPass(RenderConfiguration* config,
|
|||
|
||||
TileViewKey depth_stencil_key;
|
||||
depth_stencil_key.tile_offset = config->depth_stencil.edram_base;
|
||||
depth_stencil_key.tile_width = config->surface_pitch_px / 80;
|
||||
depth_stencil_key.tile_height = config->surface_height_px / 16;
|
||||
depth_stencil_key.tile_width =
|
||||
xe::round_up(config->surface_pitch_px, tile_width) / tile_width;
|
||||
// depth_stencil_key.tile_height =
|
||||
// xe::round_up(config->surface_height_px, tile_height) / tile_height;
|
||||
depth_stencil_key.tile_height = 160;
|
||||
depth_stencil_key.color_or_depth = 0;
|
||||
depth_stencil_key.msaa_samples =
|
||||
0; // static_cast<uint16_t>(config->surface_msaa);
|
||||
depth_stencil_key.edram_format =
|
||||
static_cast<uint16_t>(config->depth_stencil.format);
|
||||
auto target_depth_stencil_attachment = GetTileView(depth_stencil_key);
|
||||
auto target_depth_stencil_attachment =
|
||||
FindOrCreateTileView(command_buffer, depth_stencil_key);
|
||||
if (!target_depth_stencil_attachment) {
|
||||
XELOGE("Failed to get tile view for depth/stencil attachment");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t surface_pitch_px = config->surface_msaa != MsaaSamples::k4X
|
||||
? config->surface_pitch_px
|
||||
: config->surface_pitch_px * 2;
|
||||
uint32_t surface_height_px = config->surface_msaa == MsaaSamples::k1X
|
||||
? config->surface_height_px
|
||||
: config->surface_height_px * 2;
|
||||
surface_pitch_px = std::min(surface_pitch_px, 2560u);
|
||||
surface_height_px = std::min(surface_height_px, 2560u);
|
||||
framebuffer = new CachedFramebuffer(
|
||||
device_, render_pass->handle, config->surface_pitch_px,
|
||||
config->surface_height_px, target_color_attachments,
|
||||
target_depth_stencil_attachment);
|
||||
*device_, render_pass->handle, surface_pitch_px, surface_height_px,
|
||||
target_color_attachments, target_depth_stencil_attachment);
|
||||
render_pass->cached_framebuffers.push_back(framebuffer);
|
||||
}
|
||||
|
||||
|
@ -720,7 +850,75 @@ bool RenderCache::ConfigureRenderPass(RenderConfiguration* config,
|
|||
return true;
|
||||
}
|
||||
|
||||
CachedTileView* RenderCache::GetTileView(const TileViewKey& view_key) {
|
||||
CachedTileView* RenderCache::FindOrCreateTileView(
|
||||
VkCommandBuffer command_buffer, const TileViewKey& view_key) {
|
||||
auto tile_view = FindTileView(view_key);
|
||||
if (tile_view) {
|
||||
return tile_view;
|
||||
}
|
||||
|
||||
// Create a new tile and add to the cache.
|
||||
tile_view =
|
||||
new CachedTileView(device_, command_buffer, edram_memory_, view_key);
|
||||
cached_tile_views_.push_back(tile_view);
|
||||
|
||||
return tile_view;
|
||||
}
|
||||
|
||||
void RenderCache::UpdateTileView(VkCommandBuffer command_buffer,
|
||||
CachedTileView* view, bool load,
|
||||
bool insert_barrier) {
|
||||
uint32_t tile_width =
|
||||
view->key.msaa_samples == uint16_t(MsaaSamples::k4X) ? 40 : 80;
|
||||
uint32_t tile_height =
|
||||
view->key.msaa_samples != uint16_t(MsaaSamples::k1X) ? 8 : 16;
|
||||
|
||||
if (insert_barrier) {
|
||||
VkBufferMemoryBarrier barrier;
|
||||
barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
barrier.pNext = nullptr;
|
||||
if (load) {
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
} else {
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
}
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = edram_buffer_;
|
||||
barrier.offset = view->key.tile_offset * 5120;
|
||||
barrier.size = view->key.tile_width * tile_width * view->key.tile_height *
|
||||
tile_height * view->key.color_or_depth
|
||||
? 4
|
||||
: 1;
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 1,
|
||||
&barrier, 0, nullptr);
|
||||
}
|
||||
|
||||
// TODO(DrChat): Stencil copies.
|
||||
VkBufferImageCopy region;
|
||||
region.bufferOffset = view->key.tile_offset * 5120;
|
||||
region.bufferRowLength = 0;
|
||||
region.bufferImageHeight = 0;
|
||||
region.imageSubresource = {0, 0, 0, 1};
|
||||
region.imageSubresource.aspectMask = view->key.color_or_depth
|
||||
? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
region.imageOffset = {0, 0, 0};
|
||||
region.imageExtent = {view->key.tile_width * tile_width,
|
||||
view->key.tile_height * tile_height, 1};
|
||||
if (load) {
|
||||
vkCmdCopyBufferToImage(command_buffer, edram_buffer_, view->image,
|
||||
VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
||||
} else {
|
||||
vkCmdCopyImageToBuffer(command_buffer, view->image, VK_IMAGE_LAYOUT_GENERAL,
|
||||
edram_buffer_, 1, ®ion);
|
||||
}
|
||||
}
|
||||
|
||||
CachedTileView* RenderCache::FindTileView(const TileViewKey& view_key) const {
|
||||
// Check the cache.
|
||||
// TODO(benvanik): better lookup.
|
||||
for (auto tile_view : cached_tile_views_) {
|
||||
|
@ -729,25 +927,341 @@ CachedTileView* RenderCache::GetTileView(const TileViewKey& view_key) {
|
|||
}
|
||||
}
|
||||
|
||||
// Create a new tile and add to the cache.
|
||||
auto tile_view = new CachedTileView(device_, edram_memory_, view_key);
|
||||
cached_tile_views_.push_back(tile_view);
|
||||
return tile_view;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RenderCache::EndRenderPass() {
|
||||
assert_not_null(current_command_buffer_);
|
||||
auto command_buffer = current_command_buffer_;
|
||||
current_command_buffer_ = nullptr;
|
||||
|
||||
// End the render pass.
|
||||
vkCmdEndRenderPass(command_buffer);
|
||||
vkCmdEndRenderPass(current_command_buffer_);
|
||||
|
||||
// Copy all render targets back into our EDRAM buffer.
|
||||
// Don't bother waiting on this command to complete, as next render pass may
|
||||
// reuse previous framebuffer attachments. If they need this, they will wait.
|
||||
// TODO: Should we bother re-tiling the images on copy back?
|
||||
//
|
||||
// FIXME: There's a case where we may have a really big render target (as we
|
||||
// can't get the correct height atm) and we may end up overwriting the valid
|
||||
// contents of another render target by mistake! Need to reorder copy commands
|
||||
// to avoid this.
|
||||
|
||||
// TODO(DrChat): Determine if we actually need an EDRAM buffer.
|
||||
/*
|
||||
std::vector<CachedTileView*> cached_views;
|
||||
|
||||
// Depth
|
||||
auto depth_target = current_state_.framebuffer->depth_stencil_attachment;
|
||||
if (depth_target && current_state_.config.depth_stencil.used) {
|
||||
cached_views.push_back(depth_target);
|
||||
}
|
||||
|
||||
// Color
|
||||
for (int i = 0; i < 4; i++) {
|
||||
auto target = current_state_.framebuffer->color_attachments[i];
|
||||
if (!target || !current_state_.config.color[i].used) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cached_views.push_back(target);
|
||||
}
|
||||
|
||||
std::sort(
|
||||
cached_views.begin(), cached_views.end(),
|
||||
[](CachedTileView const* a, CachedTileView const* b) { return *a < *b; });
|
||||
|
||||
for (auto view : cached_views) {
|
||||
UpdateTileView(current_command_buffer_, view, false, false);
|
||||
}
|
||||
*/
|
||||
|
||||
current_command_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
void RenderCache::ClearCache() {
|
||||
// TODO(benvanik): caching.
|
||||
}
|
||||
|
||||
void RenderCache::RawCopyToImage(VkCommandBuffer command_buffer,
|
||||
uint32_t edram_base, VkImage image,
|
||||
VkImageLayout image_layout,
|
||||
bool color_or_depth, VkOffset3D offset,
|
||||
VkExtent3D extents) {
|
||||
// Transition the texture into a transfer destination layout.
|
||||
VkImageMemoryBarrier image_barrier;
|
||||
image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
image_barrier.pNext = nullptr;
|
||||
image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
if (image_layout != VK_IMAGE_LAYOUT_GENERAL &&
|
||||
image_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
|
||||
image_barrier.srcAccessMask = 0;
|
||||
image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
image_barrier.oldLayout = image_layout;
|
||||
image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
image_barrier.image = image;
|
||||
image_barrier.subresourceRange = {0, 0, 1, 0, 1};
|
||||
image_barrier.subresourceRange.aspectMask =
|
||||
color_or_depth
|
||||
? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &image_barrier);
|
||||
}
|
||||
|
||||
VkBufferMemoryBarrier buffer_barrier;
|
||||
buffer_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
|
||||
buffer_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
buffer_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
buffer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
buffer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
buffer_barrier.buffer = edram_buffer_;
|
||||
buffer_barrier.offset = edram_base * 5120;
|
||||
// TODO: Calculate this accurately (need texel size)
|
||||
buffer_barrier.size = extents.width * extents.height * 4;
|
||||
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 1,
|
||||
&buffer_barrier, 0, nullptr);
|
||||
|
||||
// Issue the copy command.
|
||||
// TODO(DrChat): Stencil copies.
|
||||
VkBufferImageCopy region;
|
||||
region.bufferOffset = edram_base * 5120;
|
||||
region.bufferImageHeight = 0;
|
||||
region.bufferRowLength = 0;
|
||||
region.imageOffset = offset;
|
||||
region.imageExtent = extents;
|
||||
region.imageSubresource = {0, 0, 0, 1};
|
||||
region.imageSubresource.aspectMask =
|
||||
color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
vkCmdCopyBufferToImage(command_buffer, edram_buffer_, image, image_layout, 1,
|
||||
®ion);
|
||||
|
||||
// Transition the image back into its previous layout.
|
||||
if (image_layout != VK_IMAGE_LAYOUT_GENERAL &&
|
||||
image_layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
|
||||
image_barrier.srcAccessMask = image_barrier.dstAccessMask;
|
||||
image_barrier.dstAccessMask = 0;
|
||||
std::swap(image_barrier.oldLayout, image_barrier.newLayout);
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &image_barrier);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderCache::BlitToImage(VkCommandBuffer command_buffer,
|
||||
uint32_t edram_base, uint32_t pitch,
|
||||
uint32_t height, MsaaSamples num_samples,
|
||||
VkImage image, VkImageLayout image_layout,
|
||||
bool color_or_depth, uint32_t format,
|
||||
VkFilter filter, VkOffset3D offset,
|
||||
VkExtent3D extents) {
|
||||
if (color_or_depth) {
|
||||
// Adjust similar formats for easier matching.
|
||||
switch (static_cast<ColorRenderTargetFormat>(format)) {
|
||||
case ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
format = uint32_t(ColorRenderTargetFormat::k_8_8_8_8);
|
||||
break;
|
||||
case ColorRenderTargetFormat::k_2_10_10_10_unknown:
|
||||
format = uint32_t(ColorRenderTargetFormat::k_2_10_10_10);
|
||||
break;
|
||||
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT_unknown:
|
||||
format = uint32_t(ColorRenderTargetFormat::k_2_10_10_10_FLOAT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t tile_width = num_samples == MsaaSamples::k4X ? 40 : 80;
|
||||
uint32_t tile_height = num_samples != MsaaSamples::k1X ? 8 : 16;
|
||||
|
||||
// Grab a tile view that represents the source image.
|
||||
TileViewKey key;
|
||||
key.color_or_depth = color_or_depth ? 1 : 0;
|
||||
key.msaa_samples = 0; // static_cast<uint16_t>(num_samples);
|
||||
key.edram_format = format;
|
||||
key.tile_offset = edram_base;
|
||||
key.tile_width = xe::round_up(pitch, tile_width) / tile_width;
|
||||
// key.tile_height = xe::round_up(height, tile_height) / tile_height;
|
||||
key.tile_height = 160;
|
||||
auto tile_view = FindOrCreateTileView(command_buffer, key);
|
||||
assert_not_null(tile_view);
|
||||
|
||||
// Update the view with the latest contents.
|
||||
// UpdateTileView(command_buffer, tile_view, true, true);
|
||||
|
||||
// Transition the image into a transfer destination layout, if needed.
|
||||
// TODO: Util function for this
|
||||
VkImageMemoryBarrier image_barrier;
|
||||
image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
image_barrier.pNext = nullptr;
|
||||
image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.srcAccessMask = 0;
|
||||
image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
image_barrier.oldLayout = image_layout;
|
||||
image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
image_barrier.image = image;
|
||||
image_barrier.subresourceRange = {0, 0, 1, 0, 1};
|
||||
image_barrier.subresourceRange.aspectMask =
|
||||
color_or_depth ? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &image_barrier);
|
||||
|
||||
// If we overflow we'll lose the device here.
|
||||
assert_true(extents.width <= key.tile_width * tile_width);
|
||||
assert_true(extents.height <= key.tile_height * tile_height);
|
||||
|
||||
// Now issue the blit to the destination.
|
||||
if (tile_view->sample_count == VK_SAMPLE_COUNT_1_BIT) {
|
||||
VkImageBlit image_blit;
|
||||
image_blit.srcSubresource = {0, 0, 0, 1};
|
||||
image_blit.srcSubresource.aspectMask =
|
||||
color_or_depth
|
||||
? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
image_blit.srcOffsets[0] = {0, 0, offset.z};
|
||||
image_blit.srcOffsets[1] = {int32_t(extents.width), int32_t(extents.height),
|
||||
int32_t(extents.depth)};
|
||||
|
||||
image_blit.dstSubresource = {0, 0, 0, 1};
|
||||
image_blit.dstSubresource.aspectMask =
|
||||
color_or_depth
|
||||
? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
image_blit.dstOffsets[0] = offset;
|
||||
image_blit.dstOffsets[1] = {offset.x + int32_t(extents.width),
|
||||
offset.y + int32_t(extents.height),
|
||||
offset.z + int32_t(extents.depth)};
|
||||
vkCmdBlitImage(command_buffer, tile_view->image, VK_IMAGE_LAYOUT_GENERAL,
|
||||
image, image_layout, 1, &image_blit, filter);
|
||||
} else {
|
||||
VkImageResolve image_resolve;
|
||||
image_resolve.srcSubresource = {0, 0, 0, 1};
|
||||
image_resolve.srcSubresource.aspectMask =
|
||||
color_or_depth
|
||||
? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
image_resolve.srcOffset = {0, 0, 0};
|
||||
|
||||
image_resolve.dstSubresource = {0, 0, 0, 1};
|
||||
image_resolve.dstSubresource.aspectMask =
|
||||
color_or_depth
|
||||
? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
image_resolve.dstOffset = offset;
|
||||
|
||||
image_resolve.extent = extents;
|
||||
vkCmdResolveImage(command_buffer, tile_view->image, VK_IMAGE_LAYOUT_GENERAL,
|
||||
image, image_layout, 1, &image_resolve);
|
||||
}
|
||||
|
||||
// Transition the image back into its previous layout.
|
||||
image_barrier.srcAccessMask = image_barrier.dstAccessMask;
|
||||
image_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
std::swap(image_barrier.oldLayout, image_barrier.newLayout);
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &image_barrier);
|
||||
}
|
||||
|
||||
void RenderCache::ClearEDRAMColor(VkCommandBuffer command_buffer,
|
||||
uint32_t edram_base,
|
||||
ColorRenderTargetFormat format,
|
||||
uint32_t pitch, uint32_t height,
|
||||
MsaaSamples num_samples, float* color) {
|
||||
// TODO: For formats <= 4 bpp, we can directly fill the EDRAM buffer. Just
|
||||
// need to detect this and calculate a value.
|
||||
|
||||
// Adjust similar formats for easier matching.
|
||||
switch (format) {
|
||||
case ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
||||
format = ColorRenderTargetFormat::k_8_8_8_8;
|
||||
break;
|
||||
case ColorRenderTargetFormat::k_2_10_10_10_unknown:
|
||||
format = ColorRenderTargetFormat::k_2_10_10_10;
|
||||
break;
|
||||
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT_unknown:
|
||||
format = ColorRenderTargetFormat::k_2_10_10_10_FLOAT;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t tile_width = num_samples == MsaaSamples::k4X ? 40 : 80;
|
||||
uint32_t tile_height = num_samples != MsaaSamples::k1X ? 8 : 16;
|
||||
|
||||
// Grab a tile view (as we need to clear an image first)
|
||||
TileViewKey key;
|
||||
key.color_or_depth = 1;
|
||||
key.msaa_samples = 0; // static_cast<uint16_t>(num_samples);
|
||||
key.edram_format = static_cast<uint16_t>(format);
|
||||
key.tile_offset = edram_base;
|
||||
key.tile_width = xe::round_up(pitch, tile_width) / tile_width;
|
||||
// key.tile_height = xe::round_up(height, tile_height) / tile_height;
|
||||
key.tile_height = 160;
|
||||
auto tile_view = FindOrCreateTileView(command_buffer, key);
|
||||
assert_not_null(tile_view);
|
||||
|
||||
VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
VkClearColorValue clear_value;
|
||||
std::memcpy(clear_value.float32, color, sizeof(float) * 4);
|
||||
|
||||
// Issue a clear command
|
||||
vkCmdClearColorImage(command_buffer, tile_view->image,
|
||||
VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &range);
|
||||
|
||||
// Copy image back into EDRAM buffer
|
||||
// UpdateTileView(command_buffer, tile_view, false, false);
|
||||
}
|
||||
|
||||
void RenderCache::ClearEDRAMDepthStencil(VkCommandBuffer command_buffer,
|
||||
uint32_t edram_base,
|
||||
DepthRenderTargetFormat format,
|
||||
uint32_t pitch, uint32_t height,
|
||||
MsaaSamples num_samples, float depth,
|
||||
uint32_t stencil) {
|
||||
// TODO: For formats <= 4 bpp, we can directly fill the EDRAM buffer. Just
|
||||
// need to detect this and calculate a value.
|
||||
|
||||
uint32_t tile_width = num_samples == MsaaSamples::k4X ? 40 : 80;
|
||||
uint32_t tile_height = num_samples != MsaaSamples::k1X ? 8 : 16;
|
||||
|
||||
// Grab a tile view (as we need to clear an image first)
|
||||
TileViewKey key;
|
||||
key.color_or_depth = 0;
|
||||
key.msaa_samples = 0; // static_cast<uint16_t>(num_samples);
|
||||
key.edram_format = static_cast<uint16_t>(format);
|
||||
key.tile_offset = edram_base;
|
||||
key.tile_width = xe::round_up(pitch, tile_width) / tile_width;
|
||||
// key.tile_height = xe::round_up(height, tile_height) / tile_height;
|
||||
key.tile_height = 160;
|
||||
auto tile_view = FindOrCreateTileView(command_buffer, key);
|
||||
assert_not_null(tile_view);
|
||||
|
||||
VkImageSubresourceRange range = {
|
||||
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1,
|
||||
};
|
||||
VkClearDepthStencilValue clear_value;
|
||||
clear_value.depth = depth;
|
||||
clear_value.stencil = stencil;
|
||||
|
||||
// Issue a clear command
|
||||
vkCmdClearDepthStencilImage(command_buffer, tile_view->image,
|
||||
VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &range);
|
||||
|
||||
// Copy image back into EDRAM buffer
|
||||
// UpdateTileView(command_buffer, tile_view, false, false);
|
||||
}
|
||||
|
||||
void RenderCache::FillEDRAM(VkCommandBuffer command_buffer, uint32_t value) {
|
||||
vkCmdFillBuffer(command_buffer, edram_buffer_, 0, kEdramBufferCapacity,
|
||||
value);
|
||||
}
|
||||
|
||||
bool RenderCache::SetShadowRegister(uint32_t* dest, uint32_t register_name) {
|
||||
uint32_t value = register_file_->values[register_name].u32;
|
||||
if (*dest == value) {
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "xenia/gpu/register_file.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.h"
|
||||
|
@ -36,28 +37,67 @@ struct TileViewKey {
|
|||
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 : 15;
|
||||
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;
|
||||
// Memory buffer
|
||||
VkDeviceMemory memory = nullptr;
|
||||
// Image sample count
|
||||
VkSampleCountFlagBits sample_count = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
CachedTileView(ui::vulkan::VulkanDevice* device,
|
||||
VkCommandBuffer command_buffer, VkDeviceMemory edram_memory,
|
||||
TileViewKey view_key);
|
||||
~CachedTileView();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice device_ = nullptr;
|
||||
};
|
||||
|
||||
// Parsed render configuration from the current render state.
|
||||
struct RenderConfiguration {
|
||||
// Render mode (color+depth, depth-only, etc).
|
||||
xenos::ModeControl mode_control;
|
||||
// Target surface pitch, in pixels.
|
||||
// Target surface pitch multiplied by MSAA, in pixels.
|
||||
uint32_t surface_pitch_px;
|
||||
// ESTIMATED target surface height, in pixels.
|
||||
// ESTIMATED target surface height multiplied by MSAA, in pixels.
|
||||
uint32_t surface_height_px;
|
||||
// Surface MSAA setting.
|
||||
MsaaSamples surface_msaa;
|
||||
// Color attachments for the 4 render targets.
|
||||
struct {
|
||||
bool used;
|
||||
uint32_t edram_base;
|
||||
ColorRenderTargetFormat format;
|
||||
} color[4];
|
||||
// Depth/stencil attachment.
|
||||
struct {
|
||||
bool used;
|
||||
uint32_t edram_base;
|
||||
DepthRenderTargetFormat format;
|
||||
} depth_stencil;
|
||||
|
@ -73,6 +113,9 @@ struct RenderState {
|
|||
// 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.
|
||||
|
@ -97,9 +140,13 @@ struct RenderState {
|
|||
// 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, but they are still all 5120b. As we try to
|
||||
// emulate this we adjust our viewport when rendering to stretch pixels as
|
||||
// needed.
|
||||
// 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
|
||||
|
@ -217,6 +264,10 @@ class RenderCache {
|
|||
RenderCache(RegisterFile* register_file, ui::vulkan::VulkanDevice* device);
|
||||
~RenderCache();
|
||||
|
||||
// Call this to determine if you should start a new render pass or continue
|
||||
// with an already open pass.
|
||||
bool dirty() const;
|
||||
|
||||
// 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,
|
||||
|
@ -230,24 +281,63 @@ class RenderCache {
|
|||
// 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, 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,
|
||||
ColorRenderTargetFormat format, uint32_t pitch,
|
||||
uint32_t height, 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,
|
||||
DepthRenderTargetFormat format, uint32_t pitch,
|
||||
uint32_t height, 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(RenderConfiguration* config,
|
||||
bool ConfigureRenderPass(VkCommandBuffer command_buffer,
|
||||
RenderConfiguration* config,
|
||||
CachedRenderPass** out_render_pass,
|
||||
CachedFramebuffer** out_framebuffer);
|
||||
|
||||
// Gets or creates a tile view with the given parameters.
|
||||
CachedTileView* GetTileView(const TileViewKey& view_key);
|
||||
|
||||
RegisterFile* register_file_ = nullptr;
|
||||
VkDevice device_ = nullptr;
|
||||
ui::vulkan::VulkanDevice* device_ = nullptr;
|
||||
|
||||
// Entire 10MiB of EDRAM, aliased to hell by various VkImages.
|
||||
// Entire 10MiB of EDRAM.
|
||||
VkDeviceMemory edram_memory_ = nullptr;
|
||||
// Buffer overlayed 1:1 with edram_memory_ to allow raw access.
|
||||
VkBuffer edram_buffer_ = nullptr;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// source: rect_list.geom
|
||||
const uint8_t rect_list_geom[] = {
|
||||
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x08, 0x00,
|
||||
0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
|
||||
0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||
0x11, 0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
|
||||
0x36, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
|
@ -10,8 +10,8 @@ const uint8_t rect_list_geom[] = {
|
|||
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x33, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x00,
|
||||
|
@ -40,17 +40,13 @@ const uint8_t rect_list_geom[] = {
|
|||
0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x43,
|
||||
0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x00,
|
||||
0x05, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x05, 0x00, 0x05, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x56, 0x65, 0x72, 0x74,
|
||||
0x65, 0x78, 0x44, 0x61, 0x74, 0x61, 0x00, 0x00, 0x06, 0x00, 0x04, 0x00,
|
||||
0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00,
|
||||
0x05, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00, 0x6F, 0x75, 0x74, 0x5F,
|
||||
0x76, 0x74, 0x78, 0x00, 0x05, 0x00, 0x05, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x44, 0x61, 0x74, 0x61, 0x00, 0x00,
|
||||
0x06, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x6F, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x35, 0x00, 0x00, 0x00,
|
||||
0x69, 0x6E, 0x5F, 0x76, 0x74, 0x78, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
|
||||
0x66, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
|
||||
0xB4, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
|
||||
0x05, 0x00, 0x07, 0x00, 0x30, 0x00, 0x00, 0x00, 0x6F, 0x75, 0x74, 0x5F,
|
||||
0x69, 0x6E, 0x74, 0x65, 0x72, 0x70, 0x6F, 0x6C, 0x61, 0x74, 0x6F, 0x72,
|
||||
0x73, 0x00, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x33, 0x00, 0x00, 0x00,
|
||||
0x69, 0x6E, 0x5F, 0x69, 0x6E, 0x74, 0x65, 0x72, 0x70, 0x6F, 0x6C, 0x61,
|
||||
0x74, 0x6F, 0x72, 0x73, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
|
||||
0x64, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
|
||||
0xB2, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
|
||||
0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0E, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
|
@ -65,12 +61,10 @@ const uint8_t rect_list_geom[] = {
|
|||
0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x47, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x2F, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x47, 0x00, 0x04, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00,
|
||||
0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
|
||||
0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x47, 0x00, 0x04, 0x00, 0x33, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
|
||||
|
@ -107,25 +101,23 @@ const uint8_t rect_list_geom[] = {
|
|||
0x03, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
|
||||
0x0B, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x1C, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x2D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00, 0x2F, 0x00, 0x00, 0x00,
|
||||
0x2E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x1E, 0x00, 0x03, 0x00, 0x32, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
|
||||
0x1C, 0x00, 0x04, 0x00, 0x33, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||
0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x04, 0x00, 0x36, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x32, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x65, 0x00, 0x00, 0x00,
|
||||
0x2D, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x2F, 0x00, 0x00, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||
0x2F, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x1C, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||
0x32, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x04, 0x00, 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x2E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x63, 0x00, 0x00, 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||
0x05, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
|
||||
0x65, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||
0x3B, 0x00, 0x04, 0x00, 0x65, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00,
|
||||
0x63, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||
0x3B, 0x00, 0x04, 0x00, 0x63, 0x00, 0x00, 0x00, 0xB2, 0x00, 0x00, 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 0x41, 0x00, 0x07, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x17, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
|
@ -139,7 +131,7 @@ const uint8_t rect_list_geom[] = {
|
|||
0x1C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||
0x1D, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xF7, 0x00, 0x03, 0x00,
|
||||
0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00,
|
||||
0x1D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00,
|
||||
0x1D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
|
@ -153,286 +145,283 @@ const uint8_t rect_list_geom[] = {
|
|||
0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x2C, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x36, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x38, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x31, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
|
||||
0x34, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00,
|
||||
0x36, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00,
|
||||
0x39, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0x3B, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
|
||||
0x37, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0x39, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x39, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00,
|
||||
0x3C, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x36, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x32, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x31, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00,
|
||||
0x3A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||
0x3C, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x34, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00,
|
||||
0x33, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x2E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
|
||||
0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x3F, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x42, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x43, 0x00, 0x00, 0x00,
|
||||
0x42, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x44, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x41, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x42, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x45, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x2B, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x46, 0x00, 0x00, 0x00,
|
||||
0x45, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x36, 0x00, 0x00, 0x00,
|
||||
0x47, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
|
||||
0x47, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x31, 0x00, 0x00, 0x00,
|
||||
0x48, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0xDB, 0x00, 0x01, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00,
|
||||
0x43, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x2B, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x44, 0x00, 0x00, 0x00,
|
||||
0x43, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x45, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00,
|
||||
0x45, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x46, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0xDB, 0x00, 0x01, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00,
|
||||
0x49, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0x4B, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
|
||||
0x47, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0x49, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x49, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00,
|
||||
0x4C, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||
0x4E, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x36, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x32, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x31, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00,
|
||||
0x4A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||
0x4C, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x34, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00,
|
||||
0x33, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x2E, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x00, 0x00,
|
||||
0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x51, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x4F, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x52, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x53, 0x00, 0x00, 0x00,
|
||||
0x52, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x54, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x50, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x51, 0x00, 0x00, 0x00,
|
||||
0x50, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x52, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x55, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x2B, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x56, 0x00, 0x00, 0x00,
|
||||
0x55, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x36, 0x00, 0x00, 0x00,
|
||||
0x57, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
|
||||
0x57, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x31, 0x00, 0x00, 0x00,
|
||||
0x58, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x53, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x2B, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x54, 0x00, 0x00, 0x00,
|
||||
0x53, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x55, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
|
||||
0x55, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x56, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00,
|
||||
0x5B, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x5D, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00,
|
||||
0x59, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x5B, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
|
||||
0x5E, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x60, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00,
|
||||
0x5C, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x5E, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x61, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x5F, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x64, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x66, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||
0x67, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x67, 0x00, 0x00, 0x00,
|
||||
0xF6, 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x6B, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00,
|
||||
0xB1, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00,
|
||||
0x6C, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00,
|
||||
0x6E, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00,
|
||||
0x62, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x64, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||
0x65, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x65, 0x00, 0x00, 0x00,
|
||||
0xF6, 0x00, 0x04, 0x00, 0x67, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x69, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0x69, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
|
||||
0xB1, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00,
|
||||
0x6A, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00,
|
||||
0x6C, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0x66, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00,
|
||||
0x64, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x6F, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x6E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00,
|
||||
0x64, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x73, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x72, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x74, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00,
|
||||
0x74, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||
0x76, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
|
||||
0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00,
|
||||
0x75, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x6D, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x7A, 0x00, 0x00, 0x00,
|
||||
0x79, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x68, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0x68, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
|
||||
0x66, 0x00, 0x00, 0x00, 0x41, 0x00, 0x07, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x71, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00,
|
||||
0x7F, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00,
|
||||
0x72, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||
0x74, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x41, 0x00, 0x07, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00,
|
||||
0x75, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x77, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
|
||||
0x66, 0x00, 0x00, 0x00, 0x41, 0x00, 0x07, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x79, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00,
|
||||
0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00,
|
||||
0x77, 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x7C, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||
0x6A, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x6A, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00,
|
||||
0x66, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||
0x7E, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x66, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00,
|
||||
0xF9, 0x00, 0x02, 0x00, 0x67, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||
0x69, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0xDB, 0x00, 0x01, 0x00,
|
||||
0xF9, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||
0x7F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x81, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x82, 0x00, 0x00, 0x00,
|
||||
0x81, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x83, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x84, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x2B, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x85, 0x00, 0x00, 0x00,
|
||||
0x84, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x36, 0x00, 0x00, 0x00,
|
||||
0x86, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00,
|
||||
0x86, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x31, 0x00, 0x00, 0x00,
|
||||
0x87, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00, 0x8A, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x8A, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0x8C, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x8D, 0x00, 0x00, 0x00, 0x8C, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x36, 0x00, 0x00, 0x00, 0x8E, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x8F, 0x00, 0x00, 0x00, 0x8E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x31, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00,
|
||||
0x90, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0x92, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x92, 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00,
|
||||
0x93, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||
0x95, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x95, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x36, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x32, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x31, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00,
|
||||
0xDA, 0x00, 0x01, 0x00, 0xDB, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00, 0x9A, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x9A, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x9B, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0x9C, 0x00, 0x00, 0x00, 0x9B, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x9D, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x9D, 0x00, 0x00, 0x00, 0x9C, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x36, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x9F, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x31, 0x00, 0x00, 0x00, 0x9F, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xA1, 0x00, 0x00, 0x00,
|
||||
0xA0, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0xA2, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0xA2, 0x00, 0x00, 0x00, 0xA1, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0xA3, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00,
|
||||
0xA3, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||
0xA5, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0xA5, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x36, 0x00, 0x00, 0x00, 0xA6, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x32, 0x00, 0x00, 0x00, 0xA7, 0x00, 0x00, 0x00, 0xA6, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x31, 0x00, 0x00, 0x00, 0xA7, 0x00, 0x00, 0x00,
|
||||
0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0xA8, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0xA9, 0x00, 0x00, 0x00, 0xA8, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00,
|
||||
0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00,
|
||||
0xA9, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0xAE, 0x00, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00,
|
||||
0x83, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xAF, 0x00, 0x00, 0x00,
|
||||
0xAC, 0x00, 0x00, 0x00, 0xAE, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0xB0, 0x00, 0x00, 0x00,
|
||||
0xAF, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0xB1, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0xB2, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x2B, 0x00, 0x00, 0x00, 0xB3, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0xB3, 0x00, 0x00, 0x00,
|
||||
0xB2, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0xB4, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0xB5, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0xB5, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x04, 0x00,
|
||||
0xB7, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xF9, 0x00, 0x02, 0x00, 0xB9, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||
0xB9, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||
0xBA, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00,
|
||||
0x06, 0x00, 0x00, 0x00, 0xBB, 0x00, 0x00, 0x00, 0xBA, 0x00, 0x00, 0x00,
|
||||
0x6D, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00, 0xBB, 0x00, 0x00, 0x00,
|
||||
0xB6, 0x00, 0x00, 0x00, 0xB7, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||
0xB6, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||
0xBC, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0xBD, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x07, 0x00, 0x23, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0xBD, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0xBF, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x07, 0x00, 0x23, 0x00, 0x00, 0x00, 0xC1, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0xC2, 0x00, 0x00, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x00, 0x00,
|
||||
0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x00, 0x00,
|
||||
0xBF, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0xC5, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x07, 0x00, 0x23, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0xC5, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0xC7, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x00, 0x00,
|
||||
0xC7, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0xC9, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0xBC, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0xC9, 0x00, 0x00, 0x00,
|
||||
0xC8, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0xB8, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x02, 0x00, 0xB8, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0xCA, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00, 0x05, 0x00, 0x13, 0x00, 0x00, 0x00, 0xCB, 0x00, 0x00, 0x00,
|
||||
0xCA, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0xB4, 0x00, 0x00, 0x00, 0xCB, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||
0xB5, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0xB7, 0x00, 0x00, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00, 0x05, 0x00, 0x13, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00,
|
||||
0x7B, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x64, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||
0x65, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x67, 0x00, 0x00, 0x00,
|
||||
0xDA, 0x00, 0x01, 0x00, 0xDB, 0x00, 0x01, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||
0x1F, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00,
|
||||
0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
|
||||
0x1F, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x7D, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00,
|
||||
0x7E, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0x80, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00,
|
||||
0x81, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||
0x83, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x83, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x34, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00,
|
||||
0x33, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x2E, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00,
|
||||
0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x86, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x87, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x88, 0x00, 0x00, 0x00,
|
||||
0x87, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x89, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x8A, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x2B, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x8B, 0x00, 0x00, 0x00,
|
||||
0x8A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x8C, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00,
|
||||
0x8C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x8D, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x8E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0x00, 0x8E, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x90, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x91, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x93, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x34, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00,
|
||||
0x95, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00,
|
||||
0xDB, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x96, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0x97, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x26, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x98, 0x00, 0x00, 0x00,
|
||||
0x97, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
|
||||
0x99, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||
0x9A, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x2B, 0x00, 0x00, 0x00, 0x9B, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x9B, 0x00, 0x00, 0x00,
|
||||
0x9A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x9C, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x9D, 0x00, 0x00, 0x00,
|
||||
0x9C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x30, 0x00, 0x00, 0x00,
|
||||
0x9D, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x9F, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0xA0, 0x00, 0x00, 0x00, 0x9F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x16, 0x00, 0x00, 0x00, 0xA1, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x09, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x00, 0x00, 0xA1, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00, 0xA3, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0xA3, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00,
|
||||
0x34, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
|
||||
0x19, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00,
|
||||
0xA5, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0xA5, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0xA6, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xA7, 0x00, 0x00, 0x00,
|
||||
0xA6, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0xA8, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0xA9, 0x00, 0x00, 0x00, 0xA8, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0xA7, 0x00, 0x00, 0x00,
|
||||
0xA9, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0xAB, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0xAC, 0x00, 0x00, 0x00, 0xAB, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00,
|
||||
0xAC, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00,
|
||||
0xAE, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0xAE, 0x00, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0xAF, 0x00, 0x00, 0x00,
|
||||
0x12, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00,
|
||||
0xAF, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
||||
0xB1, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0xB1, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0xB2, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||
0xF9, 0x00, 0x02, 0x00, 0xB3, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||
0xB3, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x04, 0x00, 0xB5, 0x00, 0x00, 0x00,
|
||||
0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||
0xB7, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0xB7, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00,
|
||||
0xB2, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||
0xB9, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x04, 0x00, 0xB9, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00,
|
||||
0xB5, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0xB4, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0xBA, 0x00, 0x00, 0x00,
|
||||
0xB2, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||
0xBB, 0x00, 0x00, 0x00, 0xB2, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
|
||||
0x23, 0x00, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0xBB, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0xBD, 0x00, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x00, 0x00,
|
||||
0xB2, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0xBF, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0xBE, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0xC0, 0x00, 0x00, 0x00, 0xBF, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x04, 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0xC1, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00,
|
||||
0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x00, 0x00,
|
||||
0xBD, 0x00, 0x00, 0x00, 0xC1, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, 0xB2, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x06, 0x00, 0x23, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x00, 0x00,
|
||||
0x33, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xC5, 0x00, 0x00, 0x00,
|
||||
0xC4, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||
0xC6, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x00, 0x00, 0xC5, 0x00, 0x00, 0x00,
|
||||
0x41, 0x00, 0x05, 0x00, 0x26, 0x00, 0x00, 0x00, 0xC7, 0x00, 0x00, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0xBA, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
|
||||
0xC7, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
|
||||
0xB6, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0xB6, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00,
|
||||
0xB2, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||
0xC9, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
|
||||
0x3E, 0x00, 0x03, 0x00, 0xB2, 0x00, 0x00, 0x00, 0xC9, 0x00, 0x00, 0x00,
|
||||
0xF9, 0x00, 0x02, 0x00, 0xB3, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||
0xB5, 0x00, 0x00, 0x00, 0xDA, 0x00, 0x01, 0x00, 0xDB, 0x00, 0x01, 0x00,
|
||||
0xF9, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
|
||||
0x1F, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
; SPIR-V
|
||||
; Version: 1.0
|
||||
; Generator: Khronos Glslang Reference Front End; 1
|
||||
; Bound: 204
|
||||
; Bound: 202
|
||||
; Schema: 0
|
||||
OpCapability Geometry
|
||||
OpCapability GeometryPointSize
|
||||
|
@ -9,7 +9,7 @@
|
|||
OpCapability GeometryStreams
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Geometry %4 "main" %18 %34 %49 %53
|
||||
OpEntryPoint Geometry %4 "main" %18 %34 %48 %51
|
||||
OpExecutionMode %4 Triangles
|
||||
OpExecutionMode %4 Invocations 1
|
||||
OpExecutionMode %4 OutputTriangleStrip
|
||||
|
@ -27,14 +27,10 @@
|
|||
OpMemberName %32 1 "gl_PointSize"
|
||||
OpMemberName %32 2 "gl_ClipDistance"
|
||||
OpName %34 ""
|
||||
OpName %47 "VertexData"
|
||||
OpMemberName %47 0 "o"
|
||||
OpName %49 "out_vtx"
|
||||
OpName %50 "VertexData"
|
||||
OpMemberName %50 0 "o"
|
||||
OpName %53 "in_vtx"
|
||||
OpName %102 "i"
|
||||
OpName %180 "i"
|
||||
OpName %48 "out_interpolators"
|
||||
OpName %51 "in_interpolators"
|
||||
OpName %100 "i"
|
||||
OpName %178 "i"
|
||||
OpMemberDecorate %14 0 BuiltIn Position
|
||||
OpMemberDecorate %14 1 BuiltIn PointSize
|
||||
OpMemberDecorate %14 2 BuiltIn ClipDistance
|
||||
|
@ -45,10 +41,9 @@
|
|||
OpDecorate %32 Block
|
||||
OpDecorate %32 Stream 0
|
||||
OpDecorate %34 Stream 0
|
||||
OpMemberDecorate %47 0 Location 0
|
||||
OpDecorate %47 Stream 0
|
||||
OpDecorate %49 Stream 0
|
||||
OpMemberDecorate %50 0 Location 0
|
||||
OpDecorate %48 Location 0
|
||||
OpDecorate %48 Stream 0
|
||||
OpDecorate %51 Location 0
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeBool
|
||||
|
@ -77,21 +72,19 @@
|
|||
%43 = OpTypePointer Output %9
|
||||
%45 = OpConstant %11 16
|
||||
%46 = OpTypeArray %10 %45
|
||||
%47 = OpTypeStruct %46
|
||||
%48 = OpTypePointer Output %47
|
||||
%49 = OpVariable %48 Output
|
||||
%50 = OpTypeStruct %46
|
||||
%51 = OpTypeArray %50 %15
|
||||
%52 = OpTypePointer Input %51
|
||||
%53 = OpVariable %52 Input
|
||||
%54 = OpTypePointer Input %50
|
||||
%101 = OpTypePointer Function %19
|
||||
%109 = OpConstant %19 16
|
||||
%47 = OpTypePointer Output %46
|
||||
%48 = OpVariable %47 Output
|
||||
%49 = OpTypeArray %46 %15
|
||||
%50 = OpTypePointer Input %49
|
||||
%51 = OpVariable %50 Input
|
||||
%52 = OpTypePointer Input %46
|
||||
%99 = OpTypePointer Function %19
|
||||
%107 = OpConstant %19 16
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%8 = OpVariable %7 Function
|
||||
%102 = OpVariable %101 Function
|
||||
%180 = OpVariable %101 Function
|
||||
%100 = OpVariable %99 Function
|
||||
%178 = OpVariable %99 Function
|
||||
%23 = OpAccessChain %22 %18 %20 %20 %21
|
||||
%24 = OpLoad %9 %23
|
||||
%26 = OpAccessChain %22 %18 %25 %20 %21
|
||||
|
@ -100,7 +93,7 @@
|
|||
OpStore %8 %28
|
||||
%29 = OpLoad %6 %8
|
||||
OpSelectionMerge %31 None
|
||||
OpBranchConditional %29 %30 %127
|
||||
OpBranchConditional %29 %30 %125
|
||||
%30 = OpLabel
|
||||
%36 = OpAccessChain %35 %18 %20 %20
|
||||
%37 = OpLoad %10 %36
|
||||
|
@ -110,216 +103,216 @@
|
|||
%42 = OpLoad %9 %41
|
||||
%44 = OpAccessChain %43 %34 %40
|
||||
OpStore %44 %42
|
||||
%55 = OpAccessChain %54 %53 %20
|
||||
%56 = OpLoad %50 %55
|
||||
OpStore %49 %56
|
||||
%53 = OpAccessChain %52 %51 %20
|
||||
%54 = OpLoad %46 %53
|
||||
OpStore %48 %54
|
||||
OpEmitVertex
|
||||
%57 = OpAccessChain %35 %18 %40 %20
|
||||
%58 = OpLoad %10 %57
|
||||
%59 = OpAccessChain %38 %34 %20
|
||||
OpStore %59 %58
|
||||
%60 = OpAccessChain %22 %18 %40 %40
|
||||
%61 = OpLoad %9 %60
|
||||
%62 = OpAccessChain %43 %34 %40
|
||||
OpStore %62 %61
|
||||
%63 = OpAccessChain %54 %53 %40
|
||||
%64 = OpLoad %50 %63
|
||||
OpStore %49 %64
|
||||
%55 = OpAccessChain %35 %18 %40 %20
|
||||
%56 = OpLoad %10 %55
|
||||
%57 = OpAccessChain %38 %34 %20
|
||||
OpStore %57 %56
|
||||
%58 = OpAccessChain %22 %18 %40 %40
|
||||
%59 = OpLoad %9 %58
|
||||
%60 = OpAccessChain %43 %34 %40
|
||||
OpStore %60 %59
|
||||
%61 = OpAccessChain %52 %51 %40
|
||||
%62 = OpLoad %46 %61
|
||||
OpStore %48 %62
|
||||
OpEmitVertex
|
||||
%65 = OpAccessChain %35 %18 %25 %20
|
||||
%66 = OpLoad %10 %65
|
||||
%67 = OpAccessChain %38 %34 %20
|
||||
OpStore %67 %66
|
||||
%68 = OpAccessChain %22 %18 %25 %40
|
||||
%69 = OpLoad %9 %68
|
||||
%70 = OpAccessChain %43 %34 %40
|
||||
OpStore %70 %69
|
||||
%71 = OpAccessChain %54 %53 %25
|
||||
%72 = OpLoad %50 %71
|
||||
OpStore %49 %72
|
||||
%63 = OpAccessChain %35 %18 %25 %20
|
||||
%64 = OpLoad %10 %63
|
||||
%65 = OpAccessChain %38 %34 %20
|
||||
OpStore %65 %64
|
||||
%66 = OpAccessChain %22 %18 %25 %40
|
||||
%67 = OpLoad %9 %66
|
||||
%68 = OpAccessChain %43 %34 %40
|
||||
OpStore %68 %67
|
||||
%69 = OpAccessChain %52 %51 %25
|
||||
%70 = OpLoad %46 %69
|
||||
OpStore %48 %70
|
||||
OpEmitVertex
|
||||
OpEndPrimitive
|
||||
%73 = OpAccessChain %35 %18 %25 %20
|
||||
%74 = OpLoad %10 %73
|
||||
%75 = OpAccessChain %38 %34 %20
|
||||
OpStore %75 %74
|
||||
%76 = OpAccessChain %22 %18 %25 %40
|
||||
%77 = OpLoad %9 %76
|
||||
%78 = OpAccessChain %43 %34 %40
|
||||
OpStore %78 %77
|
||||
%79 = OpAccessChain %54 %53 %25
|
||||
%80 = OpLoad %50 %79
|
||||
OpStore %49 %80
|
||||
%71 = OpAccessChain %35 %18 %25 %20
|
||||
%72 = OpLoad %10 %71
|
||||
%73 = OpAccessChain %38 %34 %20
|
||||
OpStore %73 %72
|
||||
%74 = OpAccessChain %22 %18 %25 %40
|
||||
%75 = OpLoad %9 %74
|
||||
%76 = OpAccessChain %43 %34 %40
|
||||
OpStore %76 %75
|
||||
%77 = OpAccessChain %52 %51 %25
|
||||
%78 = OpLoad %46 %77
|
||||
OpStore %48 %78
|
||||
OpEmitVertex
|
||||
%81 = OpAccessChain %35 %18 %40 %20
|
||||
%82 = OpLoad %10 %81
|
||||
%83 = OpAccessChain %38 %34 %20
|
||||
OpStore %83 %82
|
||||
%84 = OpAccessChain %22 %18 %40 %40
|
||||
%85 = OpLoad %9 %84
|
||||
%86 = OpAccessChain %43 %34 %40
|
||||
OpStore %86 %85
|
||||
%87 = OpAccessChain %54 %53 %40
|
||||
%88 = OpLoad %50 %87
|
||||
OpStore %49 %88
|
||||
%79 = OpAccessChain %35 %18 %40 %20
|
||||
%80 = OpLoad %10 %79
|
||||
%81 = OpAccessChain %38 %34 %20
|
||||
OpStore %81 %80
|
||||
%82 = OpAccessChain %22 %18 %40 %40
|
||||
%83 = OpLoad %9 %82
|
||||
%84 = OpAccessChain %43 %34 %40
|
||||
OpStore %84 %83
|
||||
%85 = OpAccessChain %52 %51 %40
|
||||
%86 = OpLoad %46 %85
|
||||
OpStore %48 %86
|
||||
OpEmitVertex
|
||||
%89 = OpAccessChain %35 %18 %40 %20
|
||||
%87 = OpAccessChain %35 %18 %40 %20
|
||||
%88 = OpLoad %10 %87
|
||||
%89 = OpAccessChain %35 %18 %25 %20
|
||||
%90 = OpLoad %10 %89
|
||||
%91 = OpAccessChain %35 %18 %25 %20
|
||||
%92 = OpLoad %10 %91
|
||||
%93 = OpFAdd %10 %90 %92
|
||||
%94 = OpAccessChain %35 %18 %20 %20
|
||||
%95 = OpLoad %10 %94
|
||||
%96 = OpFSub %10 %93 %95
|
||||
%97 = OpAccessChain %38 %34 %20
|
||||
OpStore %97 %96
|
||||
%98 = OpAccessChain %22 %18 %25 %40
|
||||
%99 = OpLoad %9 %98
|
||||
%100 = OpAccessChain %43 %34 %40
|
||||
OpStore %100 %99
|
||||
OpStore %102 %20
|
||||
OpBranch %103
|
||||
%103 = OpLabel
|
||||
OpLoopMerge %105 %106 None
|
||||
OpBranch %107
|
||||
%107 = OpLabel
|
||||
%108 = OpLoad %19 %102
|
||||
%110 = OpSLessThan %6 %108 %109
|
||||
OpBranchConditional %110 %104 %105
|
||||
%104 = OpLabel
|
||||
%111 = OpLoad %19 %102
|
||||
%112 = OpLoad %19 %102
|
||||
%113 = OpAccessChain %35 %53 %20 %20 %112
|
||||
%114 = OpLoad %10 %113
|
||||
%115 = OpFNegate %10 %114
|
||||
%116 = OpLoad %19 %102
|
||||
%117 = OpAccessChain %35 %53 %40 %20 %116
|
||||
%118 = OpLoad %10 %117
|
||||
%119 = OpFAdd %10 %115 %118
|
||||
%120 = OpLoad %19 %102
|
||||
%121 = OpAccessChain %35 %53 %25 %20 %120
|
||||
%122 = OpLoad %10 %121
|
||||
%123 = OpFAdd %10 %119 %122
|
||||
%124 = OpAccessChain %38 %49 %20 %111
|
||||
OpStore %124 %123
|
||||
OpBranch %106
|
||||
%106 = OpLabel
|
||||
%125 = OpLoad %19 %102
|
||||
%126 = OpIAdd %19 %125 %40
|
||||
OpStore %102 %126
|
||||
OpBranch %103
|
||||
%91 = OpFAdd %10 %88 %90
|
||||
%92 = OpAccessChain %35 %18 %20 %20
|
||||
%93 = OpLoad %10 %92
|
||||
%94 = OpFSub %10 %91 %93
|
||||
%95 = OpAccessChain %38 %34 %20
|
||||
OpStore %95 %94
|
||||
%96 = OpAccessChain %22 %18 %25 %40
|
||||
%97 = OpLoad %9 %96
|
||||
%98 = OpAccessChain %43 %34 %40
|
||||
OpStore %98 %97
|
||||
OpStore %100 %20
|
||||
OpBranch %101
|
||||
%101 = OpLabel
|
||||
OpLoopMerge %103 %104 None
|
||||
OpBranch %105
|
||||
%105 = OpLabel
|
||||
%106 = OpLoad %19 %100
|
||||
%108 = OpSLessThan %6 %106 %107
|
||||
OpBranchConditional %108 %102 %103
|
||||
%102 = OpLabel
|
||||
%109 = OpLoad %19 %100
|
||||
%110 = OpLoad %19 %100
|
||||
%111 = OpAccessChain %35 %51 %20 %110
|
||||
%112 = OpLoad %10 %111
|
||||
%113 = OpFNegate %10 %112
|
||||
%114 = OpLoad %19 %100
|
||||
%115 = OpAccessChain %35 %51 %40 %114
|
||||
%116 = OpLoad %10 %115
|
||||
%117 = OpFAdd %10 %113 %116
|
||||
%118 = OpLoad %19 %100
|
||||
%119 = OpAccessChain %35 %51 %25 %118
|
||||
%120 = OpLoad %10 %119
|
||||
%121 = OpFAdd %10 %117 %120
|
||||
%122 = OpAccessChain %38 %48 %109
|
||||
OpStore %122 %121
|
||||
OpBranch %104
|
||||
%104 = OpLabel
|
||||
%123 = OpLoad %19 %100
|
||||
%124 = OpIAdd %19 %123 %40
|
||||
OpStore %100 %124
|
||||
OpBranch %101
|
||||
%103 = OpLabel
|
||||
OpEmitVertex
|
||||
OpEndPrimitive
|
||||
OpBranch %31
|
||||
%127 = OpLabel
|
||||
%128 = OpAccessChain %35 %18 %20 %20
|
||||
%129 = OpLoad %10 %128
|
||||
%130 = OpAccessChain %38 %34 %20
|
||||
OpStore %130 %129
|
||||
%131 = OpAccessChain %22 %18 %20 %40
|
||||
%132 = OpLoad %9 %131
|
||||
%133 = OpAccessChain %43 %34 %40
|
||||
OpStore %133 %132
|
||||
%134 = OpAccessChain %54 %53 %20
|
||||
%135 = OpLoad %50 %134
|
||||
OpStore %49 %135
|
||||
%125 = OpLabel
|
||||
%126 = OpAccessChain %35 %18 %20 %20
|
||||
%127 = OpLoad %10 %126
|
||||
%128 = OpAccessChain %38 %34 %20
|
||||
OpStore %128 %127
|
||||
%129 = OpAccessChain %22 %18 %20 %40
|
||||
%130 = OpLoad %9 %129
|
||||
%131 = OpAccessChain %43 %34 %40
|
||||
OpStore %131 %130
|
||||
%132 = OpAccessChain %52 %51 %20
|
||||
%133 = OpLoad %46 %132
|
||||
OpStore %48 %133
|
||||
OpEmitVertex
|
||||
%136 = OpAccessChain %35 %18 %40 %20
|
||||
%137 = OpLoad %10 %136
|
||||
%138 = OpAccessChain %38 %34 %20
|
||||
OpStore %138 %137
|
||||
%139 = OpAccessChain %22 %18 %40 %40
|
||||
%140 = OpLoad %9 %139
|
||||
%141 = OpAccessChain %43 %34 %40
|
||||
OpStore %141 %140
|
||||
%142 = OpAccessChain %54 %53 %40
|
||||
%143 = OpLoad %50 %142
|
||||
OpStore %49 %143
|
||||
%134 = OpAccessChain %35 %18 %40 %20
|
||||
%135 = OpLoad %10 %134
|
||||
%136 = OpAccessChain %38 %34 %20
|
||||
OpStore %136 %135
|
||||
%137 = OpAccessChain %22 %18 %40 %40
|
||||
%138 = OpLoad %9 %137
|
||||
%139 = OpAccessChain %43 %34 %40
|
||||
OpStore %139 %138
|
||||
%140 = OpAccessChain %52 %51 %40
|
||||
%141 = OpLoad %46 %140
|
||||
OpStore %48 %141
|
||||
OpEmitVertex
|
||||
%144 = OpAccessChain %35 %18 %25 %20
|
||||
%145 = OpLoad %10 %144
|
||||
%146 = OpAccessChain %38 %34 %20
|
||||
OpStore %146 %145
|
||||
%147 = OpAccessChain %22 %18 %25 %40
|
||||
%148 = OpLoad %9 %147
|
||||
%149 = OpAccessChain %43 %34 %40
|
||||
OpStore %149 %148
|
||||
%150 = OpAccessChain %54 %53 %25
|
||||
%151 = OpLoad %50 %150
|
||||
OpStore %49 %151
|
||||
%142 = OpAccessChain %35 %18 %25 %20
|
||||
%143 = OpLoad %10 %142
|
||||
%144 = OpAccessChain %38 %34 %20
|
||||
OpStore %144 %143
|
||||
%145 = OpAccessChain %22 %18 %25 %40
|
||||
%146 = OpLoad %9 %145
|
||||
%147 = OpAccessChain %43 %34 %40
|
||||
OpStore %147 %146
|
||||
%148 = OpAccessChain %52 %51 %25
|
||||
%149 = OpLoad %46 %148
|
||||
OpStore %48 %149
|
||||
OpEmitVertex
|
||||
OpEndPrimitive
|
||||
%152 = OpAccessChain %35 %18 %20 %20
|
||||
%153 = OpLoad %10 %152
|
||||
%154 = OpAccessChain %38 %34 %20
|
||||
OpStore %154 %153
|
||||
%155 = OpAccessChain %22 %18 %20 %40
|
||||
%156 = OpLoad %9 %155
|
||||
%157 = OpAccessChain %43 %34 %40
|
||||
OpStore %157 %156
|
||||
%158 = OpAccessChain %54 %53 %20
|
||||
%159 = OpLoad %50 %158
|
||||
OpStore %49 %159
|
||||
%150 = OpAccessChain %35 %18 %20 %20
|
||||
%151 = OpLoad %10 %150
|
||||
%152 = OpAccessChain %38 %34 %20
|
||||
OpStore %152 %151
|
||||
%153 = OpAccessChain %22 %18 %20 %40
|
||||
%154 = OpLoad %9 %153
|
||||
%155 = OpAccessChain %43 %34 %40
|
||||
OpStore %155 %154
|
||||
%156 = OpAccessChain %52 %51 %20
|
||||
%157 = OpLoad %46 %156
|
||||
OpStore %48 %157
|
||||
OpEmitVertex
|
||||
%160 = OpAccessChain %35 %18 %25 %20
|
||||
%161 = OpLoad %10 %160
|
||||
%162 = OpAccessChain %38 %34 %20
|
||||
OpStore %162 %161
|
||||
%163 = OpAccessChain %22 %18 %25 %40
|
||||
%164 = OpLoad %9 %163
|
||||
%165 = OpAccessChain %43 %34 %40
|
||||
OpStore %165 %164
|
||||
%166 = OpAccessChain %54 %53 %25
|
||||
%167 = OpLoad %50 %166
|
||||
OpStore %49 %167
|
||||
%158 = OpAccessChain %35 %18 %25 %20
|
||||
%159 = OpLoad %10 %158
|
||||
%160 = OpAccessChain %38 %34 %20
|
||||
OpStore %160 %159
|
||||
%161 = OpAccessChain %22 %18 %25 %40
|
||||
%162 = OpLoad %9 %161
|
||||
%163 = OpAccessChain %43 %34 %40
|
||||
OpStore %163 %162
|
||||
%164 = OpAccessChain %52 %51 %25
|
||||
%165 = OpLoad %46 %164
|
||||
OpStore %48 %165
|
||||
OpEmitVertex
|
||||
%168 = OpAccessChain %35 %18 %20 %20
|
||||
%166 = OpAccessChain %35 %18 %20 %20
|
||||
%167 = OpLoad %10 %166
|
||||
%168 = OpAccessChain %35 %18 %25 %20
|
||||
%169 = OpLoad %10 %168
|
||||
%170 = OpAccessChain %35 %18 %25 %20
|
||||
%171 = OpLoad %10 %170
|
||||
%172 = OpFAdd %10 %169 %171
|
||||
%173 = OpAccessChain %35 %18 %40 %20
|
||||
%174 = OpLoad %10 %173
|
||||
%175 = OpFSub %10 %172 %174
|
||||
%176 = OpAccessChain %38 %34 %20
|
||||
OpStore %176 %175
|
||||
%177 = OpAccessChain %22 %18 %25 %40
|
||||
%178 = OpLoad %9 %177
|
||||
%179 = OpAccessChain %43 %34 %40
|
||||
OpStore %179 %178
|
||||
OpStore %180 %20
|
||||
OpBranch %181
|
||||
%181 = OpLabel
|
||||
OpLoopMerge %183 %184 None
|
||||
OpBranch %185
|
||||
%185 = OpLabel
|
||||
%186 = OpLoad %19 %180
|
||||
%187 = OpSLessThan %6 %186 %109
|
||||
OpBranchConditional %187 %182 %183
|
||||
%182 = OpLabel
|
||||
%188 = OpLoad %19 %180
|
||||
%189 = OpLoad %19 %180
|
||||
%190 = OpAccessChain %35 %53 %20 %20 %189
|
||||
%191 = OpLoad %10 %190
|
||||
%192 = OpLoad %19 %180
|
||||
%193 = OpAccessChain %35 %53 %40 %20 %192
|
||||
%194 = OpLoad %10 %193
|
||||
%195 = OpFNegate %10 %194
|
||||
%196 = OpFAdd %10 %191 %195
|
||||
%197 = OpLoad %19 %180
|
||||
%198 = OpAccessChain %35 %53 %25 %20 %197
|
||||
%199 = OpLoad %10 %198
|
||||
%200 = OpFAdd %10 %196 %199
|
||||
%201 = OpAccessChain %38 %49 %20 %188
|
||||
OpStore %201 %200
|
||||
OpBranch %184
|
||||
%184 = OpLabel
|
||||
%202 = OpLoad %19 %180
|
||||
%203 = OpIAdd %19 %202 %40
|
||||
OpStore %180 %203
|
||||
OpBranch %181
|
||||
%170 = OpFAdd %10 %167 %169
|
||||
%171 = OpAccessChain %35 %18 %40 %20
|
||||
%172 = OpLoad %10 %171
|
||||
%173 = OpFSub %10 %170 %172
|
||||
%174 = OpAccessChain %38 %34 %20
|
||||
OpStore %174 %173
|
||||
%175 = OpAccessChain %22 %18 %25 %40
|
||||
%176 = OpLoad %9 %175
|
||||
%177 = OpAccessChain %43 %34 %40
|
||||
OpStore %177 %176
|
||||
OpStore %178 %20
|
||||
OpBranch %179
|
||||
%179 = OpLabel
|
||||
OpLoopMerge %181 %182 None
|
||||
OpBranch %183
|
||||
%183 = OpLabel
|
||||
%184 = OpLoad %19 %178
|
||||
%185 = OpSLessThan %6 %184 %107
|
||||
OpBranchConditional %185 %180 %181
|
||||
%180 = OpLabel
|
||||
%186 = OpLoad %19 %178
|
||||
%187 = OpLoad %19 %178
|
||||
%188 = OpAccessChain %35 %51 %20 %187
|
||||
%189 = OpLoad %10 %188
|
||||
%190 = OpLoad %19 %178
|
||||
%191 = OpAccessChain %35 %51 %40 %190
|
||||
%192 = OpLoad %10 %191
|
||||
%193 = OpFNegate %10 %192
|
||||
%194 = OpFAdd %10 %189 %193
|
||||
%195 = OpLoad %19 %178
|
||||
%196 = OpAccessChain %35 %51 %25 %195
|
||||
%197 = OpLoad %10 %196
|
||||
%198 = OpFAdd %10 %194 %197
|
||||
%199 = OpAccessChain %38 %48 %186
|
||||
OpStore %199 %198
|
||||
OpBranch %182
|
||||
%182 = OpLabel
|
||||
%200 = OpLoad %19 %178
|
||||
%201 = OpIAdd %19 %200 %40
|
||||
OpStore %178 %201
|
||||
OpBranch %179
|
||||
%181 = OpLabel
|
||||
OpEmitVertex
|
||||
OpEndPrimitive
|
||||
OpBranch %31
|
||||
|
|
|
@ -16,11 +16,8 @@ out gl_PerVertex {
|
|||
float gl_ClipDistance[];
|
||||
};
|
||||
|
||||
struct VertexData {
|
||||
vec4 o[16];
|
||||
};
|
||||
layout(location = 0) in VertexData in_vtx[];
|
||||
layout(location = 0) out VertexData out_vtx;
|
||||
layout(location = 0) in vec4 in_interpolators[][16];
|
||||
layout(location = 0) out vec4 out_interpolators[16];
|
||||
|
||||
layout(triangles) in;
|
||||
layout(triangle_strip, max_vertices = 6) out;
|
||||
|
@ -35,30 +32,30 @@ void main() {
|
|||
// 2 ----- [3]
|
||||
gl_Position = gl_in[0].gl_Position;
|
||||
gl_PointSize = gl_in[0].gl_PointSize;
|
||||
out_vtx = in_vtx[0];
|
||||
out_interpolators = in_interpolators[0];
|
||||
EmitVertex();
|
||||
gl_Position = gl_in[1].gl_Position;
|
||||
gl_PointSize = gl_in[1].gl_PointSize;
|
||||
out_vtx = in_vtx[1];
|
||||
out_interpolators = in_interpolators[1];
|
||||
EmitVertex();
|
||||
gl_Position = gl_in[2].gl_Position;
|
||||
gl_PointSize = gl_in[2].gl_PointSize;
|
||||
out_vtx = in_vtx[2];
|
||||
out_interpolators = in_interpolators[2];
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
gl_Position = gl_in[2].gl_Position;
|
||||
gl_PointSize = gl_in[2].gl_PointSize;
|
||||
out_vtx = in_vtx[2];
|
||||
out_interpolators = in_interpolators[2];
|
||||
EmitVertex();
|
||||
gl_Position = gl_in[1].gl_Position;
|
||||
gl_PointSize = gl_in[1].gl_PointSize;
|
||||
out_vtx = in_vtx[1];
|
||||
out_interpolators = in_interpolators[1];
|
||||
EmitVertex();
|
||||
gl_Position = (gl_in[1].gl_Position + gl_in[2].gl_Position) -
|
||||
gl_in[0].gl_Position;
|
||||
gl_PointSize = gl_in[2].gl_PointSize;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
out_vtx.o[i] = -in_vtx[0].o[i] + in_vtx[1].o[i] + in_vtx[2].o[i];
|
||||
out_interpolators[i] = -in_interpolators[0][i] + in_interpolators[1][i] + in_interpolators[2][i];
|
||||
}
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
@ -70,30 +67,30 @@ void main() {
|
|||
// [3] ----- 2
|
||||
gl_Position = gl_in[0].gl_Position;
|
||||
gl_PointSize = gl_in[0].gl_PointSize;
|
||||
out_vtx = in_vtx[0];
|
||||
out_interpolators = in_interpolators[0];
|
||||
EmitVertex();
|
||||
gl_Position = gl_in[1].gl_Position;
|
||||
gl_PointSize = gl_in[1].gl_PointSize;
|
||||
out_vtx = in_vtx[1];
|
||||
out_interpolators = in_interpolators[1];
|
||||
EmitVertex();
|
||||
gl_Position = gl_in[2].gl_Position;
|
||||
gl_PointSize = gl_in[2].gl_PointSize;
|
||||
out_vtx = in_vtx[2];
|
||||
out_interpolators = in_interpolators[2];
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
gl_Position = gl_in[0].gl_Position;
|
||||
gl_PointSize = gl_in[0].gl_PointSize;
|
||||
out_vtx = in_vtx[0];
|
||||
out_interpolators = in_interpolators[0];
|
||||
EmitVertex();
|
||||
gl_Position = gl_in[2].gl_Position;
|
||||
gl_PointSize = gl_in[2].gl_PointSize;
|
||||
out_vtx = in_vtx[2];
|
||||
out_interpolators = in_interpolators[2];
|
||||
EmitVertex();
|
||||
gl_Position = (gl_in[0].gl_Position + gl_in[2].gl_Position) -
|
||||
gl_in[1].gl_Position;
|
||||
gl_PointSize = gl_in[2].gl_PointSize;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
out_vtx.o[i] = in_vtx[0].o[i] + -in_vtx[1].o[i] + in_vtx[2].o[i];
|
||||
out_interpolators[i] = in_interpolators[0][i] + -in_interpolators[1][i] + in_interpolators[2][i];
|
||||
}
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,10 +10,16 @@
|
|||
#ifndef XENIA_GPU_VULKAN_TEXTURE_CACHE_H_
|
||||
#define XENIA_GPU_VULKAN_TEXTURE_CACHE_H_
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "xenia/gpu/register_file.h"
|
||||
#include "xenia/gpu/sampler_info.h"
|
||||
#include "xenia/gpu/shader.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/vulkan.h"
|
||||
#include "xenia/ui/vulkan/vulkan_device.h"
|
||||
|
||||
|
@ -24,8 +30,51 @@ namespace vulkan {
|
|||
//
|
||||
class TextureCache {
|
||||
public:
|
||||
TextureCache(RegisterFile* register_file, TraceWriter* trace_writer,
|
||||
ui::vulkan::VulkanDevice* device);
|
||||
struct TextureView;
|
||||
|
||||
// This represents an uploaded Vulkan texture.
|
||||
struct Texture {
|
||||
TextureInfo texture_info;
|
||||
std::vector<std::unique_ptr<TextureView>> views;
|
||||
|
||||
// True if we know all info about this texture, false otherwise.
|
||||
// (e.g. we resolve to system memory and may not know the full details about
|
||||
// this texture)
|
||||
bool is_full_texture;
|
||||
VkFormat format;
|
||||
VkImage image;
|
||||
VkImageLayout image_layout;
|
||||
VkDeviceMemory image_memory;
|
||||
VkDeviceSize memory_offset;
|
||||
VkDeviceSize memory_size;
|
||||
|
||||
uintptr_t access_watch_handle;
|
||||
bool pending_invalidation;
|
||||
|
||||
// Pointer to the latest usage fence.
|
||||
std::shared_ptr<ui::vulkan::Fence> in_flight_fence;
|
||||
};
|
||||
|
||||
struct TextureView {
|
||||
Texture* texture;
|
||||
VkImageView view;
|
||||
|
||||
union {
|
||||
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;
|
||||
};
|
||||
|
||||
uint16_t swizzle;
|
||||
};
|
||||
};
|
||||
|
||||
TextureCache(Memory* memory, RegisterFile* register_file,
|
||||
TraceWriter* trace_writer, ui::vulkan::VulkanDevice* device);
|
||||
~TextureCache();
|
||||
|
||||
// Descriptor set layout containing all possible texture bindings.
|
||||
|
@ -36,8 +85,11 @@ class TextureCache {
|
|||
|
||||
// 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 command_buffer,
|
||||
VkCommandBuffer setup_command_buffer,
|
||||
std::shared_ptr<ui::vulkan::Fence> completion_fence,
|
||||
const std::vector<Shader::TextureBinding>& vertex_bindings,
|
||||
const std::vector<Shader::TextureBinding>& pixel_bindings);
|
||||
|
||||
|
@ -45,45 +97,106 @@ class TextureCache {
|
|||
// TODO(benvanik): Resolve.
|
||||
// TODO(benvanik): ReadTexture.
|
||||
|
||||
// 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, TextureFormat format,
|
||||
VkOffset2D* out_offset = nullptr);
|
||||
|
||||
// Demands a texture for the purpose of resolving from EDRAM. This either
|
||||
// creates a new texture or returns a previously created texture. texture_info
|
||||
// is not required to be completely filled out, just guest_address and all
|
||||
// sizes.
|
||||
//
|
||||
// It's possible that this may return an image that is larger than the
|
||||
// requested size (e.g. resolving into a bigger texture) or an image that
|
||||
// must have an offset applied. If so, the caller must handle this.
|
||||
// At the very least, it's guaranteed that the image will be large enough to
|
||||
// hold the requested size.
|
||||
Texture* DemandResolveTexture(const TextureInfo& texture_info,
|
||||
TextureFormat format, VkOffset2D* out_offset);
|
||||
|
||||
// Clears all cached content.
|
||||
void ClearCache();
|
||||
|
||||
// Frees any unused resources
|
||||
void Scavenge();
|
||||
|
||||
private:
|
||||
struct UpdateSetInfo;
|
||||
|
||||
void SetupGridImages();
|
||||
// Cached Vulkan sampler.
|
||||
struct Sampler {
|
||||
SamplerInfo sampler_info;
|
||||
VkSampler sampler;
|
||||
};
|
||||
|
||||
// Allocates a new texture and memory to back it on the GPU.
|
||||
Texture* AllocateTexture(const TextureInfo& texture_info);
|
||||
bool FreeTexture(Texture* texture);
|
||||
|
||||
// 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,
|
||||
std::shared_ptr<ui::vulkan::Fence> completion_fence = nullptr);
|
||||
TextureView* DemandView(Texture* texture, uint16_t swizzle);
|
||||
Sampler* Demand(const SamplerInfo& sampler_info);
|
||||
|
||||
// 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 UploadTexture2D(VkCommandBuffer command_buffer,
|
||||
std::shared_ptr<ui::vulkan::Fence> completion_fence,
|
||||
Texture* dest, TextureInfo src);
|
||||
|
||||
bool SetupTextureBindings(
|
||||
VkCommandBuffer command_buffer,
|
||||
std::shared_ptr<ui::vulkan::Fence> completion_fence,
|
||||
UpdateSetInfo* update_set_info,
|
||||
const std::vector<Shader::TextureBinding>& bindings);
|
||||
bool SetupTextureBinding(UpdateSetInfo* update_set_info,
|
||||
bool SetupTextureBinding(VkCommandBuffer command_buffer,
|
||||
std::shared_ptr<ui::vulkan::Fence> completion_fence,
|
||||
UpdateSetInfo* update_set_info,
|
||||
const Shader::TextureBinding& binding);
|
||||
|
||||
Memory* memory_ = nullptr;
|
||||
|
||||
RegisterFile* register_file_ = nullptr;
|
||||
TraceWriter* trace_writer_ = nullptr;
|
||||
ui::vulkan::VulkanDevice* device_ = nullptr;
|
||||
|
||||
VkDescriptorPool descriptor_pool_ = nullptr;
|
||||
VkDescriptorSetLayout texture_descriptor_set_layout_ = nullptr;
|
||||
std::list<std::pair<VkDescriptorSet, std::shared_ptr<ui::vulkan::Fence>>>
|
||||
in_flight_sets_;
|
||||
|
||||
VkDeviceMemory grid_image_2d_memory_ = nullptr;
|
||||
VkImage grid_image_2d_ = nullptr;
|
||||
VkImageView grid_image_2d_view_ = nullptr;
|
||||
ui::vulkan::CircularBuffer staging_buffer_;
|
||||
std::unordered_map<uint64_t, Texture*> textures_;
|
||||
std::unordered_map<uint64_t, Sampler*> samplers_;
|
||||
std::vector<Texture*> resolve_textures_;
|
||||
std::list<Texture*> pending_delete_textures_;
|
||||
|
||||
std::mutex invalidated_textures_mutex_;
|
||||
std::vector<Texture*>* invalidated_textures_;
|
||||
std::vector<Texture*> invalidated_textures_sets_[2];
|
||||
|
||||
std::mutex invalidated_resolve_textures_mutex_;
|
||||
std::vector<Texture*> invalidated_resolve_textures_;
|
||||
|
||||
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 sampler_write_count = 0;
|
||||
VkDescriptorImageInfo sampler_infos[32];
|
||||
uint32_t image_1d_write_count = 0;
|
||||
VkDescriptorImageInfo image_1d_infos[32];
|
||||
uint32_t image_2d_write_count = 0;
|
||||
VkDescriptorImageInfo image_2d_infos[32];
|
||||
uint32_t image_3d_write_count = 0;
|
||||
VkDescriptorImageInfo image_3d_infos[32];
|
||||
uint32_t image_cube_write_count = 0;
|
||||
VkDescriptorImageInfo image_cube_infos[32];
|
||||
uint32_t image_write_count = 0;
|
||||
struct ImageSetInfo {
|
||||
Dimension dimension;
|
||||
uint32_t tf_binding;
|
||||
VkDescriptorImageInfo info;
|
||||
} image_infos[32];
|
||||
} update_set_info_;
|
||||
};
|
||||
|
||||
|
|
|
@ -37,9 +37,22 @@ VulkanCommandProcessor::VulkanCommandProcessor(
|
|||
|
||||
VulkanCommandProcessor::~VulkanCommandProcessor() = default;
|
||||
|
||||
void VulkanCommandProcessor::RequestFrameTrace(const std::wstring& root_path) {
|
||||
// Override traces if renderdoc is attached.
|
||||
if (device_->is_renderdoc_attached()) {
|
||||
trace_requested_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
return CommandProcessor::RequestFrameTrace(root_path);
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::ClearCaches() {
|
||||
CommandProcessor::ClearCaches();
|
||||
|
||||
auto status = vkQueueWaitIdle(queue_);
|
||||
CheckResult(status, "vkQueueWaitIdle");
|
||||
|
||||
buffer_cache_->ClearCache();
|
||||
pipeline_cache_->ClearCache();
|
||||
render_cache_->ClearCache();
|
||||
|
@ -69,8 +82,8 @@ bool VulkanCommandProcessor::SetupContext() {
|
|||
// Initialize the state machine caches.
|
||||
buffer_cache_ = std::make_unique<BufferCache>(register_file_, device_,
|
||||
kDefaultBufferCacheCapacity);
|
||||
texture_cache_ =
|
||||
std::make_unique<TextureCache>(register_file_, &trace_writer_, device_);
|
||||
texture_cache_ = std::make_unique<TextureCache>(memory_, register_file_,
|
||||
&trace_writer_, device_);
|
||||
pipeline_cache_ = std::make_unique<PipelineCache>(
|
||||
register_file_, device_, buffer_cache_->constant_descriptor_set_layout(),
|
||||
texture_cache_->texture_descriptor_set_layout());
|
||||
|
@ -82,6 +95,11 @@ bool VulkanCommandProcessor::SetupContext() {
|
|||
void VulkanCommandProcessor::ShutdownContext() {
|
||||
// TODO(benvanik): wait until idle.
|
||||
|
||||
if (swap_state_.front_buffer_texture) {
|
||||
// Free swap chain images.
|
||||
DestroySwapImages();
|
||||
}
|
||||
|
||||
buffer_cache_.reset();
|
||||
pipeline_cache_.reset();
|
||||
render_cache_.reset();
|
||||
|
@ -90,7 +108,7 @@ void VulkanCommandProcessor::ShutdownContext() {
|
|||
// Free all pools. This must come after all of our caches clean up.
|
||||
command_buffer_pool_.reset();
|
||||
|
||||
// Release queue, if were using an acquired one.
|
||||
// Release queue, if we were using an acquired one.
|
||||
if (!queue_mutex_) {
|
||||
device_->ReleaseQueue(queue_);
|
||||
queue_ = nullptr;
|
||||
|
@ -131,24 +149,241 @@ void VulkanCommandProcessor::ReturnFromWait() {
|
|||
CommandProcessor::ReturnFromWait();
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::CreateSwapImages(VkCommandBuffer setup_buffer,
|
||||
VkExtent2D extents) {
|
||||
VkImageCreateInfo image_info;
|
||||
std::memset(&image_info, 0, sizeof(VkImageCreateInfo));
|
||||
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
image_info.extent = {extents.width, extents.height, 1};
|
||||
image_info.mipLevels = 1;
|
||||
image_info.arrayLayers = 1;
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_info.usage =
|
||||
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_info.queueFamilyIndexCount = 0;
|
||||
image_info.pQueueFamilyIndices = nullptr;
|
||||
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
VkImage image_fb, image_bb;
|
||||
auto status = vkCreateImage(*device_, &image_info, nullptr, &image_fb);
|
||||
CheckResult(status, "vkCreateImage");
|
||||
|
||||
status = vkCreateImage(*device_, &image_info, nullptr, &image_bb);
|
||||
CheckResult(status, "vkCreateImage");
|
||||
|
||||
// Bind memory to images.
|
||||
VkMemoryRequirements mem_requirements;
|
||||
vkGetImageMemoryRequirements(*device_, image_fb, &mem_requirements);
|
||||
fb_memory = device_->AllocateMemory(mem_requirements, 0);
|
||||
assert_not_null(fb_memory);
|
||||
|
||||
status = vkBindImageMemory(*device_, image_fb, fb_memory, 0);
|
||||
CheckResult(status, "vkBindImageMemory");
|
||||
|
||||
vkGetImageMemoryRequirements(*device_, image_fb, &mem_requirements);
|
||||
bb_memory = device_->AllocateMemory(mem_requirements, 0);
|
||||
assert_not_null(bb_memory);
|
||||
|
||||
status = vkBindImageMemory(*device_, image_bb, bb_memory, 0);
|
||||
CheckResult(status, "vkBindImageMemory");
|
||||
|
||||
std::lock_guard<std::mutex> lock(swap_state_.mutex);
|
||||
swap_state_.front_buffer_texture = reinterpret_cast<uintptr_t>(image_fb);
|
||||
swap_state_.back_buffer_texture = reinterpret_cast<uintptr_t>(image_bb);
|
||||
|
||||
// Transition both images to general layout.
|
||||
VkImageMemoryBarrier barrier;
|
||||
std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier));
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.srcAccessMask = 0;
|
||||
barrier.dstAccessMask = 0;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image_fb;
|
||||
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
|
||||
vkCmdPipelineBarrier(setup_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &barrier);
|
||||
|
||||
barrier.image = image_bb;
|
||||
|
||||
vkCmdPipelineBarrier(setup_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &barrier);
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::DestroySwapImages() {
|
||||
std::lock_guard<std::mutex> lock(swap_state_.mutex);
|
||||
vkDestroyImage(*device_,
|
||||
reinterpret_cast<VkImage>(swap_state_.front_buffer_texture),
|
||||
nullptr);
|
||||
vkDestroyImage(*device_,
|
||||
reinterpret_cast<VkImage>(swap_state_.back_buffer_texture),
|
||||
nullptr);
|
||||
vkFreeMemory(*device_, fb_memory, nullptr);
|
||||
vkFreeMemory(*device_, bb_memory, nullptr);
|
||||
|
||||
swap_state_.front_buffer_texture = 0;
|
||||
swap_state_.back_buffer_texture = 0;
|
||||
fb_memory = nullptr;
|
||||
bb_memory = nullptr;
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
|
||||
uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) {
|
||||
// Ensure we issue any pending draws.
|
||||
// draw_batcher_.Flush(DrawBatcher::FlushMode::kMakeCoherent);
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
|
||||
// Need to finish to be sure the other context sees the right data.
|
||||
// TODO(benvanik): prevent this? fences?
|
||||
// glFinish();
|
||||
|
||||
if (context_->WasLost()) {
|
||||
// We've lost the context due to a TDR.
|
||||
// TODO: Dump the current commands to a tracefile.
|
||||
assert_always();
|
||||
// Build a final command buffer that copies the game's frontbuffer texture
|
||||
// into our backbuffer texture.
|
||||
VkCommandBuffer copy_commands = nullptr;
|
||||
bool opened_batch;
|
||||
if (command_buffer_pool_->has_open_batch()) {
|
||||
copy_commands = command_buffer_pool_->AcquireEntry();
|
||||
opened_batch = false;
|
||||
} else {
|
||||
command_buffer_pool_->BeginBatch();
|
||||
copy_commands = command_buffer_pool_->AcquireEntry();
|
||||
current_batch_fence_.reset(new ui::vulkan::Fence(*device_));
|
||||
opened_batch = true;
|
||||
}
|
||||
|
||||
// Remove any dead textures, etc.
|
||||
// texture_cache_.Scavenge();
|
||||
VkCommandBufferBeginInfo begin_info;
|
||||
std::memset(&begin_info, 0, sizeof(begin_info));
|
||||
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
auto status = vkBeginCommandBuffer(copy_commands, &begin_info);
|
||||
CheckResult(status, "vkBeginCommandBuffer");
|
||||
|
||||
if (!frontbuffer_ptr) {
|
||||
// Trace viewer does this.
|
||||
frontbuffer_ptr = last_copy_base_;
|
||||
}
|
||||
|
||||
if (!swap_state_.back_buffer_texture) {
|
||||
CreateSwapImages(copy_commands, {frontbuffer_width, frontbuffer_height});
|
||||
}
|
||||
auto swap_bb = reinterpret_cast<VkImage>(swap_state_.back_buffer_texture);
|
||||
|
||||
// Issue the commands to copy the game's frontbuffer to our backbuffer.
|
||||
auto texture = texture_cache_->LookupAddress(
|
||||
frontbuffer_ptr, xe::round_up(frontbuffer_width, 32),
|
||||
xe::round_up(frontbuffer_height, 32), TextureFormat::k_8_8_8_8);
|
||||
if (texture) {
|
||||
texture->in_flight_fence = current_batch_fence_;
|
||||
|
||||
// Insert a barrier so the GPU finishes writing to the image.
|
||||
VkImageMemoryBarrier barrier;
|
||||
std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier));
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.srcAccessMask =
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
barrier.oldLayout = texture->image_layout;
|
||||
barrier.newLayout = texture->image_layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = texture->image;
|
||||
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
|
||||
vkCmdPipelineBarrier(copy_commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &barrier);
|
||||
|
||||
// Now issue a blit command.
|
||||
VkImageBlit blit;
|
||||
std::memset(&blit, 0, sizeof(VkImageBlit));
|
||||
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
blit.srcOffsets[0] = {0, 0, 0};
|
||||
blit.srcOffsets[1] = {int32_t(frontbuffer_width),
|
||||
int32_t(frontbuffer_height), 1};
|
||||
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
blit.dstOffsets[0] = {0, 0, 0};
|
||||
blit.dstOffsets[1] = {int32_t(frontbuffer_width),
|
||||
int32_t(frontbuffer_height), 1};
|
||||
|
||||
vkCmdBlitImage(copy_commands, texture->image, texture->image_layout,
|
||||
swap_bb, VK_IMAGE_LAYOUT_GENERAL, 1, &blit,
|
||||
VK_FILTER_LINEAR);
|
||||
|
||||
std::lock_guard<std::mutex> lock(swap_state_.mutex);
|
||||
swap_state_.width = frontbuffer_width;
|
||||
swap_state_.height = frontbuffer_height;
|
||||
}
|
||||
|
||||
status = vkEndCommandBuffer(copy_commands);
|
||||
CheckResult(status, "vkEndCommandBuffer");
|
||||
|
||||
// Queue up current command buffers.
|
||||
// TODO(benvanik): bigger batches.
|
||||
std::vector<VkCommandBuffer> submit_buffers;
|
||||
if (current_command_buffer_) {
|
||||
if (current_render_state_) {
|
||||
render_cache_->EndRenderPass();
|
||||
current_render_state_ = nullptr;
|
||||
}
|
||||
|
||||
status = vkEndCommandBuffer(current_setup_buffer_);
|
||||
CheckResult(status, "vkEndCommandBuffer");
|
||||
status = vkEndCommandBuffer(current_command_buffer_);
|
||||
CheckResult(status, "vkEndCommandBuffer");
|
||||
|
||||
// TODO(DrChat): If the setup buffer is empty, don't bother queueing it up.
|
||||
submit_buffers.push_back(current_setup_buffer_);
|
||||
submit_buffers.push_back(current_command_buffer_);
|
||||
|
||||
current_command_buffer_ = nullptr;
|
||||
current_setup_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
submit_buffers.push_back(copy_commands);
|
||||
if (!submit_buffers.empty()) {
|
||||
// TODO(benvanik): move to CP or to host (trace dump, etc).
|
||||
// This only needs to surround a vkQueueSubmit.
|
||||
if (queue_mutex_) {
|
||||
queue_mutex_->lock();
|
||||
}
|
||||
|
||||
VkSubmitInfo submit_info;
|
||||
std::memset(&submit_info, 0, sizeof(VkSubmitInfo));
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.commandBufferCount = uint32_t(submit_buffers.size());
|
||||
submit_info.pCommandBuffers = submit_buffers.data();
|
||||
status = vkQueueSubmit(queue_, 1, &submit_info, *current_batch_fence_);
|
||||
CheckResult(status, "vkQueueSubmit");
|
||||
|
||||
if (device_->is_renderdoc_attached() && capturing_) {
|
||||
device_->EndRenderDocFrameCapture();
|
||||
capturing_ = false;
|
||||
}
|
||||
if (queue_mutex_) {
|
||||
queue_mutex_->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
command_buffer_pool_->EndBatch(current_batch_fence_);
|
||||
|
||||
// Scavenging.
|
||||
{
|
||||
#if FINE_GRAINED_DRAW_SCOPES
|
||||
SCOPE_profile_cpu_i(
|
||||
"gpu",
|
||||
"xe::gpu::vulkan::VulkanCommandProcessor::PerformSwap Scavenging");
|
||||
#endif // FINE_GRAINED_DRAW_SCOPES
|
||||
command_buffer_pool_->Scavenge();
|
||||
|
||||
texture_cache_->Scavenge();
|
||||
buffer_cache_->Scavenge();
|
||||
}
|
||||
|
||||
current_batch_fence_ = nullptr;
|
||||
}
|
||||
|
||||
Shader* VulkanCommandProcessor::LoadShader(ShaderType shader_type,
|
||||
|
@ -178,16 +413,16 @@ bool VulkanCommandProcessor::IssueDraw(PrimitiveType primitive_type,
|
|||
return IssueCopy();
|
||||
}
|
||||
|
||||
// TODO(benvanik): move to CP or to host (trace dump, etc).
|
||||
if (FLAGS_vulkan_renderdoc_capture_all && device_->is_renderdoc_attached()) {
|
||||
device_->BeginRenderDocFrameCapture();
|
||||
if ((regs[XE_GPU_REG_RB_SURFACE_INFO].u32 & 0x3FFF) == 0) {
|
||||
// Doesn't actually draw.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Shaders will have already been defined by previous loads.
|
||||
// We need the to do just about anything so validate here.
|
||||
// We need them to do just about anything so validate here.
|
||||
auto vertex_shader = static_cast<VulkanShader*>(active_vertex_shader());
|
||||
auto pixel_shader = static_cast<VulkanShader*>(active_pixel_shader());
|
||||
if (!vertex_shader || !vertex_shader->is_valid()) {
|
||||
if (!vertex_shader) {
|
||||
// Always need a vertex shader.
|
||||
return true;
|
||||
}
|
||||
|
@ -196,61 +431,142 @@ bool VulkanCommandProcessor::IssueDraw(PrimitiveType primitive_type,
|
|||
// Use a dummy pixel shader when required.
|
||||
// TODO(benvanik): dummy pixel shader.
|
||||
assert_not_null(pixel_shader);
|
||||
} else if (!pixel_shader || !pixel_shader->is_valid()) {
|
||||
} else if (!pixel_shader) {
|
||||
// Need a pixel shader in normal color mode.
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(benvanik): bigger batches.
|
||||
command_buffer_pool_->BeginBatch();
|
||||
VkCommandBuffer command_buffer = command_buffer_pool_->AcquireEntry();
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info;
|
||||
command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
command_buffer_begin_info.pNext = nullptr;
|
||||
command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
command_buffer_begin_info.pInheritanceInfo = nullptr;
|
||||
auto err = vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info);
|
||||
CheckResult(err, "vkBeginCommandBuffer");
|
||||
bool started_command_buffer = false;
|
||||
if (!current_command_buffer_) {
|
||||
// TODO(benvanik): bigger batches.
|
||||
// TODO(DrChat): Decouple setup buffer from current batch.
|
||||
command_buffer_pool_->BeginBatch();
|
||||
current_command_buffer_ = command_buffer_pool_->AcquireEntry();
|
||||
current_setup_buffer_ = command_buffer_pool_->AcquireEntry();
|
||||
current_batch_fence_.reset(new ui::vulkan::Fence(*device_));
|
||||
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info;
|
||||
command_buffer_begin_info.sType =
|
||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
command_buffer_begin_info.pNext = nullptr;
|
||||
command_buffer_begin_info.flags =
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
command_buffer_begin_info.pInheritanceInfo = nullptr;
|
||||
auto status = vkBeginCommandBuffer(current_command_buffer_,
|
||||
&command_buffer_begin_info);
|
||||
CheckResult(status, "vkBeginCommandBuffer");
|
||||
|
||||
status =
|
||||
vkBeginCommandBuffer(current_setup_buffer_, &command_buffer_begin_info);
|
||||
CheckResult(status, "vkBeginCommandBuffer");
|
||||
|
||||
static uint32_t frame = 0;
|
||||
if (device_->is_renderdoc_attached() && !capturing_ &&
|
||||
(FLAGS_vulkan_renderdoc_capture_all || trace_requested_)) {
|
||||
if (queue_mutex_) {
|
||||
queue_mutex_->lock();
|
||||
}
|
||||
|
||||
capturing_ = true;
|
||||
trace_requested_ = false;
|
||||
device_->BeginRenderDocFrameCapture();
|
||||
|
||||
if (queue_mutex_) {
|
||||
queue_mutex_->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
started_command_buffer = true;
|
||||
}
|
||||
auto command_buffer = current_command_buffer_;
|
||||
auto setup_buffer = current_setup_buffer_;
|
||||
|
||||
// Begin the render pass.
|
||||
// This will setup our framebuffer and begin the pass in the command buffer.
|
||||
auto render_state = render_cache_->BeginRenderPass(
|
||||
command_buffer, vertex_shader, pixel_shader);
|
||||
if (!render_state) {
|
||||
return false;
|
||||
// This reuses a previous render pass if one is already open.
|
||||
if (render_cache_->dirty() || !current_render_state_) {
|
||||
if (current_render_state_) {
|
||||
render_cache_->EndRenderPass();
|
||||
current_render_state_ = nullptr;
|
||||
}
|
||||
|
||||
current_render_state_ = render_cache_->BeginRenderPass(
|
||||
command_buffer, vertex_shader, pixel_shader);
|
||||
if (!current_render_state_) {
|
||||
command_buffer_pool_->CancelBatch();
|
||||
current_command_buffer_ = nullptr;
|
||||
current_setup_buffer_ = nullptr;
|
||||
current_batch_fence_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the pipeline for drawing.
|
||||
// This encodes all render state (blend, depth, etc), our shader stages,
|
||||
// and our vertex input layout.
|
||||
if (!pipeline_cache_->ConfigurePipeline(command_buffer, render_state,
|
||||
vertex_shader, pixel_shader,
|
||||
primitive_type)) {
|
||||
VkPipeline pipeline = nullptr;
|
||||
auto pipeline_status = pipeline_cache_->ConfigurePipeline(
|
||||
command_buffer, current_render_state_, vertex_shader, pixel_shader,
|
||||
primitive_type, &pipeline);
|
||||
if (pipeline_status == PipelineCache::UpdateStatus::kMismatch ||
|
||||
started_command_buffer) {
|
||||
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipeline);
|
||||
} else if (pipeline_status == PipelineCache::UpdateStatus::kError) {
|
||||
render_cache_->EndRenderPass();
|
||||
command_buffer_pool_->CancelBatch();
|
||||
current_command_buffer_ = nullptr;
|
||||
current_setup_buffer_ = nullptr;
|
||||
current_batch_fence_ = nullptr;
|
||||
current_render_state_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
pipeline_cache_->SetDynamicState(command_buffer, started_command_buffer);
|
||||
|
||||
// Pass registers to the shaders.
|
||||
if (!PopulateConstants(command_buffer, vertex_shader, pixel_shader)) {
|
||||
render_cache_->EndRenderPass();
|
||||
command_buffer_pool_->CancelBatch();
|
||||
current_command_buffer_ = nullptr;
|
||||
current_setup_buffer_ = nullptr;
|
||||
current_batch_fence_ = nullptr;
|
||||
current_render_state_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upload and bind index buffer data (if we have any).
|
||||
if (!PopulateIndexBuffer(command_buffer, index_buffer_info)) {
|
||||
render_cache_->EndRenderPass();
|
||||
command_buffer_pool_->CancelBatch();
|
||||
current_command_buffer_ = nullptr;
|
||||
current_setup_buffer_ = nullptr;
|
||||
current_batch_fence_ = nullptr;
|
||||
current_render_state_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upload and bind all vertex buffer data.
|
||||
if (!PopulateVertexBuffers(command_buffer, vertex_shader)) {
|
||||
render_cache_->EndRenderPass();
|
||||
command_buffer_pool_->CancelBatch();
|
||||
current_command_buffer_ = nullptr;
|
||||
current_setup_buffer_ = nullptr;
|
||||
current_batch_fence_ = nullptr;
|
||||
current_render_state_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upload and set descriptors for all textures.
|
||||
if (!PopulateSamplers(command_buffer, vertex_shader, pixel_shader)) {
|
||||
// Bind samplers/textures.
|
||||
// Uploads all textures that need it.
|
||||
// Setup buffer may be flushed to GPU if the texture cache needs it.
|
||||
if (!PopulateSamplers(command_buffer, setup_buffer, vertex_shader,
|
||||
pixel_shader)) {
|
||||
render_cache_->EndRenderPass();
|
||||
command_buffer_pool_->CancelBatch();
|
||||
current_command_buffer_ = nullptr;
|
||||
current_setup_buffer_ = nullptr;
|
||||
current_batch_fence_ = nullptr;
|
||||
current_render_state_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -273,68 +589,21 @@ bool VulkanCommandProcessor::IssueDraw(PrimitiveType primitive_type,
|
|||
vertex_offset, first_instance);
|
||||
}
|
||||
|
||||
// End the rendering pass.
|
||||
render_cache_->EndRenderPass();
|
||||
|
||||
// TODO(benvanik): bigger batches.
|
||||
err = vkEndCommandBuffer(command_buffer);
|
||||
CheckResult(err, "vkEndCommandBuffer");
|
||||
VkFence fence;
|
||||
VkFenceCreateInfo fence_info;
|
||||
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fence_info.pNext = nullptr;
|
||||
fence_info.flags = 0;
|
||||
vkCreateFence(*device_, &fence_info, nullptr, &fence);
|
||||
command_buffer_pool_->EndBatch(fence);
|
||||
VkSubmitInfo submit_info;
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.pNext = nullptr;
|
||||
submit_info.waitSemaphoreCount = 0;
|
||||
submit_info.pWaitSemaphores = nullptr;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &command_buffer;
|
||||
submit_info.signalSemaphoreCount = 0;
|
||||
submit_info.pSignalSemaphores = nullptr;
|
||||
if (queue_mutex_) {
|
||||
queue_mutex_->lock();
|
||||
}
|
||||
err = vkQueueSubmit(queue_, 1, &submit_info, fence);
|
||||
if (queue_mutex_) {
|
||||
queue_mutex_->unlock();
|
||||
}
|
||||
CheckResult(err, "vkQueueSubmit");
|
||||
if (queue_mutex_) {
|
||||
queue_mutex_->lock();
|
||||
}
|
||||
err = vkQueueWaitIdle(queue_);
|
||||
CheckResult(err, "vkQueueWaitIdle");
|
||||
err = vkDeviceWaitIdle(*device_);
|
||||
CheckResult(err, "vkDeviceWaitIdle");
|
||||
if (queue_mutex_) {
|
||||
queue_mutex_->unlock();
|
||||
}
|
||||
while (command_buffer_pool_->has_pending()) {
|
||||
command_buffer_pool_->Scavenge();
|
||||
xe::threading::MaybeYield();
|
||||
}
|
||||
vkDestroyFence(*device_, fence, nullptr);
|
||||
|
||||
// TODO(benvanik): move to CP or to host (trace dump, etc).
|
||||
if (FLAGS_vulkan_renderdoc_capture_all && device_->is_renderdoc_attached()) {
|
||||
device_->EndRenderDocFrameCapture();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanCommandProcessor::PopulateConstants(VkCommandBuffer command_buffer,
|
||||
VulkanShader* vertex_shader,
|
||||
VulkanShader* pixel_shader) {
|
||||
#if FINE_GRAINED_DRAW_SCOPES
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
#endif // FINE_GRAINED_DRAW_SCOPES
|
||||
|
||||
// Upload the constants the shaders require.
|
||||
// These are optional, and if none are defined 0 will be returned.
|
||||
auto constant_offsets = buffer_cache_->UploadConstantRegisters(
|
||||
vertex_shader->constant_register_map(),
|
||||
pixel_shader->constant_register_map());
|
||||
pixel_shader->constant_register_map(), current_batch_fence_);
|
||||
if (constant_offsets.first == VK_WHOLE_SIZE ||
|
||||
constant_offsets.second == VK_WHOLE_SIZE) {
|
||||
// Shader wants constants but we couldn't upload them.
|
||||
|
@ -387,8 +656,8 @@ bool VulkanCommandProcessor::PopulateIndexBuffer(
|
|||
size_t source_length =
|
||||
info.count * (info.format == IndexFormat::kInt32 ? sizeof(uint32_t)
|
||||
: sizeof(uint16_t));
|
||||
auto buffer_ref =
|
||||
buffer_cache_->UploadIndexBuffer(source_ptr, source_length, info.format);
|
||||
auto buffer_ref = buffer_cache_->UploadIndexBuffer(
|
||||
source_ptr, source_length, info.format, current_batch_fence_);
|
||||
if (buffer_ref.second == VK_WHOLE_SIZE) {
|
||||
// Failed to upload buffer.
|
||||
return false;
|
||||
|
@ -413,6 +682,11 @@ bool VulkanCommandProcessor::PopulateVertexBuffers(
|
|||
#endif // FINE_GRAINED_DRAW_SCOPES
|
||||
|
||||
auto& vertex_bindings = vertex_shader->vertex_bindings();
|
||||
if (vertex_bindings.empty()) {
|
||||
// No bindings.
|
||||
return true;
|
||||
}
|
||||
|
||||
assert_true(vertex_bindings.size() <= 32);
|
||||
VkBuffer all_buffers[32];
|
||||
VkDeviceSize all_buffer_offsets[32];
|
||||
|
@ -434,7 +708,6 @@ bool VulkanCommandProcessor::PopulateVertexBuffers(
|
|||
fetch = &group->vertex_fetch_2;
|
||||
break;
|
||||
}
|
||||
assert_true(fetch->endian == 2);
|
||||
|
||||
// TODO(benvanik): compute based on indices or vertex count.
|
||||
// THIS CAN BE MASSIVELY INCORRECT (too large).
|
||||
|
@ -446,8 +719,9 @@ bool VulkanCommandProcessor::PopulateVertexBuffers(
|
|||
const void* source_ptr =
|
||||
memory_->TranslatePhysical<const void*>(fetch->address << 2);
|
||||
size_t source_length = valid_range;
|
||||
auto buffer_ref =
|
||||
buffer_cache_->UploadVertexBuffer(source_ptr, source_length);
|
||||
auto buffer_ref = buffer_cache_->UploadVertexBuffer(
|
||||
source_ptr, source_length, static_cast<Endian>(fetch->endian),
|
||||
current_batch_fence_);
|
||||
if (buffer_ref.second == VK_WHOLE_SIZE) {
|
||||
// Failed to upload buffer.
|
||||
return false;
|
||||
|
@ -467,6 +741,7 @@ bool VulkanCommandProcessor::PopulateVertexBuffers(
|
|||
}
|
||||
|
||||
bool VulkanCommandProcessor::PopulateSamplers(VkCommandBuffer command_buffer,
|
||||
VkCommandBuffer setup_buffer,
|
||||
VulkanShader* vertex_shader,
|
||||
VulkanShader* pixel_shader) {
|
||||
#if FINE_GRAINED_DRAW_SCOPES
|
||||
|
@ -474,14 +749,13 @@ bool VulkanCommandProcessor::PopulateSamplers(VkCommandBuffer command_buffer,
|
|||
#endif // FINE_GRAINED_DRAW_SCOPES
|
||||
|
||||
auto descriptor_set = texture_cache_->PrepareTextureSet(
|
||||
command_buffer, vertex_shader->texture_bindings(),
|
||||
setup_buffer, current_batch_fence_, vertex_shader->texture_bindings(),
|
||||
pixel_shader->texture_bindings());
|
||||
if (!descriptor_set) {
|
||||
// Unable to bind set.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bind samplers/textures.
|
||||
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipeline_cache_->pipeline_layout(), 1, 1,
|
||||
&descriptor_set, 0, nullptr);
|
||||
|
@ -491,7 +765,294 @@ bool VulkanCommandProcessor::PopulateSamplers(VkCommandBuffer command_buffer,
|
|||
|
||||
bool VulkanCommandProcessor::IssueCopy() {
|
||||
SCOPE_profile_cpu_f("gpu");
|
||||
// TODO(benvanik): resolve.
|
||||
auto& regs = *register_file_;
|
||||
|
||||
// This is used to resolve surfaces, taking them from EDRAM render targets
|
||||
// to system memory. It can optionally clear color/depth surfaces, too.
|
||||
// The command buffer has stuff for actually doing this by drawing, however
|
||||
// we should be able to do it without that much easier.
|
||||
|
||||
uint32_t copy_control = regs[XE_GPU_REG_RB_COPY_CONTROL].u32;
|
||||
// Render targets 0-3, 4 = depth
|
||||
uint32_t copy_src_select = copy_control & 0x7;
|
||||
bool color_clear_enabled = (copy_control >> 8) & 0x1;
|
||||
bool depth_clear_enabled = (copy_control >> 9) & 0x1;
|
||||
auto copy_command = static_cast<CopyCommand>((copy_control >> 20) & 0x3);
|
||||
|
||||
uint32_t copy_dest_info = regs[XE_GPU_REG_RB_COPY_DEST_INFO].u32;
|
||||
auto copy_dest_endian = static_cast<Endian128>(copy_dest_info & 0x7);
|
||||
uint32_t copy_dest_array = (copy_dest_info >> 3) & 0x1;
|
||||
assert_true(copy_dest_array == 0);
|
||||
uint32_t copy_dest_slice = (copy_dest_info >> 4) & 0x7;
|
||||
assert_true(copy_dest_slice == 0);
|
||||
auto copy_dest_format =
|
||||
static_cast<ColorFormat>((copy_dest_info >> 7) & 0x3F);
|
||||
uint32_t copy_dest_number = (copy_dest_info >> 13) & 0x7;
|
||||
// assert_true(copy_dest_number == 0); // ?
|
||||
uint32_t copy_dest_bias = (copy_dest_info >> 16) & 0x3F;
|
||||
// assert_true(copy_dest_bias == 0);
|
||||
uint32_t copy_dest_swap = (copy_dest_info >> 25) & 0x1;
|
||||
|
||||
uint32_t copy_dest_base = regs[XE_GPU_REG_RB_COPY_DEST_BASE].u32;
|
||||
uint32_t copy_dest_pitch = regs[XE_GPU_REG_RB_COPY_DEST_PITCH].u32;
|
||||
uint32_t copy_dest_height = (copy_dest_pitch >> 16) & 0x3FFF;
|
||||
copy_dest_pitch &= 0x3FFF;
|
||||
|
||||
// None of this is supported yet:
|
||||
uint32_t copy_surface_slice = regs[XE_GPU_REG_RB_COPY_SURFACE_SLICE].u32;
|
||||
assert_true(copy_surface_slice == 0);
|
||||
uint32_t copy_func = regs[XE_GPU_REG_RB_COPY_FUNC].u32;
|
||||
assert_true(copy_func == 0);
|
||||
uint32_t copy_ref = regs[XE_GPU_REG_RB_COPY_REF].u32;
|
||||
assert_true(copy_ref == 0);
|
||||
uint32_t copy_mask = regs[XE_GPU_REG_RB_COPY_MASK].u32;
|
||||
assert_true(copy_mask == 0);
|
||||
|
||||
// Supported in GL4, not supported here yet.
|
||||
assert_zero(copy_dest_swap);
|
||||
|
||||
// RB_SURFACE_INFO
|
||||
// http://fossies.org/dox/MesaLib-10.3.5/fd2__gmem_8c_source.html
|
||||
uint32_t surface_info = regs[XE_GPU_REG_RB_SURFACE_INFO].u32;
|
||||
uint32_t surface_pitch = surface_info & 0x3FFF;
|
||||
auto surface_msaa = static_cast<MsaaSamples>((surface_info >> 16) & 0x3);
|
||||
|
||||
// TODO(benvanik): any way to scissor this? a200 has:
|
||||
// REG_A2XX_RB_COPY_DEST_OFFSET = A2XX_RB_COPY_DEST_OFFSET_X(tile->xoff) |
|
||||
// A2XX_RB_COPY_DEST_OFFSET_Y(tile->yoff);
|
||||
// but I can't seem to find something similar.
|
||||
uint32_t dest_logical_width = copy_dest_pitch;
|
||||
uint32_t dest_logical_height = copy_dest_height;
|
||||
uint32_t dest_block_width = xe::round_up(dest_logical_width, 32);
|
||||
uint32_t dest_block_height = xe::round_up(dest_logical_height, 32);
|
||||
|
||||
uint32_t window_offset = regs[XE_GPU_REG_PA_SC_WINDOW_OFFSET].u32;
|
||||
int16_t window_offset_x = window_offset & 0x7FFF;
|
||||
int16_t window_offset_y = (window_offset >> 16) & 0x7FFF;
|
||||
// Sign-extension
|
||||
if (window_offset_x & 0x4000) {
|
||||
window_offset_x |= 0x8000;
|
||||
}
|
||||
if (window_offset_y & 0x4000) {
|
||||
window_offset_y |= 0x8000;
|
||||
}
|
||||
|
||||
size_t read_size = GetTexelSize(ColorFormatToTextureFormat(copy_dest_format));
|
||||
|
||||
// Adjust the copy base offset to point to the beginning of the texture, so
|
||||
// we don't run into hiccups down the road (e.g. resolving the last part going
|
||||
// backwards).
|
||||
int32_t dest_offset = window_offset_y * copy_dest_pitch * int(read_size);
|
||||
dest_offset += window_offset_x * 32 * int(read_size);
|
||||
copy_dest_base += dest_offset;
|
||||
|
||||
// HACK: vertices to use are always in vf0.
|
||||
int copy_vertex_fetch_slot = 0;
|
||||
int r =
|
||||
XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0 + (copy_vertex_fetch_slot / 3) * 6;
|
||||
const auto group = reinterpret_cast<xe_gpu_fetch_group_t*>(®s.values[r]);
|
||||
const xe_gpu_vertex_fetch_t* fetch = nullptr;
|
||||
switch (copy_vertex_fetch_slot % 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;
|
||||
}
|
||||
assert_true(fetch->type == 3);
|
||||
assert_true(fetch->endian == 2);
|
||||
assert_true(fetch->size == 6);
|
||||
const uint8_t* vertex_addr = memory_->TranslatePhysical(fetch->address << 2);
|
||||
trace_writer_.WriteMemoryRead(fetch->address << 2, fetch->size * 4);
|
||||
int32_t dest_min_x = int32_t((std::min(
|
||||
std::min(
|
||||
GpuSwap(xe::load<float>(vertex_addr + 0), Endian(fetch->endian)),
|
||||
GpuSwap(xe::load<float>(vertex_addr + 8), Endian(fetch->endian))),
|
||||
GpuSwap(xe::load<float>(vertex_addr + 16), Endian(fetch->endian)))));
|
||||
int32_t dest_max_x = int32_t((std::max(
|
||||
std::max(
|
||||
GpuSwap(xe::load<float>(vertex_addr + 0), Endian(fetch->endian)),
|
||||
GpuSwap(xe::load<float>(vertex_addr + 8), Endian(fetch->endian))),
|
||||
GpuSwap(xe::load<float>(vertex_addr + 16), Endian(fetch->endian)))));
|
||||
int32_t dest_min_y = int32_t((std::min(
|
||||
std::min(
|
||||
GpuSwap(xe::load<float>(vertex_addr + 4), Endian(fetch->endian)),
|
||||
GpuSwap(xe::load<float>(vertex_addr + 12), Endian(fetch->endian))),
|
||||
GpuSwap(xe::load<float>(vertex_addr + 20), Endian(fetch->endian)))));
|
||||
int32_t dest_max_y = int32_t((std::max(
|
||||
std::max(
|
||||
GpuSwap(xe::load<float>(vertex_addr + 4), Endian(fetch->endian)),
|
||||
GpuSwap(xe::load<float>(vertex_addr + 12), Endian(fetch->endian))),
|
||||
GpuSwap(xe::load<float>(vertex_addr + 20), Endian(fetch->endian)))));
|
||||
|
||||
uint32_t color_edram_base = 0;
|
||||
uint32_t depth_edram_base = 0;
|
||||
ColorRenderTargetFormat color_format;
|
||||
DepthRenderTargetFormat depth_format;
|
||||
if (copy_src_select <= 3) {
|
||||
// Source from a color target.
|
||||
uint32_t color_info[4] = {
|
||||
regs[XE_GPU_REG_RB_COLOR_INFO].u32, regs[XE_GPU_REG_RB_COLOR1_INFO].u32,
|
||||
regs[XE_GPU_REG_RB_COLOR2_INFO].u32,
|
||||
regs[XE_GPU_REG_RB_COLOR3_INFO].u32,
|
||||
};
|
||||
color_edram_base = color_info[copy_src_select] & 0xFFF;
|
||||
|
||||
color_format = static_cast<ColorRenderTargetFormat>(
|
||||
(color_info[copy_src_select] >> 16) & 0xF);
|
||||
}
|
||||
|
||||
if (copy_src_select > 3 || depth_clear_enabled) {
|
||||
// Source from a depth target.
|
||||
uint32_t depth_info = regs[XE_GPU_REG_RB_DEPTH_INFO].u32;
|
||||
depth_edram_base = depth_info & 0xFFF;
|
||||
|
||||
depth_format =
|
||||
static_cast<DepthRenderTargetFormat>((depth_info >> 16) & 0x1);
|
||||
}
|
||||
|
||||
// Demand a resolve texture from the texture cache.
|
||||
TextureInfo tex_info = {};
|
||||
tex_info.guest_address = copy_dest_base;
|
||||
tex_info.width = dest_logical_width - 1;
|
||||
tex_info.height = dest_logical_height - 1;
|
||||
tex_info.dimension = gpu::Dimension::k2D;
|
||||
tex_info.input_length = copy_dest_pitch * copy_dest_height * 4;
|
||||
tex_info.format_info =
|
||||
FormatInfo::Get(uint32_t(ColorFormatToTextureFormat(copy_dest_format)));
|
||||
tex_info.size_2d.logical_width = dest_logical_width;
|
||||
tex_info.size_2d.logical_height = dest_logical_height;
|
||||
tex_info.size_2d.block_width = dest_block_width;
|
||||
tex_info.size_2d.block_height = dest_block_height;
|
||||
tex_info.size_2d.input_width = dest_block_width;
|
||||
tex_info.size_2d.input_height = dest_block_height;
|
||||
tex_info.size_2d.input_pitch = copy_dest_pitch * 4;
|
||||
auto texture = texture_cache_->DemandResolveTexture(
|
||||
tex_info, ColorFormatToTextureFormat(copy_dest_format), nullptr);
|
||||
assert_not_null(texture);
|
||||
texture->in_flight_fence = current_batch_fence_;
|
||||
|
||||
// For debugging purposes only (trace viewer)
|
||||
last_copy_base_ = texture->texture_info.guest_address;
|
||||
|
||||
if (!current_command_buffer_) {
|
||||
command_buffer_pool_->BeginBatch();
|
||||
current_command_buffer_ = command_buffer_pool_->AcquireEntry();
|
||||
current_setup_buffer_ = command_buffer_pool_->AcquireEntry();
|
||||
current_batch_fence_.reset(new ui::vulkan::Fence(*device_));
|
||||
|
||||
VkCommandBufferBeginInfo command_buffer_begin_info;
|
||||
command_buffer_begin_info.sType =
|
||||
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
command_buffer_begin_info.pNext = nullptr;
|
||||
command_buffer_begin_info.flags =
|
||||
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
command_buffer_begin_info.pInheritanceInfo = nullptr;
|
||||
auto status = vkBeginCommandBuffer(current_command_buffer_,
|
||||
&command_buffer_begin_info);
|
||||
CheckResult(status, "vkBeginCommandBuffer");
|
||||
|
||||
status =
|
||||
vkBeginCommandBuffer(current_setup_buffer_, &command_buffer_begin_info);
|
||||
CheckResult(status, "vkBeginCommandBuffer");
|
||||
} else if (current_render_state_) {
|
||||
render_cache_->EndRenderPass();
|
||||
current_render_state_ = nullptr;
|
||||
}
|
||||
auto command_buffer = current_command_buffer_;
|
||||
|
||||
if (texture->image_layout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
// Transition the image to a general layout.
|
||||
VkImageMemoryBarrier image_barrier;
|
||||
image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
image_barrier.pNext = nullptr;
|
||||
image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.srcAccessMask = 0;
|
||||
image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
image_barrier.image = texture->image;
|
||||
image_barrier.subresourceRange = {0, 0, 1, 0, 1};
|
||||
image_barrier.subresourceRange.aspectMask =
|
||||
copy_src_select <= 3
|
||||
? VK_IMAGE_ASPECT_COLOR_BIT
|
||||
: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
texture->image_layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &image_barrier);
|
||||
}
|
||||
|
||||
VkOffset3D resolve_offset = {dest_min_x, dest_min_y, 0};
|
||||
VkExtent3D resolve_extent = {uint32_t(dest_max_x - dest_min_x),
|
||||
uint32_t(dest_max_y - dest_min_y), 1};
|
||||
|
||||
// Ask the render cache to copy to the resolve texture.
|
||||
auto edram_base = copy_src_select <= 3 ? color_edram_base : depth_edram_base;
|
||||
uint32_t src_format = copy_src_select <= 3
|
||||
? static_cast<uint32_t>(color_format)
|
||||
: static_cast<uint32_t>(depth_format);
|
||||
switch (copy_command) {
|
||||
case CopyCommand::kRaw:
|
||||
/*
|
||||
render_cache_->RawCopyToImage(command_buffer, edram_base, texture->image,
|
||||
texture->image_layout, copy_src_select <= 3,
|
||||
resolve_offset, resolve_extent);
|
||||
break;
|
||||
*/
|
||||
case CopyCommand::kConvert:
|
||||
render_cache_->BlitToImage(
|
||||
command_buffer, edram_base, surface_pitch, resolve_extent.height,
|
||||
surface_msaa, texture->image, texture->image_layout,
|
||||
copy_src_select <= 3, src_format, VK_FILTER_LINEAR, resolve_offset,
|
||||
resolve_extent);
|
||||
break;
|
||||
|
||||
case CopyCommand::kConstantOne:
|
||||
case CopyCommand::kNull:
|
||||
assert_always();
|
||||
break;
|
||||
}
|
||||
|
||||
// Perform any requested clears.
|
||||
uint32_t copy_depth_clear = regs[XE_GPU_REG_RB_DEPTH_CLEAR].u32;
|
||||
uint32_t copy_color_clear = regs[XE_GPU_REG_RB_COLOR_CLEAR].u32;
|
||||
uint32_t copy_color_clear_low = regs[XE_GPU_REG_RB_COLOR_CLEAR_LOW].u32;
|
||||
assert_true(copy_color_clear == copy_color_clear_low);
|
||||
|
||||
if (color_clear_enabled) {
|
||||
// If color clear is enabled, we can only clear a selected color target!
|
||||
assert_true(copy_src_select <= 3);
|
||||
|
||||
// TODO(benvanik): verify color order.
|
||||
float color[] = {((copy_color_clear >> 0) & 0xFF) / 255.0f,
|
||||
((copy_color_clear >> 8) & 0xFF) / 255.0f,
|
||||
((copy_color_clear >> 16) & 0xFF) / 255.0f,
|
||||
((copy_color_clear >> 24) & 0xFF) / 255.0f};
|
||||
|
||||
// TODO(DrChat): Do we know the surface height at this point?
|
||||
render_cache_->ClearEDRAMColor(command_buffer, color_edram_base,
|
||||
color_format, surface_pitch,
|
||||
resolve_extent.height, surface_msaa, color);
|
||||
}
|
||||
|
||||
if (depth_clear_enabled) {
|
||||
float depth =
|
||||
(copy_depth_clear & 0xFFFFFF00) / static_cast<float>(0xFFFFFF00);
|
||||
uint8_t stencil = copy_depth_clear & 0xFF;
|
||||
|
||||
// TODO(DrChat): Do we know the surface height at this point?
|
||||
render_cache_->ClearEDRAMDepthStencil(
|
||||
command_buffer, depth_edram_base, depth_format, surface_pitch,
|
||||
resolve_extent.height, surface_msaa, depth, stencil);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,12 +34,14 @@
|
|||
#include "xenia/ui/vulkan/fenced_pools.h"
|
||||
#include "xenia/ui/vulkan/vulkan_context.h"
|
||||
#include "xenia/ui/vulkan/vulkan_device.h"
|
||||
#include "xenia/ui/vulkan/vulkan_util.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace vulkan {
|
||||
|
||||
class VulkanGraphicsSystem;
|
||||
class TextureCache;
|
||||
|
||||
class VulkanCommandProcessor : public CommandProcessor {
|
||||
public:
|
||||
|
@ -47,8 +49,11 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
kernel::KernelState* kernel_state);
|
||||
~VulkanCommandProcessor() override;
|
||||
|
||||
virtual void RequestFrameTrace(const std::wstring& root_path) override;
|
||||
void ClearCaches() override;
|
||||
|
||||
RenderCache* render_cache() { return render_cache_.get(); }
|
||||
|
||||
private:
|
||||
bool SetupContext() override;
|
||||
void ShutdownContext() override;
|
||||
|
@ -57,6 +62,9 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
void PrepareForWait() override;
|
||||
void ReturnFromWait() override;
|
||||
|
||||
void CreateSwapImages(VkCommandBuffer setup_buffer, VkExtent2D extents);
|
||||
void DestroySwapImages();
|
||||
|
||||
void PerformSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) override;
|
||||
|
||||
|
@ -74,12 +82,17 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
bool PopulateVertexBuffers(VkCommandBuffer command_buffer,
|
||||
VulkanShader* vertex_shader);
|
||||
bool PopulateSamplers(VkCommandBuffer command_buffer,
|
||||
VkCommandBuffer setup_buffer,
|
||||
VulkanShader* vertex_shader,
|
||||
VulkanShader* pixel_shader);
|
||||
bool IssueCopy() override;
|
||||
|
||||
xe::ui::vulkan::VulkanDevice* device_ = nullptr;
|
||||
|
||||
// front buffer / back buffer memory
|
||||
VkDeviceMemory fb_memory = nullptr;
|
||||
VkDeviceMemory bb_memory = nullptr;
|
||||
|
||||
// TODO(benvanik): abstract behind context?
|
||||
// Queue used to submit work. This may be a dedicated queue for the command
|
||||
// processor and no locking will be required for use. If a dedicated queue
|
||||
|
@ -88,12 +101,22 @@ class VulkanCommandProcessor : public CommandProcessor {
|
|||
VkQueue queue_ = nullptr;
|
||||
std::mutex* queue_mutex_ = nullptr;
|
||||
|
||||
// Last copy base address, for debugging only.
|
||||
uint32_t last_copy_base_ = 0;
|
||||
bool capturing_ = false;
|
||||
bool trace_requested_ = false;
|
||||
|
||||
std::unique_ptr<BufferCache> buffer_cache_;
|
||||
std::unique_ptr<PipelineCache> pipeline_cache_;
|
||||
std::unique_ptr<RenderCache> render_cache_;
|
||||
std::unique_ptr<TextureCache> texture_cache_;
|
||||
|
||||
std::unique_ptr<ui::vulkan::CommandBufferPool> command_buffer_pool_;
|
||||
|
||||
const RenderState* current_render_state_ = nullptr;
|
||||
VkCommandBuffer current_command_buffer_ = nullptr;
|
||||
VkCommandBuffer current_setup_buffer_ = nullptr;
|
||||
std::shared_ptr<ui::vulkan::Fence> current_batch_fence_;
|
||||
};
|
||||
|
||||
} // namespace vulkan
|
||||
|
|
|
@ -11,3 +11,6 @@
|
|||
|
||||
DEFINE_bool(vulkan_renderdoc_capture_all, false,
|
||||
"Capture everything with RenderDoc.");
|
||||
DEFINE_bool(vulkan_native_msaa, false, "Use native MSAA");
|
||||
DEFINE_bool(vulkan_dump_disasm, false,
|
||||
"Dump shader disassembly. NVIDIA only supported.");
|
||||
|
|
|
@ -15,5 +15,7 @@
|
|||
#define FINE_GRAINED_DRAW_SCOPES 1
|
||||
|
||||
DECLARE_bool(vulkan_renderdoc_capture_all);
|
||||
DECLARE_bool(vulkan_native_msaa);
|
||||
DECLARE_bool(vulkan_dump_disasm);
|
||||
|
||||
#endif // XENIA_GPU_VULKAN_VULKAN_GPU_FLAGS_H_
|
||||
|
|
|
@ -19,14 +19,14 @@
|
|||
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
|
||||
#include "xenia/gpu/vulkan/vulkan_gpu_flags.h"
|
||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||
#include "xenia/ui/vulkan/vulkan_swap_chain.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
namespace vulkan {
|
||||
|
||||
VulkanGraphicsSystem::VulkanGraphicsSystem() = default;
|
||||
|
||||
VulkanGraphicsSystem::VulkanGraphicsSystem() {}
|
||||
VulkanGraphicsSystem::~VulkanGraphicsSystem() = default;
|
||||
|
||||
X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor,
|
||||
|
@ -74,12 +74,41 @@ void VulkanGraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Blit the frontbuffer.
|
||||
// display_context_->blitter()->BlitTexture2D(
|
||||
// static_cast<GLuint>(swap_state.front_buffer_texture),
|
||||
// Rect2D(0, 0, swap_state.width, swap_state.height),
|
||||
// Rect2D(0, 0, target_window_->width(), target_window_->height()),
|
||||
// GL_LINEAR, false);
|
||||
auto swap_chain = display_context_->swap_chain();
|
||||
auto copy_cmd_buffer = swap_chain->copy_cmd_buffer();
|
||||
auto front_buffer =
|
||||
reinterpret_cast<VkImage>(swap_state.front_buffer_texture);
|
||||
|
||||
VkImageMemoryBarrier barrier;
|
||||
std::memset(&barrier, 0, sizeof(VkImageMemoryBarrier));
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = front_buffer;
|
||||
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkCmdPipelineBarrier(copy_cmd_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &barrier);
|
||||
|
||||
VkImageBlit region;
|
||||
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.srcOffsets[0] = {0, 0, 0};
|
||||
region.srcOffsets[1] = {static_cast<int32_t>(swap_state.width),
|
||||
static_cast<int32_t>(swap_state.height), 1};
|
||||
|
||||
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
||||
region.dstOffsets[0] = {0, 0, 0};
|
||||
region.dstOffsets[1] = {static_cast<int32_t>(swap_chain->surface_width()),
|
||||
static_cast<int32_t>(swap_chain->surface_height()),
|
||||
1};
|
||||
vkCmdBlitImage(copy_cmd_buffer, front_buffer, VK_IMAGE_LAYOUT_GENERAL,
|
||||
swap_chain->surface_image(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion,
|
||||
VK_FILTER_LINEAR);
|
||||
}
|
||||
|
||||
} // namespace vulkan
|
||||
|
|
|
@ -44,11 +44,11 @@ bool VulkanShader::Prepare() {
|
|||
shader_info.codeSize = translated_binary_.size();
|
||||
shader_info.pCode =
|
||||
reinterpret_cast<const uint32_t*>(translated_binary_.data());
|
||||
auto err =
|
||||
auto status =
|
||||
vkCreateShaderModule(device_, &shader_info, nullptr, &shader_module_);
|
||||
CheckResult(err, "vkCreateShaderModule");
|
||||
CheckResult(status, "vkCreateShaderModule");
|
||||
|
||||
return true;
|
||||
return status == VK_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace vulkan
|
||||
|
|
|
@ -49,6 +49,7 @@ enum class PrimitiveType : uint32_t {
|
|||
kLineLoop = 0x0C,
|
||||
kQuadList = 0x0D,
|
||||
kQuadStrip = 0x0E,
|
||||
kUnknown0x11 = 0x11,
|
||||
};
|
||||
|
||||
enum class Dimension : uint32_t {
|
||||
|
@ -382,7 +383,7 @@ XEPACKEDUNION(xe_gpu_vertex_fetch_t, {
|
|||
uint32_t type : 2;
|
||||
uint32_t address : 30;
|
||||
uint32_t endian : 2;
|
||||
uint32_t size : 24;
|
||||
uint32_t size : 24; // size in words
|
||||
uint32_t unk1 : 6;
|
||||
});
|
||||
XEPACKEDSTRUCTANONYMOUS({
|
||||
|
@ -486,6 +487,46 @@ XEPACKEDUNION(xe_gpu_fetch_group_t, {
|
|||
});
|
||||
});
|
||||
|
||||
enum Event {
|
||||
SAMPLE_STREAMOUTSTATS1 = (1 << 0),
|
||||
SAMPLE_STREAMOUTSTATS2 = (2 << 0),
|
||||
SAMPLE_STREAMOUTSTATS3 = (3 << 0),
|
||||
CACHE_FLUSH_TS = (4 << 0),
|
||||
CACHE_FLUSH = (6 << 0),
|
||||
CS_PARTIAL_FLUSH = (7 << 0),
|
||||
VGT_STREAMOUT_RESET = (10 << 0),
|
||||
END_OF_PIPE_INCR_DE = (11 << 0),
|
||||
END_OF_PIPE_IB_END = (12 << 0),
|
||||
RST_PIX_CNT = (13 << 0),
|
||||
VS_PARTIAL_FLUSH = (15 << 0),
|
||||
PS_PARTIAL_FLUSH = (16 << 0),
|
||||
CACHE_FLUSH_AND_INV_TS_EVENT = (20 << 0),
|
||||
ZPASS_DONE = (21 << 0),
|
||||
CACHE_FLUSH_AND_INV_EVENT = (22 << 0),
|
||||
PERFCOUNTER_START = (23 << 0),
|
||||
PERFCOUNTER_STOP = (24 << 0),
|
||||
PIPELINESTAT_START = (25 << 0),
|
||||
PIPELINESTAT_STOP = (26 << 0),
|
||||
PERFCOUNTER_SAMPLE = (27 << 0),
|
||||
SAMPLE_PIPELINESTAT = (30 << 0),
|
||||
SAMPLE_STREAMOUTSTATS = (32 << 0),
|
||||
RESET_VTX_CNT = (33 << 0),
|
||||
VGT_FLUSH = (36 << 0),
|
||||
BOTTOM_OF_PIPE_TS = (40 << 0),
|
||||
DB_CACHE_FLUSH_AND_INV = (42 << 0),
|
||||
FLUSH_AND_INV_DB_DATA_TS = (43 << 0),
|
||||
FLUSH_AND_INV_DB_META = (44 << 0),
|
||||
FLUSH_AND_INV_CB_DATA_TS = (45 << 0),
|
||||
FLUSH_AND_INV_CB_META = (46 << 0),
|
||||
CS_DONE = (47 << 0),
|
||||
PS_DONE = (48 << 0),
|
||||
FLUSH_AND_INV_CB_PIXEL_DATA = (49 << 0),
|
||||
THREAD_TRACE_START = (51 << 0),
|
||||
THREAD_TRACE_STOP = (52 << 0),
|
||||
THREAD_TRACE_FLUSH = (54 << 0),
|
||||
THREAD_TRACE_FINISH = (55 << 0),
|
||||
};
|
||||
|
||||
// Opcodes (IT_OPCODE) for Type-3 commands in the ringbuffer.
|
||||
// https://github.com/freedreno/amd-gpu/blob/master/include/api/gsl_pm4types.h
|
||||
// Not sure if all of these are used.
|
||||
|
@ -501,7 +542,7 @@ enum Type3Opcode {
|
|||
PM4_WAIT_FOR_IDLE = 0x26, // wait for the IDLE state of the engine
|
||||
PM4_WAIT_REG_MEM = 0x3c, // wait until a register or memory location is a specific value
|
||||
PM4_WAIT_REG_EQ = 0x52, // wait until a register location is equal to a specific value
|
||||
PM4_WAT_REG_GTE = 0x53, // wait until a register location is >= a specific value
|
||||
PM4_WAIT_REG_GTE = 0x53, // wait until a register location is >= a specific value
|
||||
PM4_WAIT_UNTIL_READ = 0x5c, // wait until a read completes
|
||||
PM4_WAIT_IB_PFD_COMPLETE = 0x5d, // wait until all base/size writes from an IB_PFD packet have completed
|
||||
|
||||
|
|
|
@ -366,7 +366,7 @@ void VdSwap(lpvoid_t buffer_ptr, // ptr into primary ringbuffer
|
|||
auto dwords = buffer_ptr.as_array<uint32_t>();
|
||||
dwords[0] = xenos::MakePacketType3<xenos::PM4_XE_SWAP, 63>();
|
||||
dwords[1] = 'SWAP';
|
||||
dwords[2] = *frontbuffer_ptr;
|
||||
dwords[2] = (*frontbuffer_ptr) & 0x1FFFFFFF;
|
||||
|
||||
// Set by VdCallGraphicsNotificationRoutines.
|
||||
dwords[3] = last_frontbuffer_width_;
|
||||
|
|
|
@ -376,17 +376,19 @@ cpu::MMIORange* Memory::LookupVirtualMappedRange(uint32_t virtual_address) {
|
|||
return mmio_handler_->LookupRange(virtual_address);
|
||||
}
|
||||
|
||||
uintptr_t Memory::AddPhysicalWriteWatch(uint32_t physical_address,
|
||||
uint32_t length,
|
||||
cpu::WriteWatchCallback callback,
|
||||
void* callback_context,
|
||||
void* callback_data) {
|
||||
return mmio_handler_->AddPhysicalWriteWatch(
|
||||
physical_address, length, callback, callback_context, callback_data);
|
||||
uintptr_t Memory::AddPhysicalAccessWatch(uint32_t physical_address,
|
||||
uint32_t length,
|
||||
cpu::MMIOHandler::WatchType type,
|
||||
cpu::AccessWatchCallback callback,
|
||||
void* callback_context,
|
||||
void* callback_data) {
|
||||
return mmio_handler_->AddPhysicalAccessWatch(physical_address, length, type,
|
||||
callback, callback_context,
|
||||
callback_data);
|
||||
}
|
||||
|
||||
void Memory::CancelWriteWatch(uintptr_t watch_handle) {
|
||||
mmio_handler_->CancelWriteWatch(watch_handle);
|
||||
void Memory::CancelAccessWatch(uintptr_t watch_handle) {
|
||||
mmio_handler_->CancelAccessWatch(watch_handle);
|
||||
}
|
||||
|
||||
uint32_t Memory::SystemHeapAlloc(uint32_t size, uint32_t alignment,
|
||||
|
@ -453,6 +455,7 @@ bool Memory::Save(ByteStream* stream) {
|
|||
}
|
||||
|
||||
bool Memory::Restore(ByteStream* stream) {
|
||||
XELOGD("Restoring memory...");
|
||||
heaps_.v00000000.Restore(stream);
|
||||
heaps_.v40000000.Restore(stream);
|
||||
heaps_.v80000000.Restore(stream);
|
||||
|
@ -577,6 +580,8 @@ bool BaseHeap::Save(ByteStream* stream) {
|
|||
}
|
||||
|
||||
bool BaseHeap::Restore(ByteStream* stream) {
|
||||
XELOGD("Heap %.8X-%.8X", heap_base_, heap_base_ + heap_size_);
|
||||
|
||||
for (size_t i = 0; i < page_table_.size(); i++) {
|
||||
auto& page = page_table_[i];
|
||||
page.qword = stream->Read<uint64_t>();
|
||||
|
@ -897,7 +902,7 @@ bool BaseHeap::Release(uint32_t base_address, uint32_t* out_region_size) {
|
|||
auto base_page_entry = page_table_[base_page_number];
|
||||
if (base_page_entry.base_address != base_page_number) {
|
||||
XELOGE("BaseHeap::Release failed because address is not a region start");
|
||||
// return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_region_size) {
|
||||
|
|
|
@ -303,12 +303,13 @@ class Memory {
|
|||
//
|
||||
// This has a significant performance penalty for writes in in the range or
|
||||
// nearby (sharing 64KiB pages).
|
||||
uintptr_t AddPhysicalWriteWatch(uint32_t physical_address, uint32_t length,
|
||||
cpu::WriteWatchCallback callback,
|
||||
void* callback_context, void* callback_data);
|
||||
uintptr_t AddPhysicalAccessWatch(uint32_t physical_address, uint32_t length,
|
||||
cpu::MMIOHandler::WatchType type,
|
||||
cpu::AccessWatchCallback callback,
|
||||
void* callback_context, void* callback_data);
|
||||
|
||||
// Cancels a write watch requested with AddPhysicalWriteWatch.
|
||||
void CancelWriteWatch(uintptr_t watch_handle);
|
||||
// Cancels a write watch requested with AddPhysicalAccessWatch.
|
||||
void CancelAccessWatch(uintptr_t watch_handle);
|
||||
|
||||
// Allocates virtual memory from the 'system' heap.
|
||||
// System memory is kept separate from game memory but is still accessible
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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/ui/spirv/spirv_validator.h"
|
||||
|
||||
#include "third_party/spirv-tools/include/spirv-tools/libspirv.h"
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace spirv {
|
||||
|
||||
SpirvValidator::Result::Result(spv_text text, spv_diagnostic diagnostic)
|
||||
: text_(text), diagnostic_(diagnostic) {}
|
||||
|
||||
SpirvValidator::Result::~Result() {
|
||||
if (text_) {
|
||||
spvTextDestroy(text_);
|
||||
}
|
||||
if (diagnostic_) {
|
||||
spvDiagnosticDestroy(diagnostic_);
|
||||
}
|
||||
}
|
||||
|
||||
bool SpirvValidator::Result::has_error() const { return !!diagnostic_; }
|
||||
|
||||
size_t SpirvValidator::Result::error_word_index() const {
|
||||
return diagnostic_ ? diagnostic_->position.index : 0;
|
||||
}
|
||||
|
||||
const char* SpirvValidator::Result::error_string() const {
|
||||
return diagnostic_ ? diagnostic_->error : "";
|
||||
}
|
||||
|
||||
const char* SpirvValidator::Result::text() const {
|
||||
return text_ ? text_->str : "";
|
||||
}
|
||||
|
||||
std::string SpirvValidator::Result::to_string() const {
|
||||
return text_ ? std::string(text_->str, text_->length) : "";
|
||||
}
|
||||
|
||||
void SpirvValidator::Result::AppendText(StringBuffer* target_buffer) const {
|
||||
if (text_) {
|
||||
target_buffer->AppendBytes(reinterpret_cast<const uint8_t*>(text_->str),
|
||||
text_->length);
|
||||
}
|
||||
}
|
||||
|
||||
SpirvValidator::SpirvValidator() : spv_context_(spvContextCreate()) {}
|
||||
SpirvValidator::~SpirvValidator() { spvContextDestroy(spv_context_); }
|
||||
|
||||
std::unique_ptr<SpirvValidator::Result> SpirvValidator::Validate(
|
||||
const uint32_t* words, size_t word_count) {
|
||||
spv_text text = nullptr;
|
||||
spv_diagnostic diagnostic = nullptr;
|
||||
spv_const_binary_t binary = {words, word_count};
|
||||
auto result_code =
|
||||
spvValidate(spv_context_, &binary, SPV_VALIDATE_ALL, &diagnostic);
|
||||
std::unique_ptr<Result> result(new Result(text, diagnostic));
|
||||
if (result_code) {
|
||||
XELOGE("Failed to validate spv: %d", result_code);
|
||||
if (result->has_error()) {
|
||||
return result;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace ui
|
||||
} // namespace xe
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_UI_SPIRV_SPIRV_VALIDATOR_H_
|
||||
#define XENIA_UI_SPIRV_SPIRV_VALIDATOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/string_buffer.h"
|
||||
#include "xenia/ui/spirv/spirv_util.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace spirv {
|
||||
|
||||
class SpirvValidator {
|
||||
public:
|
||||
class Result {
|
||||
public:
|
||||
Result(spv_text text, spv_diagnostic diagnostic);
|
||||
~Result();
|
||||
|
||||
// True if the result has an error associated with it.
|
||||
bool has_error() const;
|
||||
// Index of the error in the provided binary word data.
|
||||
size_t error_word_index() const;
|
||||
// Human-readable description of the error.
|
||||
const char* error_string() const;
|
||||
|
||||
// Disassembled source text.
|
||||
// Returned pointer lifetime is tied to this Result instance.
|
||||
const char* text() const;
|
||||
// Converts the disassembled source text to a string.
|
||||
std::string to_string() const;
|
||||
// Appends the disassembled source text to the given buffer.
|
||||
void AppendText(StringBuffer* target_buffer) const;
|
||||
|
||||
private:
|
||||
spv_text text_ = nullptr;
|
||||
spv_diagnostic diagnostic_ = nullptr;
|
||||
};
|
||||
|
||||
SpirvValidator();
|
||||
~SpirvValidator();
|
||||
|
||||
// Validates the given SPIRV binary.
|
||||
// The return will be nullptr if validation fails due to a library error.
|
||||
// The return may have an error set on it if the SPIRV binary is malformed.
|
||||
std::unique_ptr<Result> Validate(const uint32_t* words, size_t word_count);
|
||||
|
||||
private:
|
||||
spv_context spv_context_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace spirv
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_SPIRV_SPIRV_VALIDATOR_H_
|
|
@ -0,0 +1,227 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
|
||||
#include "xenia/ui/vulkan/circular_buffer.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
CircularBuffer::CircularBuffer(VulkanDevice* device) : device_(device) {}
|
||||
CircularBuffer::~CircularBuffer() { Shutdown(); }
|
||||
|
||||
bool CircularBuffer::Initialize(VkDeviceSize capacity, VkBufferUsageFlags usage,
|
||||
VkDeviceSize alignment) {
|
||||
VkResult status = VK_SUCCESS;
|
||||
capacity = xe::round_up(capacity, alignment);
|
||||
|
||||
// Create our internal buffer.
|
||||
VkBufferCreateInfo buffer_info;
|
||||
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
buffer_info.pNext = nullptr;
|
||||
buffer_info.flags = 0;
|
||||
buffer_info.size = capacity;
|
||||
buffer_info.usage = usage;
|
||||
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
buffer_info.queueFamilyIndexCount = 0;
|
||||
buffer_info.pQueueFamilyIndices = nullptr;
|
||||
status = vkCreateBuffer(*device_, &buffer_info, nullptr, &gpu_buffer_);
|
||||
CheckResult(status, "vkCreateBuffer");
|
||||
if (status != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkMemoryRequirements reqs;
|
||||
vkGetBufferMemoryRequirements(*device_, gpu_buffer_, &reqs);
|
||||
|
||||
// Allocate memory from the device to back the buffer.
|
||||
assert_true(reqs.size == capacity);
|
||||
reqs.alignment = std::max(alignment, reqs.alignment);
|
||||
gpu_memory_ = device_->AllocateMemory(reqs);
|
||||
if (!gpu_memory_) {
|
||||
XELOGE("CircularBuffer::Initialize - Failed to allocate memory!");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
alignment_ = reqs.alignment;
|
||||
capacity_ = reqs.size;
|
||||
gpu_base_ = 0;
|
||||
|
||||
// Bind the buffer to its backing memory.
|
||||
status = vkBindBufferMemory(*device_, gpu_buffer_, gpu_memory_, gpu_base_);
|
||||
CheckResult(status, "vkBindBufferMemory");
|
||||
if (status != VK_SUCCESS) {
|
||||
XELOGE("CircularBuffer::Initialize - Failed to bind memory!");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Map the memory so we can access it.
|
||||
status = vkMapMemory(*device_, gpu_memory_, gpu_base_, capacity_, 0,
|
||||
reinterpret_cast<void**>(&host_base_));
|
||||
CheckResult(status, "vkMapMemory");
|
||||
if (status != VK_SUCCESS) {
|
||||
XELOGE("CircularBuffer::Initialize - Failed to map memory!");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CircularBuffer::Shutdown() {
|
||||
Clear();
|
||||
if (host_base_) {
|
||||
vkUnmapMemory(*device_, gpu_memory_);
|
||||
host_base_ = nullptr;
|
||||
}
|
||||
if (gpu_buffer_) {
|
||||
vkDestroyBuffer(*device_, gpu_buffer_, nullptr);
|
||||
gpu_buffer_ = nullptr;
|
||||
}
|
||||
if (gpu_memory_) {
|
||||
vkFreeMemory(*device_, gpu_memory_, nullptr);
|
||||
gpu_memory_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool CircularBuffer::CanAcquire(VkDeviceSize length) {
|
||||
// Make sure the length is aligned.
|
||||
length = xe::round_up(length, alignment_);
|
||||
if (allocations_.empty()) {
|
||||
// Read head has caught up to write head (entire buffer available for write)
|
||||
assert_true(read_head_ == write_head_);
|
||||
return capacity_ >= length;
|
||||
} else if (write_head_ < read_head_) {
|
||||
// Write head wrapped around and is behind read head.
|
||||
// | write |---- read ----|
|
||||
return (read_head_ - write_head_) >= length;
|
||||
} else if (write_head_ > read_head_) {
|
||||
// Read head behind write head.
|
||||
// 1. Check if there's enough room from write -> capacity
|
||||
// | |---- read ----| write |
|
||||
if ((capacity_ - write_head_) >= length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Check if there's enough room from 0 -> read
|
||||
// | write |---- read ----| |
|
||||
if ((read_head_ - 0) >= length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
CircularBuffer::Allocation* CircularBuffer::Acquire(
|
||||
VkDeviceSize length, std::shared_ptr<Fence> fence) {
|
||||
VkDeviceSize aligned_length = xe::round_up(length, alignment_);
|
||||
if (!CanAcquire(aligned_length)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
assert_true(write_head_ % alignment_ == 0);
|
||||
if (write_head_ < read_head_) {
|
||||
// Write head behind read head.
|
||||
assert_true(read_head_ - write_head_ >= aligned_length);
|
||||
|
||||
auto alloc = new Allocation();
|
||||
alloc->host_ptr = host_base_ + write_head_;
|
||||
alloc->gpu_memory = gpu_memory_;
|
||||
alloc->offset = gpu_base_ + write_head_;
|
||||
alloc->length = length;
|
||||
alloc->aligned_length = aligned_length;
|
||||
alloc->fence = fence;
|
||||
write_head_ += aligned_length;
|
||||
allocations_.push_back(alloc);
|
||||
|
||||
return alloc;
|
||||
} else {
|
||||
// Write head equal to/after read head
|
||||
if (capacity_ - write_head_ >= aligned_length) {
|
||||
// Free space from write -> capacity
|
||||
auto alloc = new Allocation();
|
||||
alloc->host_ptr = host_base_ + write_head_;
|
||||
alloc->gpu_memory = gpu_memory_;
|
||||
alloc->offset = gpu_base_ + write_head_;
|
||||
alloc->length = length;
|
||||
alloc->aligned_length = aligned_length;
|
||||
alloc->fence = fence;
|
||||
write_head_ += aligned_length;
|
||||
allocations_.push_back(alloc);
|
||||
|
||||
return alloc;
|
||||
} else if ((read_head_ - 0) >= aligned_length) {
|
||||
// Free space from begin -> read
|
||||
auto alloc = new Allocation();
|
||||
alloc->host_ptr = host_base_ + 0;
|
||||
alloc->gpu_memory = gpu_memory_;
|
||||
alloc->offset = gpu_base_ + 0;
|
||||
alloc->length = length;
|
||||
alloc->aligned_length = aligned_length;
|
||||
alloc->fence = fence;
|
||||
write_head_ = aligned_length;
|
||||
allocations_.push_back(alloc);
|
||||
|
||||
return alloc;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CircularBuffer::Flush(Allocation* allocation) {
|
||||
VkMappedMemoryRange range;
|
||||
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||
range.pNext = nullptr;
|
||||
range.memory = gpu_memory_;
|
||||
range.offset = gpu_base_ + allocation->offset;
|
||||
range.size = allocation->length;
|
||||
vkFlushMappedMemoryRanges(*device_, 1, &range);
|
||||
}
|
||||
|
||||
void CircularBuffer::Clear() {
|
||||
for (auto alloc : allocations_) {
|
||||
delete alloc;
|
||||
}
|
||||
allocations_.clear();
|
||||
|
||||
write_head_ = read_head_ = 0;
|
||||
}
|
||||
|
||||
void CircularBuffer::Scavenge() {
|
||||
for (auto it = allocations_.begin(); it != allocations_.end();) {
|
||||
if ((*it)->fence->status() != VK_SUCCESS) {
|
||||
// Don't bother freeing following allocations to ensure proper ordering.
|
||||
break;
|
||||
}
|
||||
|
||||
if (capacity_ - read_head_ < (*it)->aligned_length) {
|
||||
// This allocation is stored at the beginning of the buffer.
|
||||
read_head_ = (*it)->aligned_length;
|
||||
} else {
|
||||
read_head_ += (*it)->aligned_length;
|
||||
}
|
||||
|
||||
delete *it;
|
||||
it = allocations_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace ui
|
||||
} // namespace xe
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_VULKAN_CIRCULAR_BUFFER_H_
|
||||
#define XENIA_UI_VULKAN_CIRCULAR_BUFFER_H_
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "xenia/ui/vulkan/vulkan.h"
|
||||
#include "xenia/ui/vulkan/vulkan_device.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
// A circular buffer, intended to hold (fairly) temporary memory that will be
|
||||
// released when a fence is signaled. Best used when allocations are taken
|
||||
// in-order with command buffer submission.
|
||||
//
|
||||
// Allocations loop around the buffer in circles (but are not fragmented at the
|
||||
// ends of the buffer), where trailing older allocations are freed after use.
|
||||
class CircularBuffer {
|
||||
public:
|
||||
CircularBuffer(VulkanDevice* device);
|
||||
~CircularBuffer();
|
||||
|
||||
struct Allocation {
|
||||
void* host_ptr;
|
||||
VkDeviceMemory gpu_memory;
|
||||
VkDeviceSize offset;
|
||||
VkDeviceSize length;
|
||||
VkDeviceSize aligned_length;
|
||||
|
||||
// Allocation usage fence. This allocation will be deleted when the fence
|
||||
// becomes signaled.
|
||||
std::shared_ptr<Fence> fence;
|
||||
};
|
||||
|
||||
bool Initialize(VkDeviceSize capacity, VkBufferUsageFlags usage,
|
||||
VkDeviceSize alignment = 256);
|
||||
void Shutdown();
|
||||
|
||||
VkDeviceSize alignment() const { return alignment_; }
|
||||
VkDeviceSize capacity() const { return capacity_; }
|
||||
VkBuffer gpu_buffer() const { return gpu_buffer_; }
|
||||
VkDeviceMemory gpu_memory() const { return gpu_memory_; }
|
||||
uint8_t* host_base() const { return host_base_; }
|
||||
|
||||
bool CanAcquire(VkDeviceSize length);
|
||||
|
||||
// Acquires space to hold memory. This allocation is only freed when the fence
|
||||
// reaches the signaled state.
|
||||
Allocation* Acquire(VkDeviceSize length, std::shared_ptr<Fence> fence);
|
||||
void Flush(Allocation* allocation);
|
||||
|
||||
// Clears all allocations, regardless of whether they've been consumed or not.
|
||||
void Clear();
|
||||
|
||||
// Frees any allocations whose fences have been signaled.
|
||||
void Scavenge();
|
||||
|
||||
private:
|
||||
VkDeviceSize capacity_ = 0;
|
||||
VkDeviceSize alignment_ = 0;
|
||||
VkDeviceSize write_head_ = 0;
|
||||
VkDeviceSize read_head_ = 0;
|
||||
|
||||
VulkanDevice* device_;
|
||||
VkBuffer gpu_buffer_ = nullptr;
|
||||
VkDeviceMemory gpu_memory_ = nullptr;
|
||||
VkDeviceSize gpu_base_ = 0;
|
||||
uint8_t* host_base_ = nullptr;
|
||||
|
||||
std::list<Allocation*> allocations_;
|
||||
};
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_GL_CIRCULAR_BUFFER_H_
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/ui/vulkan/vulkan.h"
|
||||
#include "xenia/ui/vulkan/vulkan_util.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
|
@ -40,13 +41,15 @@ class BaseFencedPool {
|
|||
|
||||
// True if one or more batches are still pending on the GPU.
|
||||
bool has_pending() const { return pending_batch_list_head_ != nullptr; }
|
||||
// True if a batch is open.
|
||||
bool has_open_batch() const { return open_batch_ != nullptr; }
|
||||
|
||||
// Checks all pending batches for completion and scavenges their entries.
|
||||
// This should be called as frequently as reasonable.
|
||||
void Scavenge() {
|
||||
while (pending_batch_list_head_) {
|
||||
auto batch = pending_batch_list_head_;
|
||||
if (vkGetFenceStatus(device_, batch->fence) == VK_SUCCESS) {
|
||||
if (vkGetFenceStatus(device_, *batch->fence) == VK_SUCCESS) {
|
||||
// Batch has completed. Reclaim.
|
||||
pending_batch_list_head_ = batch->next;
|
||||
if (batch == pending_batch_list_tail_) {
|
||||
|
@ -88,6 +91,24 @@ class BaseFencedPool {
|
|||
open_batch_ = batch;
|
||||
}
|
||||
|
||||
// Cancels an open batch, and releases all entries acquired within.
|
||||
void CancelBatch() {
|
||||
assert_not_null(open_batch_);
|
||||
|
||||
auto batch = open_batch_;
|
||||
open_batch_ = nullptr;
|
||||
|
||||
// Relink the batch back into the free batch list.
|
||||
batch->next = free_batch_list_head_;
|
||||
free_batch_list_head_ = batch;
|
||||
|
||||
// Relink entries back into free entries list.
|
||||
batch->entry_list_tail->next = free_entry_list_head_;
|
||||
free_entry_list_head_ = batch->entry_list_head;
|
||||
batch->entry_list_head = nullptr;
|
||||
batch->entry_list_tail = nullptr;
|
||||
}
|
||||
|
||||
// Attempts to acquire an entry from the pool in the current batch.
|
||||
// If none are available a new one will be allocated.
|
||||
HANDLE AcquireEntry() {
|
||||
|
@ -114,7 +135,7 @@ class BaseFencedPool {
|
|||
|
||||
// Ends the current batch using the given fence to indicate when the batch
|
||||
// has completed execution on the GPU.
|
||||
void EndBatch(VkFence fence) {
|
||||
void EndBatch(std::shared_ptr<Fence> fence) {
|
||||
assert_not_null(open_batch_);
|
||||
|
||||
// Close and see if we have anything.
|
||||
|
@ -137,6 +158,7 @@ class BaseFencedPool {
|
|||
}
|
||||
if (pending_batch_list_tail_) {
|
||||
pending_batch_list_tail_->next = batch;
|
||||
pending_batch_list_tail_ = batch;
|
||||
} else {
|
||||
pending_batch_list_tail_ = batch;
|
||||
}
|
||||
|
@ -176,7 +198,7 @@ class BaseFencedPool {
|
|||
Batch* next;
|
||||
Entry* entry_list_head;
|
||||
Entry* entry_list_tail;
|
||||
VkFence fence;
|
||||
std::shared_ptr<Fence> fence;
|
||||
};
|
||||
|
||||
Batch* free_batch_list_head_ = nullptr;
|
||||
|
|
|
@ -93,8 +93,8 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) {
|
|||
}
|
||||
ENABLE_AND_EXPECT(geometryShader);
|
||||
ENABLE_AND_EXPECT(depthClamp);
|
||||
ENABLE_AND_EXPECT(alphaToOne);
|
||||
ENABLE_AND_EXPECT(multiViewport);
|
||||
ENABLE_AND_EXPECT(independentBlend);
|
||||
// TODO(benvanik): add other features.
|
||||
if (any_features_missing) {
|
||||
XELOGE(
|
||||
|
|
|
@ -136,6 +136,46 @@ class LightweightCircularBuffer {
|
|||
|
||||
class VulkanImmediateTexture : public ImmediateTexture {
|
||||
public:
|
||||
VulkanImmediateTexture(VulkanDevice* device, VkDescriptorPool descriptor_pool,
|
||||
VkDescriptorSetLayout descriptor_set_layout,
|
||||
VkImageView image_view, VkSampler sampler,
|
||||
uint32_t width, uint32_t height)
|
||||
: ImmediateTexture(width, height),
|
||||
device_(*device),
|
||||
descriptor_pool_(descriptor_pool),
|
||||
image_view_(image_view),
|
||||
sampler_(sampler) {
|
||||
handle = reinterpret_cast<uintptr_t>(this);
|
||||
|
||||
// Create descriptor set used just for this texture.
|
||||
// It never changes, so we can reuse it and not worry with updates.
|
||||
VkDescriptorSetAllocateInfo set_alloc_info;
|
||||
set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
set_alloc_info.pNext = nullptr;
|
||||
set_alloc_info.descriptorPool = descriptor_pool_;
|
||||
set_alloc_info.descriptorSetCount = 1;
|
||||
set_alloc_info.pSetLayouts = &descriptor_set_layout;
|
||||
auto err =
|
||||
vkAllocateDescriptorSets(device_, &set_alloc_info, &descriptor_set_);
|
||||
CheckResult(err, "vkAllocateDescriptorSets");
|
||||
|
||||
// Initialize descriptor with our texture.
|
||||
VkDescriptorImageInfo texture_info;
|
||||
texture_info.sampler = sampler_;
|
||||
texture_info.imageView = image_view_;
|
||||
texture_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
VkWriteDescriptorSet descriptor_write;
|
||||
descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptor_write.pNext = nullptr;
|
||||
descriptor_write.dstSet = descriptor_set_;
|
||||
descriptor_write.dstBinding = 0;
|
||||
descriptor_write.dstArrayElement = 0;
|
||||
descriptor_write.descriptorCount = 1;
|
||||
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
descriptor_write.pImageInfo = &texture_info;
|
||||
vkUpdateDescriptorSets(device_, 1, &descriptor_write, 0, nullptr);
|
||||
}
|
||||
|
||||
VulkanImmediateTexture(VulkanDevice* device, VkDescriptorPool descriptor_pool,
|
||||
VkDescriptorSetLayout descriptor_set_layout,
|
||||
VkSampler sampler, uint32_t width, uint32_t height)
|
||||
|
@ -161,7 +201,7 @@ class VulkanImmediateTexture : public ImmediateTexture {
|
|||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_info.queueFamilyIndexCount = 0;
|
||||
image_info.pQueueFamilyIndices = nullptr;
|
||||
image_info.initialLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
image_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
|
||||
auto err = vkCreateImage(device_, &image_info, nullptr, &image_);
|
||||
CheckResult(err, "vkCreateImage");
|
||||
|
||||
|
@ -221,9 +261,12 @@ class VulkanImmediateTexture : public ImmediateTexture {
|
|||
|
||||
~VulkanImmediateTexture() override {
|
||||
vkFreeDescriptorSets(device_, descriptor_pool_, 1, &descriptor_set_);
|
||||
vkDestroyImageView(device_, image_view_, nullptr);
|
||||
vkDestroyImage(device_, image_, nullptr);
|
||||
vkFreeMemory(device_, device_memory_, nullptr);
|
||||
|
||||
if (device_memory_) {
|
||||
vkDestroyImageView(device_, image_view_, nullptr);
|
||||
vkDestroyImage(device_, image_, nullptr);
|
||||
vkFreeMemory(device_, device_memory_, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Upload(const uint8_t* src_data) {
|
||||
|
@ -238,25 +281,49 @@ class VulkanImmediateTexture : public ImmediateTexture {
|
|||
vkGetImageSubresourceLayout(device_, image_, &subresource, &layout);
|
||||
|
||||
// Map memory for upload.
|
||||
void* gpu_data = nullptr;
|
||||
auto err =
|
||||
vkMapMemory(device_, device_memory_, 0, layout.size, 0, &gpu_data);
|
||||
uint8_t* gpu_data = nullptr;
|
||||
auto err = vkMapMemory(device_, device_memory_, 0, layout.size, 0,
|
||||
reinterpret_cast<void**>(&gpu_data));
|
||||
CheckResult(err, "vkMapMemory");
|
||||
|
||||
// Copy the entire texture, hoping its layout matches what we expect.
|
||||
std::memcpy(gpu_data, src_data, layout.size);
|
||||
std::memcpy(gpu_data + layout.offset, src_data, layout.size);
|
||||
|
||||
vkUnmapMemory(device_, device_memory_);
|
||||
}
|
||||
|
||||
// Queues a command to transition this texture to a new layout. This assumes
|
||||
// the command buffer WILL be queued and executed by the device.
|
||||
void TransitionLayout(VkCommandBuffer command_buffer,
|
||||
VkImageLayout new_layout) {
|
||||
VkImageMemoryBarrier image_barrier;
|
||||
image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
image_barrier.pNext = nullptr;
|
||||
image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_barrier.srcAccessMask = 0;
|
||||
image_barrier.dstAccessMask = 0;
|
||||
image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
image_barrier.newLayout = new_layout;
|
||||
image_barrier.image = image_;
|
||||
image_barrier.subresourceRange = {0, 0, 1, 0, 1};
|
||||
image_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_layout_ = new_layout;
|
||||
|
||||
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &image_barrier);
|
||||
}
|
||||
|
||||
VkDescriptorSet descriptor_set() const { return descriptor_set_; }
|
||||
VkImageLayout layout() const { return image_layout_; }
|
||||
|
||||
private:
|
||||
VkDevice device_ = nullptr;
|
||||
VkDescriptorPool descriptor_pool_ = nullptr;
|
||||
VkSampler sampler_ = nullptr; // Not owned.
|
||||
VkImage image_ = nullptr;
|
||||
VkImageLayout image_layout_ = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkImageLayout image_layout_ = VK_IMAGE_LAYOUT_PREINITIALIZED;
|
||||
VkDeviceMemory device_memory_ = nullptr;
|
||||
VkImageView image_view_ = nullptr;
|
||||
VkDescriptorSet descriptor_set_ = nullptr;
|
||||
|
@ -538,7 +605,7 @@ VulkanImmediateDrawer::VulkanImmediateDrawer(VulkanContext* graphics_context)
|
|||
pipeline_info.renderPass = context_->swap_chain()->render_pass();
|
||||
pipeline_info.subpass = 0;
|
||||
pipeline_info.basePipelineHandle = nullptr;
|
||||
pipeline_info.basePipelineIndex = 0;
|
||||
pipeline_info.basePipelineIndex = -1;
|
||||
err = vkCreateGraphicsPipelines(*device, nullptr, 1, &pipeline_info, nullptr,
|
||||
&triangle_pipeline_);
|
||||
CheckResult(err, "vkCreateGraphicsPipelines");
|
||||
|
@ -547,7 +614,7 @@ VulkanImmediateDrawer::VulkanImmediateDrawer(VulkanContext* graphics_context)
|
|||
pipeline_info.flags = VK_PIPELINE_CREATE_DERIVATIVE_BIT;
|
||||
input_info.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
pipeline_info.basePipelineHandle = triangle_pipeline_;
|
||||
pipeline_info.basePipelineIndex = 0;
|
||||
pipeline_info.basePipelineIndex = -1;
|
||||
err = vkCreateGraphicsPipelines(*device, nullptr, 1, &pipeline_info, nullptr,
|
||||
&line_pipeline_);
|
||||
CheckResult(err, "vkCreateGraphicsPipelines");
|
||||
|
@ -604,6 +671,14 @@ std::unique_ptr<ImmediateTexture> VulkanImmediateDrawer::CreateTexture(
|
|||
return std::unique_ptr<ImmediateTexture>(texture.release());
|
||||
}
|
||||
|
||||
std::unique_ptr<ImmediateTexture> VulkanImmediateDrawer::WrapTexture(
|
||||
VkImageView image_view, VkSampler sampler, uint32_t width,
|
||||
uint32_t height) {
|
||||
return std::make_unique<VulkanImmediateTexture>(
|
||||
context_->device(), descriptor_pool_, texture_set_layout_, image_view,
|
||||
sampler, width, height);
|
||||
}
|
||||
|
||||
void VulkanImmediateDrawer::UpdateTexture(ImmediateTexture* texture,
|
||||
const uint8_t* data) {
|
||||
static_cast<VulkanImmediateTexture*>(texture)->Upload(data);
|
||||
|
@ -672,9 +747,6 @@ void VulkanImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) {
|
|||
void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
||||
auto swap_chain = context_->swap_chain();
|
||||
|
||||
if (draw.primitive_type != ImmediatePrimitiveType::kTriangles) {
|
||||
return;
|
||||
}
|
||||
switch (draw.primitive_type) {
|
||||
case ImmediatePrimitiveType::kLines:
|
||||
vkCmdBindPipeline(current_cmd_buffer_, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
|
@ -689,6 +761,10 @@ void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
|||
// Setup texture binding.
|
||||
auto texture = reinterpret_cast<VulkanImmediateTexture*>(draw.texture_handle);
|
||||
if (texture) {
|
||||
if (texture->layout() != VK_IMAGE_LAYOUT_GENERAL) {
|
||||
texture->TransitionLayout(current_cmd_buffer_, VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
|
||||
auto texture_set = texture->descriptor_set();
|
||||
vkCmdBindDescriptorSets(current_cmd_buffer_,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout_,
|
||||
|
|
|
@ -32,6 +32,10 @@ class VulkanImmediateDrawer : public ImmediateDrawer {
|
|||
ImmediateTextureFilter filter,
|
||||
bool repeat,
|
||||
const uint8_t* data) override;
|
||||
std::unique_ptr<ImmediateTexture> WrapTexture(VkImageView image_view,
|
||||
VkSampler sampler,
|
||||
uint32_t width,
|
||||
uint32_t height);
|
||||
void UpdateTexture(ImmediateTexture* texture, const uint8_t* data) override;
|
||||
|
||||
void Begin(int render_target_width, int render_target_height) override;
|
||||
|
|
|
@ -187,6 +187,10 @@ bool VulkanSwapChain::Initialize(VkSurfaceKHR surface) {
|
|||
vkAllocateCommandBuffers(*device_, &cmd_buffer_info, &render_cmd_buffer_);
|
||||
CheckResult(err, "vkCreateCommandBuffer");
|
||||
|
||||
// Create another command buffer that handles image copies.
|
||||
err = vkAllocateCommandBuffers(*device_, &cmd_buffer_info, ©_cmd_buffer_);
|
||||
CheckResult(err, "vkCreateCommandBuffer");
|
||||
|
||||
// Create the render pass used to draw to the swap chain.
|
||||
// The actual framebuffer attached will depend on which image we are drawing
|
||||
// into.
|
||||
|
@ -194,7 +198,7 @@ bool VulkanSwapChain::Initialize(VkSurfaceKHR surface) {
|
|||
color_attachment.flags = 0;
|
||||
color_attachment.format = surface_format_;
|
||||
color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; // CLEAR;
|
||||
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
|
@ -388,6 +392,7 @@ bool VulkanSwapChain::Begin() {
|
|||
|
||||
// Reset all command buffers.
|
||||
vkResetCommandBuffer(render_cmd_buffer_, 0);
|
||||
vkResetCommandBuffer(copy_cmd_buffer_, 0);
|
||||
auto& current_buffer = buffers_[current_buffer_index_];
|
||||
|
||||
// Build the command buffer that will execute all queued rendering buffers.
|
||||
|
@ -399,14 +404,18 @@ bool VulkanSwapChain::Begin() {
|
|||
err = vkBeginCommandBuffer(render_cmd_buffer_, &begin_info);
|
||||
CheckResult(err, "vkBeginCommandBuffer");
|
||||
|
||||
// Transition the image to a format we can render to.
|
||||
// Start recording the copy command buffer as well.
|
||||
err = vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info);
|
||||
CheckResult(err, "vkBeginCommandBuffer");
|
||||
|
||||
// Transition the image to a format we can copy to.
|
||||
VkImageMemoryBarrier pre_image_memory_barrier;
|
||||
pre_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
pre_image_memory_barrier.pNext = nullptr;
|
||||
pre_image_memory_barrier.srcAccessMask = 0;
|
||||
pre_image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
pre_image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
pre_image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
pre_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
pre_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
pre_image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
pre_image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
pre_image_memory_barrier.image = current_buffer.image;
|
||||
|
@ -416,23 +425,37 @@ bool VulkanSwapChain::Begin() {
|
|||
pre_image_memory_barrier.subresourceRange.levelCount = 1;
|
||||
pre_image_memory_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
pre_image_memory_barrier.subresourceRange.layerCount = 1;
|
||||
vkCmdPipelineBarrier(copy_cmd_buffer_, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &pre_image_memory_barrier);
|
||||
|
||||
// First: Issue a command to clear the render target.
|
||||
VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
VkClearColorValue clear_color;
|
||||
clear_color.float32[0] = 238 / 255.0f;
|
||||
clear_color.float32[1] = 238 / 255.0f;
|
||||
clear_color.float32[2] = 238 / 255.0f;
|
||||
clear_color.float32[3] = 1.0f;
|
||||
if (FLAGS_vulkan_random_clear_color) {
|
||||
clear_color.float32[0] =
|
||||
rand() / static_cast<float>(RAND_MAX); // NOLINT(runtime/threadsafe_fn)
|
||||
clear_color.float32[1] = 1.0f;
|
||||
clear_color.float32[2] = 0.0f;
|
||||
}
|
||||
vkCmdClearColorImage(copy_cmd_buffer_, current_buffer.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1,
|
||||
&clear_range);
|
||||
|
||||
// Transition the image to a color attachment target for drawing.
|
||||
pre_image_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
pre_image_memory_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
pre_image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
pre_image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
vkCmdPipelineBarrier(render_cmd_buffer_, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &pre_image_memory_barrier);
|
||||
|
||||
// Begin render pass.
|
||||
VkClearValue color_clear_value;
|
||||
color_clear_value.color.float32[0] = 238 / 255.0f;
|
||||
color_clear_value.color.float32[1] = 238 / 255.0f;
|
||||
color_clear_value.color.float32[2] = 238 / 255.0f;
|
||||
color_clear_value.color.float32[3] = 1.0f;
|
||||
if (FLAGS_vulkan_random_clear_color) {
|
||||
color_clear_value.color.float32[0] =
|
||||
rand() / static_cast<float>(RAND_MAX); // NOLINT(runtime/threadsafe_fn)
|
||||
color_clear_value.color.float32[1] = 1.0f;
|
||||
color_clear_value.color.float32[2] = 0.0f;
|
||||
}
|
||||
VkClearValue clear_values[] = {color_clear_value};
|
||||
VkRenderPassBeginInfo render_pass_begin_info;
|
||||
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
render_pass_begin_info.pNext = nullptr;
|
||||
|
@ -442,9 +465,8 @@ bool VulkanSwapChain::Begin() {
|
|||
render_pass_begin_info.renderArea.offset.y = 0;
|
||||
render_pass_begin_info.renderArea.extent.width = surface_width_;
|
||||
render_pass_begin_info.renderArea.extent.height = surface_height_;
|
||||
render_pass_begin_info.clearValueCount =
|
||||
static_cast<uint32_t>(xe::countof(clear_values));
|
||||
render_pass_begin_info.pClearValues = clear_values;
|
||||
render_pass_begin_info.clearValueCount = 0;
|
||||
render_pass_begin_info.pClearValues = nullptr;
|
||||
vkCmdBeginRenderPass(render_cmd_buffer_, &render_pass_begin_info,
|
||||
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
|
||||
|
||||
|
@ -458,6 +480,7 @@ bool VulkanSwapChain::End() {
|
|||
vkCmdEndRenderPass(render_cmd_buffer_);
|
||||
|
||||
// Transition the image to a format the presentation engine can source from.
|
||||
// FIXME: Do we need more synchronization here between the copy buffer?
|
||||
VkImageMemoryBarrier post_image_memory_barrier;
|
||||
post_image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
post_image_memory_barrier.pNext = nullptr;
|
||||
|
@ -483,14 +506,20 @@ bool VulkanSwapChain::End() {
|
|||
auto err = vkEndCommandBuffer(render_cmd_buffer_);
|
||||
CheckResult(err, "vkEndCommandBuffer");
|
||||
|
||||
err = vkEndCommandBuffer(copy_cmd_buffer_);
|
||||
CheckResult(err, "vkEndCommandBuffer");
|
||||
|
||||
VkCommandBuffer command_buffers[] = {copy_cmd_buffer_, render_cmd_buffer_};
|
||||
|
||||
// Submit rendering.
|
||||
VkSubmitInfo render_submit_info;
|
||||
render_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
render_submit_info.pNext = nullptr;
|
||||
render_submit_info.waitSemaphoreCount = 0;
|
||||
render_submit_info.pWaitSemaphores = nullptr;
|
||||
render_submit_info.commandBufferCount = 1;
|
||||
render_submit_info.pCommandBuffers = &render_cmd_buffer_;
|
||||
render_submit_info.commandBufferCount =
|
||||
static_cast<uint32_t>(xe::countof(command_buffers));
|
||||
render_submit_info.pCommandBuffers = command_buffers;
|
||||
render_submit_info.signalSemaphoreCount = 0;
|
||||
render_submit_info.pSignalSemaphores = nullptr;
|
||||
{
|
||||
|
|
|
@ -35,11 +35,16 @@ class VulkanSwapChain {
|
|||
|
||||
uint32_t surface_width() const { return surface_width_; }
|
||||
uint32_t surface_height() const { return surface_height_; }
|
||||
VkImage surface_image() const {
|
||||
return buffers_[current_buffer_index_].image;
|
||||
}
|
||||
|
||||
// Render pass used for compositing.
|
||||
VkRenderPass render_pass() const { return render_pass_; }
|
||||
// Render command buffer, active inside the render pass from Begin to End.
|
||||
VkCommandBuffer render_cmd_buffer() const { return render_cmd_buffer_; }
|
||||
// Copy commands, ran before the render command buffer.
|
||||
VkCommandBuffer copy_cmd_buffer() const { return copy_cmd_buffer_; }
|
||||
|
||||
// Initializes the swap chain with the given WSI surface.
|
||||
bool Initialize(VkSurfaceKHR surface);
|
||||
|
@ -74,6 +79,7 @@ class VulkanSwapChain {
|
|||
uint32_t surface_height_ = 0;
|
||||
VkFormat surface_format_ = VK_FORMAT_UNDEFINED;
|
||||
VkCommandPool cmd_pool_ = nullptr;
|
||||
VkCommandBuffer copy_cmd_buffer_ = nullptr;
|
||||
VkCommandBuffer render_cmd_buffer_ = nullptr;
|
||||
VkRenderPass render_pass_ = nullptr;
|
||||
VkSemaphore image_available_semaphore_ = nullptr;
|
||||
|
|
|
@ -25,6 +25,30 @@ namespace xe {
|
|||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
class Fence {
|
||||
public:
|
||||
Fence(VkDevice device) : device_(device) {
|
||||
VkFenceCreateInfo fence_info;
|
||||
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fence_info.pNext = nullptr;
|
||||
fence_info.flags = 0;
|
||||
vkCreateFence(device, &fence_info, nullptr, &fence_);
|
||||
}
|
||||
~Fence() {
|
||||
vkDestroyFence(device_, fence_, nullptr);
|
||||
fence_ = nullptr;
|
||||
}
|
||||
|
||||
VkResult status() const { return vkGetFenceStatus(device_, fence_); }
|
||||
|
||||
VkFence fence() const { return fence_; }
|
||||
operator VkFence() const { return fence_; }
|
||||
|
||||
private:
|
||||
VkDevice device_;
|
||||
VkFence fence_ = nullptr;
|
||||
};
|
||||
|
||||
struct Version {
|
||||
uint32_t major;
|
||||
uint32_t minor;
|
||||
|
|
|
@ -1166,6 +1166,7 @@ void Builder::createMemoryBarrier(unsigned executionScope, unsigned memorySemant
|
|||
// An opcode that has one operands, a result id, and a type
|
||||
Id Builder::createUnaryOp(Op opCode, Id typeId, Id operand)
|
||||
{
|
||||
assert(operand != 0);
|
||||
Instruction* op = new Instruction(getUniqueId(), typeId, opCode);
|
||||
op->addIdOperand(operand);
|
||||
buildPoint->addInstruction(std::unique_ptr<Instruction>(op));
|
||||
|
@ -1175,6 +1176,8 @@ Id Builder::createUnaryOp(Op opCode, Id typeId, Id operand)
|
|||
|
||||
Id Builder::createBinOp(Op opCode, Id typeId, Id left, Id right)
|
||||
{
|
||||
assert(left != 0);
|
||||
assert(right != 0);
|
||||
Instruction* op = new Instruction(getUniqueId(), typeId, opCode);
|
||||
op->addIdOperand(left);
|
||||
op->addIdOperand(right);
|
||||
|
@ -1185,6 +1188,9 @@ Id Builder::createBinOp(Op opCode, Id typeId, Id left, Id right)
|
|||
|
||||
Id Builder::createTriOp(Op opCode, Id typeId, Id op1, Id op2, Id op3)
|
||||
{
|
||||
assert(op1 != 0);
|
||||
assert(op2 != 0);
|
||||
assert(op3 != 0);
|
||||
Instruction* op = new Instruction(getUniqueId(), typeId, opCode);
|
||||
op->addIdOperand(op1);
|
||||
op->addIdOperand(op2);
|
||||
|
|
|
@ -93,6 +93,8 @@ public:
|
|||
return id;
|
||||
}
|
||||
|
||||
Module* getModule() { return &module; }
|
||||
|
||||
// For creating new types (will return old type if the requested one was already made).
|
||||
Id makeVoidType();
|
||||
Id makeBoolType();
|
||||
|
@ -517,6 +519,7 @@ public:
|
|||
void createBranch(Block* block);
|
||||
void createConditionalBranch(Id condition, Block* thenBlock, Block* elseBlock);
|
||||
void createLoopMerge(Block* mergeBlock, Block* continueBlock, unsigned int control);
|
||||
void createSelectionMerge(Block* mergeBlock, unsigned int control);
|
||||
|
||||
protected:
|
||||
Id makeIntConstant(Id typeId, unsigned value, bool specConstant);
|
||||
|
@ -527,7 +530,6 @@ public:
|
|||
void transferAccessChainSwizzle(bool dynamic);
|
||||
void simplifyAccessChainSwizzle();
|
||||
void createAndSetNoPredecessorBlock(const char*);
|
||||
void createSelectionMerge(Block* mergeBlock, unsigned int control);
|
||||
void dumpInstructions(std::vector<unsigned int>&, const std::vector<std::unique_ptr<Instruction> >&) const;
|
||||
|
||||
SourceLanguage source;
|
||||
|
|
|
@ -180,6 +180,11 @@ public:
|
|||
void addInstruction(std::unique_ptr<Instruction> inst);
|
||||
void addPredecessor(Block* pred) { predecessors.push_back(pred); pred->successors.push_back(this);}
|
||||
void addLocalVariable(std::unique_ptr<Instruction> inst) { localVariables.push_back(std::move(inst)); }
|
||||
void insertInstruction(size_t pos, std::unique_ptr<Instruction> inst);
|
||||
|
||||
size_t getInstructionCount() { return instructions.size(); }
|
||||
Instruction* getInstruction(size_t i) { return instructions[i].get(); }
|
||||
void removeInstruction(size_t i) { instructions.erase(instructions.begin() + i); }
|
||||
const std::vector<Block*>& getPredecessors() const { return predecessors; }
|
||||
const std::vector<Block*>& getSuccessors() const { return successors; }
|
||||
void setUnreachable() { unreachable = true; }
|
||||
|
@ -200,6 +205,10 @@ public:
|
|||
|
||||
bool isTerminated() const
|
||||
{
|
||||
if (instructions.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (instructions.back()->getOpCode()) {
|
||||
case OpBranch:
|
||||
case OpBranchConditional:
|
||||
|
@ -215,6 +224,7 @@ public:
|
|||
|
||||
void dump(std::vector<unsigned int>& out) const
|
||||
{
|
||||
// OpLabel
|
||||
instructions[0]->dump(out);
|
||||
for (int i = 0; i < (int)localVariables.size(); ++i)
|
||||
localVariables[i]->dump(out);
|
||||
|
@ -222,7 +232,51 @@ public:
|
|||
instructions[i]->dump(out);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Moves all instructions from a target block into this block, and removes
|
||||
// the target block from our list of successors.
|
||||
// This function assumes this block unconditionally branches to the target
|
||||
// block directly.
|
||||
void merge(Block* target_block) {
|
||||
if (isTerminated()) {
|
||||
instructions.erase(instructions.end() - 1);
|
||||
}
|
||||
|
||||
// Find the target block in our successors first.
|
||||
for (auto it = successors.begin(); it != successors.end(); ++it) {
|
||||
if (*it == target_block) {
|
||||
it = successors.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add target block's successors to our successors.
|
||||
successors.insert(successors.end(), target_block->successors.begin(),
|
||||
target_block->successors.end());
|
||||
|
||||
// For each successor, replace the target block in their predecessors with
|
||||
// us.
|
||||
for (auto block : successors) {
|
||||
std::replace(block->predecessors.begin(), block->predecessors.end(),
|
||||
target_block, this);
|
||||
}
|
||||
|
||||
// Move instructions from target block into this block.
|
||||
for (auto it = target_block->instructions.begin();
|
||||
it != target_block->instructions.end();) {
|
||||
if ((*it)->getOpCode() == spv::Op::OpLabel) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
instructions.push_back(std::move(*it));
|
||||
it = target_block->instructions.erase(it);
|
||||
}
|
||||
|
||||
target_block->predecessors.clear();
|
||||
target_block->successors.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
Block(const Block&);
|
||||
Block& operator=(Block&);
|
||||
|
||||
|
@ -275,6 +329,17 @@ public:
|
|||
Module& getParent() const { return parent; }
|
||||
Block* getEntryBlock() const { return blocks.front(); }
|
||||
Block* getLastBlock() const { return blocks.back(); }
|
||||
Block* findBlockById(Id id)
|
||||
{
|
||||
for (auto block : blocks) {
|
||||
if (block->getId() == id) {
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<Block*>& getBlocks() { return blocks; }
|
||||
void addLocalVariable(std::unique_ptr<Instruction> inst);
|
||||
Id getReturnType() const { return functionInstruction.getTypeId(); }
|
||||
void dump(std::vector<unsigned int>& out) const
|
||||
|
@ -315,6 +380,8 @@ public:
|
|||
}
|
||||
|
||||
void addFunction(Function *fun) { functions.push_back(fun); }
|
||||
const std::vector<Function*>& getFunctions() const { return functions; }
|
||||
std::vector<Function*>& getFunctions() { return functions; }
|
||||
|
||||
void mapInstruction(Instruction *instruction)
|
||||
{
|
||||
|
@ -398,6 +465,14 @@ __inline void Block::addInstruction(std::unique_ptr<Instruction> inst)
|
|||
parent.getParent().mapInstruction(raw_instruction);
|
||||
}
|
||||
|
||||
__inline void Block::insertInstruction(size_t pos, std::unique_ptr<Instruction> inst) {
|
||||
Instruction* raw_instruction = inst.get();
|
||||
instructions.insert(instructions.begin() + pos, std::move(inst));
|
||||
raw_instruction->setBlock(this);
|
||||
if (raw_instruction->getResultId())
|
||||
parent.getParent().mapInstruction(raw_instruction);
|
||||
}
|
||||
|
||||
}; // end spv namespace
|
||||
|
||||
#endif // spvIR_H
|
||||
|
|
|
@ -13,9 +13,9 @@ project("spirv-tools")
|
|||
"spirv-tools/include",
|
||||
})
|
||||
files({
|
||||
"spirv-tools/external/include/headers/GLSL.std.450.h",
|
||||
"spirv-tools/external/include/headers/OpenCL.std.h",
|
||||
"spirv-tools/external/include/headers/spirv.h",
|
||||
"spirv-tools/include/spirv/GLSL.std.450.h",
|
||||
"spirv-tools/include/spirv/OpenCL.std.h",
|
||||
"spirv-tools/include/spirv/spirv.h",
|
||||
"spirv-tools/include/spirv-tools/libspirv.h",
|
||||
"spirv-tools/source/assembly_grammar.cpp",
|
||||
"spirv-tools/source/assembly_grammar.h",
|
||||
|
|
|
@ -642,8 +642,7 @@ class GenSpirvCommand(Command):
|
|||
print('Generating SPIR-V binaries...')
|
||||
print('')
|
||||
|
||||
# TODO(benvanik): actually find vulkan SDK. Env var? etc?
|
||||
vulkan_sdk_path = 'C:\\VulkanSDK\\1.0.3.1'
|
||||
vulkan_sdk_path = os.environ['VULKAN_SDK']
|
||||
vulkan_bin_path = os.path.join(vulkan_sdk_path, 'bin')
|
||||
glslang = os.path.join(vulkan_bin_path, 'glslangValidator')
|
||||
spirv_dis = os.path.join(vulkan_bin_path, 'spirv-dis')
|
||||
|
|
Loading…
Reference in New Issue