171 lines
5.8 KiB
C++
171 lines
5.8 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/shader.h"
|
|
|
|
#include <cinttypes>
|
|
#include <cstring>
|
|
#include <utility>
|
|
|
|
#include "third_party/fmt/include/fmt/format.h"
|
|
#include "xenia/base/filesystem.h"
|
|
#include "xenia/base/math.h"
|
|
#include "xenia/base/memory.h"
|
|
#include "xenia/base/string.h"
|
|
#include "xenia/gpu/ucode.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
using namespace ucode;
|
|
|
|
Shader::Shader(xenos::ShaderType shader_type, uint64_t ucode_data_hash,
|
|
const uint32_t* ucode_dwords, size_t ucode_dword_count,
|
|
std::endian ucode_source_endian)
|
|
: shader_type_(shader_type), ucode_data_hash_(ucode_data_hash) {
|
|
// We keep ucode data in host native format so it's easier to work with.
|
|
ucode_data_.resize(ucode_dword_count);
|
|
if (std::endian::native != ucode_source_endian) {
|
|
xe::copy_and_swap(ucode_data_.data(), ucode_dwords, ucode_dword_count);
|
|
} else {
|
|
std::memcpy(ucode_data_.data(), ucode_dwords,
|
|
sizeof(uint32_t) * ucode_dword_count);
|
|
}
|
|
}
|
|
|
|
Shader::~Shader() {
|
|
for (auto it : translations_) {
|
|
delete it.second;
|
|
}
|
|
}
|
|
|
|
std::string Shader::Translation::GetTranslatedBinaryString() const {
|
|
std::string result;
|
|
result.resize(translated_binary_.size());
|
|
std::memcpy(const_cast<char*>(result.data()), translated_binary_.data(),
|
|
translated_binary_.size());
|
|
return result;
|
|
}
|
|
|
|
std::pair<std::filesystem::path, std::filesystem::path>
|
|
Shader::Translation::Dump(const std::filesystem::path& base_path,
|
|
const char* path_prefix) const {
|
|
if (!is_valid()) {
|
|
return std::make_pair(std::filesystem::path(), std::filesystem::path());
|
|
}
|
|
|
|
std::filesystem::path path = base_path;
|
|
// Ensure target path exists.
|
|
std::filesystem::path target_path = base_path;
|
|
if (!target_path.empty()) {
|
|
target_path = std::filesystem::absolute(target_path);
|
|
std::filesystem::create_directories(target_path);
|
|
}
|
|
|
|
const char* type_extension =
|
|
shader().type() == xenos::ShaderType::kVertex ? "vert" : "frag";
|
|
|
|
std::filesystem::path binary_path =
|
|
target_path / fmt::format("shader_{:016X}_{:016X}.{}.bin.{}",
|
|
shader().ucode_data_hash(), modification(),
|
|
path_prefix, type_extension);
|
|
FILE* binary_file = filesystem::OpenFile(binary_path, "wb");
|
|
if (binary_file) {
|
|
fwrite(translated_binary_.data(), sizeof(*translated_binary_.data()),
|
|
translated_binary_.size(), binary_file);
|
|
fclose(binary_file);
|
|
}
|
|
|
|
std::filesystem::path disasm_path;
|
|
if (!host_disassembly_.empty()) {
|
|
disasm_path =
|
|
target_path / fmt::format("shader_{:016X}_{:016X}.{}.{}",
|
|
shader().ucode_data_hash(), modification(),
|
|
path_prefix, type_extension);
|
|
FILE* disasm_file = filesystem::OpenFile(disasm_path, "w");
|
|
if (disasm_file) {
|
|
fwrite(host_disassembly_.data(), sizeof(*host_disassembly_.data()),
|
|
host_disassembly_.size(), disasm_file);
|
|
fclose(disasm_file);
|
|
}
|
|
}
|
|
|
|
return std::make_pair(std::move(binary_path), std::move(disasm_path));
|
|
}
|
|
|
|
Shader::Translation* Shader::GetOrCreateTranslation(uint64_t modification,
|
|
bool* is_new) {
|
|
auto it = translations_.find(modification);
|
|
if (it != translations_.end()) {
|
|
if (is_new) {
|
|
*is_new = false;
|
|
}
|
|
return it->second;
|
|
}
|
|
Translation* translation = CreateTranslationInstance(modification);
|
|
translations_.emplace(modification, translation);
|
|
if (is_new) {
|
|
*is_new = true;
|
|
}
|
|
return translation;
|
|
}
|
|
|
|
void Shader::DestroyTranslation(uint64_t modification) {
|
|
auto it = translations_.find(modification);
|
|
if (it == translations_.end()) {
|
|
return;
|
|
}
|
|
delete it->second;
|
|
translations_.erase(it);
|
|
}
|
|
|
|
std::pair<std::filesystem::path, std::filesystem::path> Shader::DumpUcode(
|
|
const std::filesystem::path& base_path) const {
|
|
// Ensure target path exists.
|
|
std::filesystem::path target_path = base_path;
|
|
if (!target_path.empty()) {
|
|
target_path = std::filesystem::absolute(target_path);
|
|
std::filesystem::create_directories(target_path);
|
|
}
|
|
|
|
const char* type_extension =
|
|
type() == xenos::ShaderType::kVertex ? "vert" : "frag";
|
|
|
|
std::filesystem::path binary_path =
|
|
target_path / fmt::format("shader_{:016X}.ucode.bin.{}",
|
|
ucode_data_hash(), type_extension);
|
|
FILE* binary_file = filesystem::OpenFile(binary_path, "wb");
|
|
if (binary_file) {
|
|
fwrite(ucode_data().data(), sizeof(*ucode_data().data()),
|
|
ucode_data().size(), binary_file);
|
|
fclose(binary_file);
|
|
}
|
|
|
|
std::filesystem::path disasm_path;
|
|
if (is_ucode_analyzed()) {
|
|
disasm_path = target_path / fmt::format("shader_{:016X}.ucode.{}",
|
|
ucode_data_hash(), type_extension);
|
|
FILE* disasm_file = filesystem::OpenFile(disasm_path, "w");
|
|
if (disasm_file) {
|
|
fwrite(ucode_disassembly().data(), sizeof(*ucode_disassembly().data()),
|
|
ucode_disassembly().size(), disasm_file);
|
|
fclose(disasm_file);
|
|
}
|
|
}
|
|
|
|
return std::make_pair(std::move(binary_path), std::move(disasm_path));
|
|
}
|
|
|
|
Shader::Translation* Shader::CreateTranslationInstance(uint64_t modification) {
|
|
// Default implementation for simple cases like ucode disassembly.
|
|
return new Translation(*this, modification);
|
|
}
|
|
|
|
} // namespace gpu
|
|
} // namespace xe
|