mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-09-08 18:45:48 +00:00
VideoCommon: add an object called 'SceneDumper' that allows someone to dump a Dolphin draw call out to a GLTF file
This commit is contained in:
parent
b13f8409c9
commit
f02245db81
2 changed files with 918 additions and 0 deletions
825
Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.cpp
Normal file
825
Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.cpp
Normal file
|
@ -0,0 +1,825 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModEditor/SceneDumper.h"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/format.h>
|
||||
#include <tinygltf/tiny_gltf.h>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/EnumUtils.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModBackend.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/TextureUtils.h"
|
||||
#include "VideoCommon/VertexManagerBase.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
int ComponentFormatAndIntegerToComponentType(ComponentFormat format, bool integer)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ComponentFormat::Byte:
|
||||
return TINYGLTF_COMPONENT_TYPE_BYTE;
|
||||
case ComponentFormat::InvalidFloat5:
|
||||
case ComponentFormat::InvalidFloat6:
|
||||
case ComponentFormat::InvalidFloat7:
|
||||
case ComponentFormat::Float:
|
||||
{
|
||||
if (integer)
|
||||
{
|
||||
return TINYGLTF_COMPONENT_TYPE_INT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TINYGLTF_COMPONENT_TYPE_FLOAT;
|
||||
}
|
||||
}
|
||||
case ComponentFormat::Short:
|
||||
return TINYGLTF_COMPONENT_TYPE_SHORT;
|
||||
case ComponentFormat::UByte:
|
||||
return TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
|
||||
case ComponentFormat::UShort:
|
||||
return TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT;
|
||||
};
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ComponentCountToType(std::size_t count)
|
||||
{
|
||||
if (count == 1)
|
||||
return TINYGLTF_TYPE_SCALAR;
|
||||
else if (count == 2)
|
||||
return TINYGLTF_TYPE_VEC2;
|
||||
else if (count == 3)
|
||||
return TINYGLTF_TYPE_VEC3;
|
||||
else if (count == 4)
|
||||
return TINYGLTF_TYPE_VEC4;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PrimitiveTypeToMode(PrimitiveType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PrimitiveType::Points:
|
||||
return TINYGLTF_MODE_POINTS;
|
||||
case PrimitiveType::Lines:
|
||||
return TINYGLTF_MODE_LINE;
|
||||
case PrimitiveType::Triangles:
|
||||
return TINYGLTF_MODE_TRIANGLES;
|
||||
case PrimitiveType::TriangleStrip:
|
||||
return TINYGLTF_MODE_TRIANGLE_STRIP;
|
||||
};
|
||||
|
||||
return -1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace GraphicsModEditor
|
||||
{
|
||||
struct SceneDumper::SceneDumperImpl
|
||||
{
|
||||
SceneDumperImpl()
|
||||
{
|
||||
m_model.asset.version = "2.0";
|
||||
m_model.asset.generator = "Dolphin emulator";
|
||||
}
|
||||
|
||||
using NodeList = std::vector<tinygltf::Node>;
|
||||
std::map<std::string, NodeList, std::less<>> m_xfb_hash_to_scene;
|
||||
NodeList m_current_node_list;
|
||||
tinygltf::Model m_model;
|
||||
|
||||
bool AreAllXFBsCapture(std::span<std::string> xfbs_presented)
|
||||
{
|
||||
if (m_xfb_hash_to_scene.empty())
|
||||
return false;
|
||||
|
||||
return std::all_of(xfbs_presented.begin(), xfbs_presented.end(),
|
||||
[this](const std::string& xfb_presented) {
|
||||
return m_xfb_hash_to_scene.contains(xfb_presented);
|
||||
});
|
||||
}
|
||||
|
||||
void SaveScenesToFile(const std::string& path, std::span<std::string> xfbs_presented)
|
||||
{
|
||||
const bool embed_images = false;
|
||||
const bool embed_buffers = false;
|
||||
const bool pretty_print = true;
|
||||
const bool write_binary = false;
|
||||
|
||||
for (const auto& xfb_presented : xfbs_presented)
|
||||
{
|
||||
if (const auto it = m_xfb_hash_to_scene.find(xfb_presented); it != m_xfb_hash_to_scene.end())
|
||||
{
|
||||
for (auto&& node : it->second)
|
||||
{
|
||||
m_model.nodes.push_back(std::move(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tinygltf::TinyGLTF gltf;
|
||||
if (!gltf.WriteGltfSceneToFile(&m_model, path, embed_images, embed_buffers, pretty_print,
|
||||
write_binary))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to write GLTF file to '{}'", path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SceneDumper::SceneDumper() : m_index_buffer(VertexManagerBase::MAXIBUFFERSIZE)
|
||||
{
|
||||
m_impl = std::make_unique<SceneDumperImpl>();
|
||||
m_index_generator.Init(true);
|
||||
m_index_generator.Start(m_index_buffer.data());
|
||||
}
|
||||
|
||||
SceneDumper::~SceneDumper() = default;
|
||||
|
||||
bool SceneDumper::IsDrawCallInRecording(GraphicsModSystem::DrawCallID draw_call_id) const
|
||||
{
|
||||
return m_record_request.m_draw_call_ids.empty() ||
|
||||
m_record_request.m_draw_call_ids.contains(draw_call_id);
|
||||
}
|
||||
|
||||
void SceneDumper::AddDataToRecording(GraphicsModSystem::DrawCallID draw_call_id,
|
||||
const GraphicsModSystem::DrawDataView& draw_data,
|
||||
AdditionalDrawData additional_draw_data)
|
||||
{
|
||||
const auto& declaration = draw_data.vertex_format->GetVertexDeclaration();
|
||||
|
||||
// Skip 2 point position elements for now
|
||||
if (declaration.position.enable && declaration.position.components == 2)
|
||||
return;
|
||||
|
||||
if (m_record_request.m_dump_gpu_skinning_as_json)
|
||||
{
|
||||
m_gpu_skinning_dumper.AddData(draw_call_id, draw_data, m_record_request.m_apply_gpu_skinning,
|
||||
std::span<const u16>(m_index_generator.GetIndexDataStart(),
|
||||
m_index_generator.GetIndexLen()));
|
||||
}
|
||||
|
||||
// Initial primitive data
|
||||
tinygltf::Primitive primitive;
|
||||
if (draw_data.uid->rasterization_state.primitive == PrimitiveType::TriangleStrip)
|
||||
{
|
||||
// Triangle strips are used for primitive restart but we don't support that in GLTF
|
||||
primitive.mode = PrimitiveTypeToMode(PrimitiveType::Triangles);
|
||||
}
|
||||
else
|
||||
{
|
||||
primitive.mode = PrimitiveTypeToMode(draw_data.uid->rasterization_state.primitive);
|
||||
}
|
||||
primitive.indices = static_cast<int>(m_impl->m_model.accessors.size());
|
||||
|
||||
u32 available_texcoords = 0;
|
||||
for (std::size_t i = 0; i < declaration.texcoords.size(); i++)
|
||||
{
|
||||
if (declaration.texcoords[i].enable && declaration.texcoords[i].components == 2)
|
||||
{
|
||||
available_texcoords++;
|
||||
}
|
||||
}
|
||||
|
||||
// Material data
|
||||
if (draw_data.textures.empty() || available_texcoords == 0 ||
|
||||
!m_record_request.m_include_materials)
|
||||
{
|
||||
const std::string default_material_name = "Dolphin_Default_Material";
|
||||
if (const auto iter = m_materialhash_to_material_id.find(default_material_name);
|
||||
iter != m_materialhash_to_material_id.end())
|
||||
{
|
||||
primitive.material = iter->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
tinygltf::Material material;
|
||||
material.pbrMetallicRoughness.baseColorFactor = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
material.doubleSided = true;
|
||||
material.name = default_material_name;
|
||||
|
||||
primitive.material = static_cast<int>(m_impl->m_model.materials.size());
|
||||
m_impl->m_model.materials.push_back(material);
|
||||
m_materialhash_to_material_id[default_material_name] = primitive.material;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string texture_material_name = "";
|
||||
for (const auto& texture : draw_data.textures)
|
||||
{
|
||||
texture_material_name += texture.hash_name;
|
||||
}
|
||||
if (const auto iter = m_materialhash_to_material_id.find(texture_material_name);
|
||||
iter != m_materialhash_to_material_id.end())
|
||||
{
|
||||
primitive.material = iter->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
tinygltf::Material material;
|
||||
material.pbrMetallicRoughness.baseColorFactor = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
material.doubleSided = true;
|
||||
material.name = texture_material_name;
|
||||
if (draw_data.uid->blending_state.blendenable && m_record_request.m_enable_blending)
|
||||
{
|
||||
material.alphaMode = "BLEND";
|
||||
}
|
||||
for (const auto& texture : draw_data.textures)
|
||||
{
|
||||
if (material.pbrMetallicRoughness.baseColorTexture.index != -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (const auto texture_iter = m_texturehash_to_texture_id.find(texture.hash_name);
|
||||
texture_iter != m_texturehash_to_texture_id.end())
|
||||
{
|
||||
material.pbrMetallicRoughness.baseColorTexture.index = texture_iter->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
VideoCommon::TextureUtils::DumpTexture(*texture.texture_data,
|
||||
std::string{texture.hash_name}, 0, false);
|
||||
|
||||
tinygltf::Texture tinygltf_texture;
|
||||
tinygltf_texture.source = static_cast<int>(m_impl->m_model.images.size());
|
||||
|
||||
tinygltf::Image image;
|
||||
image.uri = fmt::format("{}.png", texture.hash_name);
|
||||
m_impl->m_model.images.push_back(image);
|
||||
|
||||
material.pbrMetallicRoughness.baseColorTexture.index =
|
||||
static_cast<int>(m_impl->m_model.textures.size());
|
||||
m_impl->m_model.textures.push_back(tinygltf_texture);
|
||||
|
||||
m_texturehash_to_texture_id[std::string{texture.hash_name}] =
|
||||
material.pbrMetallicRoughness.baseColorTexture.index;
|
||||
}
|
||||
}
|
||||
primitive.material = static_cast<int>(m_impl->m_model.materials.size());
|
||||
m_materialhash_to_material_id[texture_material_name] = primitive.material;
|
||||
m_impl->m_model.materials.push_back(material);
|
||||
}
|
||||
}
|
||||
|
||||
// Index data
|
||||
tinygltf::Accessor index_accessor;
|
||||
index_accessor.name = fmt::format("Accessor 'INDEX' for {}", Common::ToUnderlying(draw_call_id));
|
||||
index_accessor.bufferView = static_cast<int>(m_impl->m_model.bufferViews.size());
|
||||
index_accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT;
|
||||
index_accessor.byteOffset = 0;
|
||||
index_accessor.type = TINYGLTF_TYPE_SCALAR;
|
||||
index_accessor.count = m_index_generator.GetIndexLen();
|
||||
|
||||
auto indice_ptr = reinterpret_cast<const u8*>(m_index_generator.GetIndexDataStart());
|
||||
for (std::size_t indice_index = 0; indice_index < m_index_generator.GetIndexLen(); indice_index++)
|
||||
{
|
||||
u16 index = 0;
|
||||
std::memcpy(&index, indice_ptr + index_accessor.byteOffset, sizeof(u16));
|
||||
|
||||
if (index_accessor.minValues.empty())
|
||||
{
|
||||
index_accessor.minValues.push_back(static_cast<double>(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
u16 last_min = static_cast<u16>(index_accessor.minValues[0]);
|
||||
|
||||
if (index < last_min)
|
||||
index_accessor.minValues[0] = index;
|
||||
}
|
||||
|
||||
if (index_accessor.maxValues.empty())
|
||||
{
|
||||
index_accessor.maxValues.push_back(static_cast<double>(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
u16 last_max = static_cast<u16>(index_accessor.maxValues[0]);
|
||||
|
||||
if (index > last_max)
|
||||
index_accessor.maxValues[0] = index;
|
||||
}
|
||||
indice_ptr += sizeof(u16);
|
||||
}
|
||||
|
||||
m_impl->m_model.accessors.push_back(std::move(index_accessor));
|
||||
|
||||
tinygltf::BufferView index_buffer_view;
|
||||
index_buffer_view.buffer = static_cast<int>(m_impl->m_model.buffers.size());
|
||||
index_buffer_view.byteLength = m_index_generator.GetIndexLen() * sizeof(u16);
|
||||
index_buffer_view.byteOffset = 0;
|
||||
index_buffer_view.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
|
||||
m_impl->m_model.bufferViews.push_back(std::move(index_buffer_view));
|
||||
|
||||
tinygltf::Buffer index_buffer;
|
||||
|
||||
const auto index_data =
|
||||
reinterpret_cast<const unsigned char*>(m_index_generator.GetIndexDataStart());
|
||||
index_buffer.data = {index_data, index_data + index_buffer_view.byteLength};
|
||||
m_impl->m_model.buffers.push_back(std::move(index_buffer));
|
||||
|
||||
// Fill out all vertex data
|
||||
const std::size_t stride = declaration.stride;
|
||||
|
||||
Common::Vec3 origin_skinned;
|
||||
if (declaration.position.enable)
|
||||
{
|
||||
primitive.attributes["POSITION"] = static_cast<int>(m_impl->m_model.accessors.size());
|
||||
|
||||
tinygltf::Accessor accessor;
|
||||
accessor.name = fmt::format("Accessor 'POSITION' for {}", Common::ToUnderlying(draw_call_id));
|
||||
accessor.bufferView = static_cast<int>(m_impl->m_model.bufferViews.size());
|
||||
accessor.componentType = ComponentFormatAndIntegerToComponentType(declaration.position.type,
|
||||
declaration.position.integer);
|
||||
accessor.byteOffset = declaration.position.offset;
|
||||
accessor.type = ComponentCountToType(declaration.position.components);
|
||||
accessor.count = draw_data.vertex_data.size();
|
||||
|
||||
auto vert_ptr = reinterpret_cast<const u8*>(draw_data.vertex_data.data());
|
||||
|
||||
// If gpu skinning is turned on but transforms is turned off, we need to calculate
|
||||
// what the positions are when centered at the origin
|
||||
std::optional<Common::Vec3> min_position_skinned;
|
||||
std::optional<Common::Vec3> max_position_skinned;
|
||||
if (m_record_request.m_apply_gpu_skinning && !m_record_request.m_include_transform &&
|
||||
!draw_data.gpu_skinning_position_transform.empty() && declaration.posmtx.enable)
|
||||
{
|
||||
for (std::size_t vert_index = 0; vert_index < draw_data.vertex_data.size(); vert_index++)
|
||||
{
|
||||
Common::Vec3 vertex_position;
|
||||
std::memcpy(&vertex_position.x, vert_ptr + accessor.byteOffset, sizeof(float));
|
||||
std::memcpy(&vertex_position.y, vert_ptr + sizeof(float) + accessor.byteOffset,
|
||||
sizeof(float));
|
||||
std::memcpy(&vertex_position.z, vert_ptr + sizeof(float) * 2 + accessor.byteOffset,
|
||||
sizeof(float));
|
||||
|
||||
u32 gpu_skin_index;
|
||||
std::memcpy(&gpu_skin_index, vert_ptr + declaration.posmtx.offset, sizeof(u32));
|
||||
|
||||
Common::Matrix44 position_transform;
|
||||
for (std::size_t i = 0; i < 3; i++)
|
||||
{
|
||||
position_transform.data[i * 4 + 0] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][0];
|
||||
position_transform.data[i * 4 + 1] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][1];
|
||||
position_transform.data[i * 4 + 2] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][2];
|
||||
position_transform.data[i * 4 + 3] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][3];
|
||||
}
|
||||
position_transform.data[12] = 0;
|
||||
position_transform.data[13] = 0;
|
||||
position_transform.data[14] = 0;
|
||||
position_transform.data[15] = 1;
|
||||
|
||||
// Apply the transform to the position
|
||||
vertex_position = position_transform.Transform(vertex_position, 1);
|
||||
|
||||
if (!min_position_skinned)
|
||||
{
|
||||
min_position_skinned = vertex_position;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vertex_position.x < min_position_skinned->x)
|
||||
min_position_skinned->x = vertex_position.x;
|
||||
if (vertex_position.y < min_position_skinned->y)
|
||||
min_position_skinned->y = vertex_position.y;
|
||||
if (vertex_position.z < min_position_skinned->z)
|
||||
min_position_skinned->z = vertex_position.z;
|
||||
}
|
||||
|
||||
if (!max_position_skinned)
|
||||
{
|
||||
max_position_skinned = vertex_position;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vertex_position.x > max_position_skinned->x)
|
||||
max_position_skinned->x = vertex_position.x;
|
||||
if (vertex_position.y > max_position_skinned->y)
|
||||
max_position_skinned->y = vertex_position.y;
|
||||
if (vertex_position.z > max_position_skinned->z)
|
||||
max_position_skinned->z = vertex_position.z;
|
||||
}
|
||||
vert_ptr += stride;
|
||||
}
|
||||
}
|
||||
if (min_position_skinned && max_position_skinned)
|
||||
{
|
||||
origin_skinned = (*max_position_skinned - *min_position_skinned) / 2.0f;
|
||||
}
|
||||
else if (min_position_skinned)
|
||||
{
|
||||
origin_skinned = -*min_position_skinned / 2.0f;
|
||||
}
|
||||
else if (max_position_skinned)
|
||||
{
|
||||
origin_skinned = *max_position_skinned / 2.0f;
|
||||
}
|
||||
|
||||
vert_ptr = reinterpret_cast<const u8*>(draw_data.vertex_data.data());
|
||||
for (std::size_t vert_index = 0; vert_index < draw_data.vertex_data.size(); vert_index++)
|
||||
{
|
||||
Common::Vec3 vertex_position;
|
||||
std::memcpy(&vertex_position.x, vert_ptr + accessor.byteOffset, sizeof(float));
|
||||
std::memcpy(&vertex_position.y, vert_ptr + sizeof(float) + accessor.byteOffset,
|
||||
sizeof(float));
|
||||
std::memcpy(&vertex_position.z, vert_ptr + sizeof(float) * 2 + accessor.byteOffset,
|
||||
sizeof(float));
|
||||
|
||||
if (m_record_request.m_apply_gpu_skinning &&
|
||||
!draw_data.gpu_skinning_position_transform.empty() && declaration.posmtx.enable)
|
||||
{
|
||||
u32 gpu_skin_index;
|
||||
std::memcpy(&gpu_skin_index, vert_ptr + declaration.posmtx.offset, sizeof(u32));
|
||||
|
||||
Common::Matrix44 position_transform;
|
||||
for (std::size_t i = 0; i < 3; i++)
|
||||
{
|
||||
position_transform.data[i * 4 + 0] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][0];
|
||||
position_transform.data[i * 4 + 1] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][1];
|
||||
position_transform.data[i * 4 + 2] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][2];
|
||||
position_transform.data[i * 4 + 3] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][3];
|
||||
}
|
||||
position_transform.data[12] = 0;
|
||||
position_transform.data[13] = 0;
|
||||
position_transform.data[14] = 0;
|
||||
position_transform.data[15] = 1;
|
||||
|
||||
// Apply the transform to the position
|
||||
vertex_position = position_transform.Transform(vertex_position, 1);
|
||||
|
||||
if (!m_record_request.m_include_transform)
|
||||
{
|
||||
vertex_position -= origin_skinned;
|
||||
}
|
||||
}
|
||||
|
||||
if (accessor.minValues.empty())
|
||||
{
|
||||
accessor.minValues.push_back(static_cast<double>(vertex_position.x));
|
||||
accessor.minValues.push_back(static_cast<double>(vertex_position.y));
|
||||
accessor.minValues.push_back(static_cast<double>(vertex_position.z));
|
||||
}
|
||||
else
|
||||
{
|
||||
const float last_min_x = static_cast<float>(accessor.minValues[0]);
|
||||
const float last_min_y = static_cast<float>(accessor.minValues[1]);
|
||||
const float last_min_z = static_cast<float>(accessor.minValues[2]);
|
||||
|
||||
if (vertex_position.x < last_min_x)
|
||||
accessor.minValues[0] = vertex_position.x;
|
||||
if (vertex_position.y < last_min_y)
|
||||
accessor.minValues[1] = vertex_position.y;
|
||||
if (vertex_position.z < last_min_z)
|
||||
accessor.minValues[2] = vertex_position.z;
|
||||
}
|
||||
|
||||
if (accessor.maxValues.empty())
|
||||
{
|
||||
accessor.maxValues.push_back(static_cast<double>(vertex_position.x));
|
||||
accessor.maxValues.push_back(static_cast<double>(vertex_position.y));
|
||||
accessor.maxValues.push_back(static_cast<double>(vertex_position.z));
|
||||
}
|
||||
else
|
||||
{
|
||||
const float last_max_x = static_cast<float>(accessor.maxValues[0]);
|
||||
const float last_max_y = static_cast<float>(accessor.maxValues[1]);
|
||||
const float last_max_z = static_cast<float>(accessor.maxValues[2]);
|
||||
|
||||
if (vertex_position.x > last_max_x)
|
||||
accessor.maxValues[0] = vertex_position.x;
|
||||
if (vertex_position.y > last_max_y)
|
||||
accessor.maxValues[1] = vertex_position.y;
|
||||
if (vertex_position.z > last_max_z)
|
||||
accessor.maxValues[2] = vertex_position.z;
|
||||
}
|
||||
vert_ptr += stride;
|
||||
}
|
||||
|
||||
m_impl->m_model.accessors.push_back(std::move(accessor));
|
||||
}
|
||||
|
||||
static std::array<std::string, 2> color_names{"COLOR_0", "COLOR_1"};
|
||||
for (std::size_t i = 0; i < declaration.colors.size(); i++)
|
||||
{
|
||||
if (declaration.colors[i].enable)
|
||||
{
|
||||
primitive.attributes[color_names[i]] = static_cast<int>(m_impl->m_model.accessors.size());
|
||||
|
||||
tinygltf::Accessor accessor;
|
||||
accessor.name =
|
||||
fmt::format("Accessor '{}' for {}", color_names[i], Common::ToUnderlying(draw_call_id));
|
||||
accessor.bufferView = static_cast<int>(m_impl->m_model.bufferViews.size());
|
||||
accessor.componentType = ComponentFormatAndIntegerToComponentType(
|
||||
declaration.colors[i].type, declaration.colors[i].integer);
|
||||
accessor.byteOffset = declaration.colors[i].offset;
|
||||
accessor.type = ComponentCountToType(declaration.colors[i].components);
|
||||
accessor.count = draw_data.vertex_data.size();
|
||||
accessor.normalized = declaration.colors[i].type == ComponentFormat::UByte ||
|
||||
declaration.colors[i].type == ComponentFormat::UShort ||
|
||||
declaration.colors[i].type == ComponentFormat::Byte ||
|
||||
declaration.colors[i].type == ComponentFormat::Short;
|
||||
|
||||
m_impl->m_model.accessors.push_back(std::move(accessor));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: tangent/binormal?
|
||||
static std::array<std::string, 1> norm_names{"NORMAL"};
|
||||
for (std::size_t i = 0; i < declaration.normals.size(); i++)
|
||||
{
|
||||
if (declaration.normals[i].enable)
|
||||
{
|
||||
primitive.attributes[norm_names[i]] = static_cast<int>(m_impl->m_model.accessors.size());
|
||||
|
||||
tinygltf::Accessor accessor;
|
||||
accessor.name =
|
||||
fmt::format("Accessor '{}' for {}", norm_names[i], Common::ToUnderlying(draw_call_id));
|
||||
accessor.bufferView = static_cast<int>(m_impl->m_model.bufferViews.size());
|
||||
accessor.componentType = ComponentFormatAndIntegerToComponentType(
|
||||
declaration.normals[i].type, declaration.normals[i].integer);
|
||||
accessor.byteOffset = declaration.normals[i].offset;
|
||||
accessor.type = ComponentCountToType(declaration.normals[i].components);
|
||||
accessor.count = draw_data.vertex_data.size();
|
||||
|
||||
m_impl->m_model.accessors.push_back(std::move(accessor));
|
||||
}
|
||||
}
|
||||
|
||||
static std::array<std::string, 8> texcoord_names = {
|
||||
"TEXCOORD_0", "TEXCOORD_1", "TEXCOORD_2", "TEXCOORD_3",
|
||||
"TEXCOORD_4", "TEXCOORD_5", "TEXCOORD_6", "TEXCOORD_7",
|
||||
};
|
||||
for (std::size_t i = 0; i < declaration.texcoords.size(); i++)
|
||||
{
|
||||
// Ignore 3 component texcoords for now..they are tex matrixes?
|
||||
if (declaration.texcoords[i].enable && declaration.texcoords[i].components == 2)
|
||||
{
|
||||
primitive.attributes[texcoord_names[i]] = static_cast<int>(m_impl->m_model.accessors.size());
|
||||
|
||||
tinygltf::Accessor accessor;
|
||||
accessor.name = fmt::format("Accessor '{}' for {}", texcoord_names[i],
|
||||
Common::ToUnderlying(draw_call_id));
|
||||
accessor.bufferView = static_cast<int>(m_impl->m_model.bufferViews.size());
|
||||
accessor.componentType = ComponentFormatAndIntegerToComponentType(
|
||||
declaration.texcoords[i].type, declaration.texcoords[i].integer);
|
||||
accessor.byteOffset = declaration.texcoords[i].offset;
|
||||
accessor.type = ComponentCountToType(declaration.texcoords[i].components);
|
||||
accessor.count = draw_data.vertex_data.size();
|
||||
accessor.normalized = declaration.texcoords[i].type == ComponentFormat::UByte ||
|
||||
declaration.texcoords[i].type == ComponentFormat::UShort ||
|
||||
declaration.texcoords[i].type == ComponentFormat::Byte ||
|
||||
declaration.texcoords[i].type == ComponentFormat::Short;
|
||||
|
||||
m_impl->m_model.accessors.push_back(std::move(accessor));
|
||||
}
|
||||
}
|
||||
|
||||
// Vertex buffer data
|
||||
tinygltf::BufferView vertex_buffer_view;
|
||||
vertex_buffer_view.buffer = static_cast<int>(m_impl->m_model.buffers.size());
|
||||
vertex_buffer_view.byteLength = draw_data.vertex_data.size() * stride;
|
||||
vertex_buffer_view.byteOffset = 0;
|
||||
vertex_buffer_view.byteStride = stride;
|
||||
vertex_buffer_view.target = TINYGLTF_TARGET_ARRAY_BUFFER;
|
||||
m_impl->m_model.bufferViews.push_back(std::move(vertex_buffer_view));
|
||||
|
||||
tinygltf::Buffer buffer;
|
||||
const auto vert_data = static_cast<const unsigned char*>(draw_data.vertex_data.data());
|
||||
buffer.data = {vert_data, vert_data + vertex_buffer_view.byteLength};
|
||||
|
||||
// If gpu skinning is turned on but transforms is turned off, we need to calculate
|
||||
// what the positions are when centered at the origin
|
||||
if (m_record_request.m_apply_gpu_skinning && !draw_data.gpu_skinning_position_transform.empty() &&
|
||||
declaration.posmtx.enable && declaration.position.enable)
|
||||
{
|
||||
auto vert_ptr = buffer.data.data();
|
||||
for (std::size_t vert_index = 0; vert_index < draw_data.vertex_data.size(); vert_index++)
|
||||
{
|
||||
Common::Vec3 vertex_position;
|
||||
std::memcpy(&vertex_position.x, vert_ptr + declaration.position.offset, sizeof(float));
|
||||
std::memcpy(&vertex_position.y, vert_ptr + sizeof(float) + declaration.position.offset,
|
||||
sizeof(float));
|
||||
std::memcpy(&vertex_position.z, vert_ptr + sizeof(float) * 2 + declaration.position.offset,
|
||||
sizeof(float));
|
||||
|
||||
u32 gpu_skin_index;
|
||||
std::memcpy(&gpu_skin_index, vert_ptr + declaration.posmtx.offset, sizeof(u32));
|
||||
|
||||
Common::Matrix44 position_transform;
|
||||
for (std::size_t i = 0; i < 3; i++)
|
||||
{
|
||||
position_transform.data[i * 4 + 0] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][0];
|
||||
position_transform.data[i * 4 + 1] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][1];
|
||||
position_transform.data[i * 4 + 2] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][2];
|
||||
position_transform.data[i * 4 + 3] =
|
||||
draw_data.gpu_skinning_position_transform[gpu_skin_index + i][3];
|
||||
}
|
||||
position_transform.data[12] = 0;
|
||||
position_transform.data[13] = 0;
|
||||
position_transform.data[14] = 0;
|
||||
position_transform.data[15] = 1;
|
||||
|
||||
// Apply the transform to the position
|
||||
vertex_position = position_transform.Transform(vertex_position, 1);
|
||||
|
||||
if (!m_record_request.m_include_transform)
|
||||
{
|
||||
vertex_position -= origin_skinned;
|
||||
}
|
||||
|
||||
std::memcpy(vert_ptr + declaration.position.offset, &vertex_position.x, sizeof(float));
|
||||
std::memcpy(vert_ptr + sizeof(float) + declaration.position.offset, &vertex_position.y,
|
||||
sizeof(float));
|
||||
std::memcpy(vert_ptr + sizeof(float) * 2 + declaration.position.offset, &vertex_position.z,
|
||||
sizeof(float));
|
||||
|
||||
if (declaration.normals[0].enable)
|
||||
{
|
||||
Common::Vec3 vertex_normal;
|
||||
std::memcpy(&vertex_normal.x, vert_ptr + declaration.normals[0].offset, sizeof(float));
|
||||
std::memcpy(&vertex_normal.y, vert_ptr + sizeof(float) + declaration.normals[0].offset,
|
||||
sizeof(float));
|
||||
std::memcpy(&vertex_normal.z, vert_ptr + sizeof(float) * 2 + declaration.normals[0].offset,
|
||||
sizeof(float));
|
||||
|
||||
Common::Matrix33 normal_transform;
|
||||
for (std::size_t i = 0; i < 3; i++)
|
||||
{
|
||||
normal_transform.data[i * 3 + 0] =
|
||||
draw_data.gpu_skinning_normal_transform[gpu_skin_index + i][0];
|
||||
normal_transform.data[i * 3 + 1] =
|
||||
draw_data.gpu_skinning_normal_transform[gpu_skin_index + i][1];
|
||||
normal_transform.data[i * 3 + 2] =
|
||||
draw_data.gpu_skinning_normal_transform[gpu_skin_index + i][2];
|
||||
}
|
||||
|
||||
// Apply the transform to the normal
|
||||
vertex_normal = normal_transform * vertex_normal;
|
||||
|
||||
// Save into output
|
||||
std::memcpy(vert_ptr + declaration.normals[0].offset, &vertex_normal.x, sizeof(float));
|
||||
std::memcpy(vert_ptr + sizeof(float) + declaration.normals[0].offset, &vertex_normal.y,
|
||||
sizeof(float));
|
||||
std::memcpy(vert_ptr + sizeof(float) * 2 + declaration.normals[0].offset, &vertex_normal.z,
|
||||
sizeof(float));
|
||||
}
|
||||
|
||||
vert_ptr += stride;
|
||||
}
|
||||
}
|
||||
|
||||
m_impl->m_model.buffers.push_back(std::move(buffer));
|
||||
|
||||
// Node data
|
||||
tinygltf::Node node;
|
||||
node.name = fmt::format("Node {} for xfb {}", Common::ToUnderlying(draw_call_id),
|
||||
m_xfbs_since_recording_present);
|
||||
node.mesh = static_cast<int>(m_impl->m_model.meshes.size());
|
||||
|
||||
// We expect to get data as a 3x3 if there's a global transform
|
||||
if (m_record_request.m_include_transform && additional_draw_data.transform.size() == 12)
|
||||
{
|
||||
// GLTF expects to be passed a 4x4
|
||||
node.matrix.reserve(16);
|
||||
|
||||
for (int w = 0; w < 4; w++)
|
||||
{
|
||||
for (int h = 0; h < 3; h++)
|
||||
{
|
||||
node.matrix.push_back(additional_draw_data.transform[w + h * 4]);
|
||||
}
|
||||
if (w == 3)
|
||||
{
|
||||
node.matrix.push_back(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
node.matrix.push_back(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_impl->m_current_node_list.push_back(std::move(node));
|
||||
|
||||
// Mesh data
|
||||
tinygltf::Mesh mesh;
|
||||
mesh.name = fmt::format("Mesh {}", Common::ToUnderlying(draw_call_id));
|
||||
mesh.primitives.push_back(std::move(primitive));
|
||||
m_impl->m_model.meshes.push_back(std::move(mesh));
|
||||
}
|
||||
|
||||
void SceneDumper::Record(const SceneRecordingRequest& request)
|
||||
{
|
||||
const std::string path_prefix =
|
||||
File::GetUserPath(D_DUMPMESHES_IDX) + SConfig::GetInstance().GetGameID();
|
||||
|
||||
const std::time_t start_time = std::time(nullptr);
|
||||
const std::string base_name =
|
||||
fmt::format("{}_{:%Y-%m-%d_%H-%M-%S}", path_prefix, fmt::localtime(start_time));
|
||||
|
||||
const std::string gltf_path = fmt::format("{}.gltf", base_name);
|
||||
m_scene_save_path = gltf_path;
|
||||
|
||||
m_gpu_skinning_dumper.SetOutputPath(fmt::format("{}.json", base_name));
|
||||
|
||||
m_record_request = request;
|
||||
m_recording_state = RecordingState::WANTS_RECORDING;
|
||||
}
|
||||
|
||||
bool SceneDumper::IsRecording() const
|
||||
{
|
||||
return m_recording_state == RecordingState::IS_RECORDING;
|
||||
}
|
||||
|
||||
void SceneDumper::OnXFBCreated(const std::string& hash)
|
||||
{
|
||||
if (m_recording_state != RecordingState::IS_RECORDING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_xfbs_since_recording_present++;
|
||||
|
||||
// We saw a XFB create without any data and we just started capturing
|
||||
// ignore it
|
||||
if (m_impl->m_current_node_list.empty() && m_xfbs_since_recording_present == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// When our frame present happens, we might be in the middle of an xfb
|
||||
// which means this initial create won't have all the data that we expect
|
||||
// it to have. Skip the first xfb and start collecting data after that
|
||||
// This ensures our capture has all data it needs
|
||||
if (m_xfbs_since_recording_present > 1)
|
||||
{
|
||||
m_impl->m_xfb_hash_to_scene.try_emplace(hash, std::move(m_impl->m_current_node_list));
|
||||
}
|
||||
m_impl->m_current_node_list = {};
|
||||
}
|
||||
|
||||
void SceneDumper::OnFramePresented(std::span<std::string> xfbs_presented)
|
||||
{
|
||||
if (m_recording_state == RecordingState::IS_RECORDING)
|
||||
{
|
||||
// If all our xfbs aren't captured in this frame, wait for next frame
|
||||
if (!m_impl->AreAllXFBsCapture(xfbs_presented))
|
||||
return;
|
||||
|
||||
m_impl->SaveScenesToFile(m_scene_save_path, xfbs_presented);
|
||||
|
||||
if (m_record_request.m_dump_gpu_skinning_as_json)
|
||||
m_gpu_skinning_dumper.WriteToFile();
|
||||
|
||||
m_recording_state = RecordingState::NOT_RECORDING;
|
||||
|
||||
// Release state
|
||||
m_record_request = {};
|
||||
m_materialhash_to_material_id = {};
|
||||
m_texturehash_to_texture_id = {};
|
||||
m_impl = std::make_unique<SceneDumperImpl>();
|
||||
m_xfbs_since_recording_present = 0;
|
||||
}
|
||||
|
||||
if (m_recording_state == RecordingState::WANTS_RECORDING)
|
||||
{
|
||||
m_recording_state = RecordingState::IS_RECORDING;
|
||||
}
|
||||
}
|
||||
|
||||
void SceneDumper::AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices)
|
||||
{
|
||||
m_index_generator.AddIndices(primitive, num_vertices);
|
||||
}
|
||||
|
||||
void SceneDumper::ResetIndices()
|
||||
{
|
||||
m_index_generator.Start(m_index_buffer.data());
|
||||
}
|
||||
} // namespace GraphicsModEditor
|
93
Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.h
Normal file
93
Source/Core/VideoCommon/GraphicsModEditor/SceneDumper.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2024 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/SmallVector.h"
|
||||
|
||||
#include "VideoCommon/GraphicsModEditor/GpuSkinningDataDumper.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Types.h"
|
||||
#include "VideoCommon/IndexGenerator.h"
|
||||
#include "VideoCommon/NativeVertexFormat.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/RenderState.h"
|
||||
|
||||
class AbstractTexture;
|
||||
|
||||
namespace GraphicsModSystem
|
||||
{
|
||||
struct DrawData;
|
||||
}
|
||||
|
||||
namespace GraphicsModEditor
|
||||
{
|
||||
|
||||
struct SceneRecordingRequest
|
||||
{
|
||||
std::unordered_set<GraphicsModSystem::DrawCallID> m_draw_call_ids;
|
||||
|
||||
bool m_enable_blending = false;
|
||||
bool m_apply_gpu_skinning = true;
|
||||
bool m_include_transform = true;
|
||||
bool m_include_materials = true;
|
||||
bool m_ignore_orthographic = false;
|
||||
bool m_dump_gpu_skinning_as_json = false;
|
||||
};
|
||||
class SceneDumper
|
||||
{
|
||||
public:
|
||||
SceneDumper();
|
||||
~SceneDumper();
|
||||
|
||||
struct AdditionalDrawData
|
||||
{
|
||||
std::span<float> transform;
|
||||
};
|
||||
bool IsDrawCallInRecording(GraphicsModSystem::DrawCallID draw_call_id) const;
|
||||
void AddDataToRecording(GraphicsModSystem::DrawCallID draw_call_id,
|
||||
const GraphicsModSystem::DrawDataView& draw_data,
|
||||
AdditionalDrawData additional_draw_data);
|
||||
|
||||
void Record(const SceneRecordingRequest& request);
|
||||
bool IsRecording() const;
|
||||
|
||||
void OnXFBCreated(const std::string& hash);
|
||||
void OnFramePresented(std::span<std::string> xfbs_presented);
|
||||
|
||||
void AddIndices(OpcodeDecoder::Primitive primitive, u32 num_vertices);
|
||||
void ResetIndices();
|
||||
|
||||
private:
|
||||
enum RecordingState
|
||||
{
|
||||
NOT_RECORDING,
|
||||
WANTS_RECORDING,
|
||||
IS_RECORDING
|
||||
};
|
||||
RecordingState m_recording_state = RecordingState::NOT_RECORDING;
|
||||
|
||||
std::map<std::string, int, std::less<>> m_materialhash_to_material_id;
|
||||
std::map<std::string, int, std::less<>> m_texturehash_to_texture_id;
|
||||
SceneRecordingRequest m_record_request;
|
||||
std::string m_scene_save_path;
|
||||
|
||||
GpuSkinningDataDumper m_gpu_skinning_dumper;
|
||||
|
||||
u8 m_xfbs_since_recording_present = 0;
|
||||
|
||||
struct SceneDumperImpl;
|
||||
std::unique_ptr<SceneDumperImpl> m_impl;
|
||||
|
||||
std::vector<u16> m_index_buffer;
|
||||
IndexGenerator m_index_generator;
|
||||
};
|
||||
} // namespace GraphicsModEditor
|
Loading…
Add table
Add a link
Reference in a new issue