VulkanWindow's 3000-line TU becomes sdl_window.cpp (window, events, cursor, screenshot BMP), vk_device.cpp (device, swapchain, MSAA, framebuffers), vk_pipelines.cpp (render passes, pipelines), vk_resources.cpp (buffers, render targets, geometry), vk_textures.cpp (textures, samplers, descriptors), vk_frame.cpp (render) and ui_batches.cpp; vulkan_window.cpp keeps construction and timing stats. Function bodies move verbatim. render() now reads fence/acquire -> prepare_draws -> prepare_ui -> write_draw_states -> record_passes -> record_readbacks -> submit -> finish_readbacks -> present, with FrameDraws / Readback carrying the locals that used to live across 500 lines. Verified: login-screen screenshot byte-identical to HEAD; game-world draw/vertex/index/UI counts identical; ctest, port_gate macos+android, Android build pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
195 lines
8.5 KiB
C++
195 lines
8.5 KiB
C++
// VulkanWindow::build_ui_batches: UIRenderCommand quads into the frame UI buffer, batched by texture and blend.
|
|
#include "vulkan_window.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <stdexcept>
|
|
|
|
namespace mt_host {
|
|
|
|
void VulkanWindow::build_ui_batches(
|
|
std::uint32_t ui_width,
|
|
std::uint32_t ui_height,
|
|
const std::vector<UIRenderCommand>& commands,
|
|
const std::unordered_map<std::string, std::vector<std::uint8_t>>& capture_textures,
|
|
std::vector<UiBatch>& batches,
|
|
std::size_t& out_quad_count) {
|
|
ensure_ui_capacity(commands.size());
|
|
auto* vertices = reinterpret_cast<Vertex*>(f_->ui_mapped);
|
|
auto* indices = reinterpret_cast<std::uint32_t*>(f_->ui_mapped + f_->ui_vertex_bytes);
|
|
std::uint32_t v_count = 0;
|
|
std::uint32_t i_count = 0;
|
|
const float inv_w = 2.0f / float(ui_width);
|
|
const float inv_h = 2.0f / float(ui_height);
|
|
|
|
for (const auto& cmd : commands) {
|
|
if (v_count + 4 > f_->ui_vertex_capacity || i_count + 6 > f_->ui_index_capacity)
|
|
throw std::runtime_error("UI command buffer capacity exceeded");
|
|
if (cmd.kind == UIRenderCommand::Text) continue;
|
|
|
|
float x1 = cmd.x1, y1 = cmd.y1, x2 = cmd.x2, y2 = cmd.y2;
|
|
float su = cmd.su, sv = cmd.sv, eu = cmd.eu, ev = cmd.ev;
|
|
const bool has_clip = cmd.clip_x2 > cmd.clip_x1 && cmd.clip_y2 > cmd.clip_y1;
|
|
|
|
float qx[4], qy[4], u[4], v[4], mu[4] = {}, mv[4] = {};
|
|
std::array<float, 4> top_color = unpack_argb(cmd.argb);
|
|
std::array<float, 4> bot_color =
|
|
cmd.kind == UIRenderCommand::GradientBar ? unpack_argb(cmd.end_argb) : top_color;
|
|
if (top_color[3] <= 0.001f && bot_color[3] <= 0.001f) continue;
|
|
|
|
if (cmd.kind == UIRenderCommand::Line) {
|
|
const float dx = x2 - x1, dy = y2 - y1;
|
|
const float len = std::sqrt(dx * dx + dy * dy);
|
|
if (len < 0.1f) continue;
|
|
const float nx = -dy / len * 0.5f;
|
|
const float ny = dx / len * 0.5f;
|
|
qx[0] = x1 + nx; qy[0] = y1 + ny;
|
|
qx[1] = x2 + nx; qy[1] = y2 + ny;
|
|
qx[2] = x1 - nx; qy[2] = y1 - ny;
|
|
qx[3] = x2 - nx; qy[3] = y2 - ny;
|
|
for (int k = 0; k < 4; ++k) { u[k] = 0.0f; v[k] = 0.0f; }
|
|
} else if (cmd.kind == UIRenderCommand::Image && cmd.quad) {
|
|
const bool axis_aligned =
|
|
std::fabs(cmd.qx[0] - cmd.qx[2]) < 1e-3f &&
|
|
std::fabs(cmd.qx[1] - cmd.qx[3]) < 1e-3f &&
|
|
std::fabs(cmd.qy[0] - cmd.qy[1]) < 1e-3f &&
|
|
std::fabs(cmd.qy[2] - cmd.qy[3]) < 1e-3f;
|
|
for (int k = 0; k < 4; ++k) {
|
|
mu[k] = cmd.mu[k];
|
|
mv[k] = cmd.mv[k];
|
|
}
|
|
if (has_clip && axis_aligned) {
|
|
const float ox1 = cmd.qx[0], oy1 = cmd.qy[0], ox2 = cmd.qx[3], oy2 = cmd.qy[3];
|
|
const float cx1 = std::max(ox1, cmd.clip_x1);
|
|
const float cy1 = std::max(oy1, cmd.clip_y1);
|
|
const float cx2 = std::min(ox2, cmd.clip_x2);
|
|
const float cy2 = std::min(oy2, cmd.clip_y2);
|
|
if (cx2 <= cx1 || cy2 <= cy1) continue;
|
|
float msu = cmd.mu[0], meu = cmd.mu[1];
|
|
float msv = cmd.mv[0], mev = cmd.mv[2];
|
|
if (ox2 > ox1) {
|
|
const float t0 = (cx1 - ox1) / (ox2 - ox1);
|
|
const float t1 = (cx2 - ox1) / (ox2 - ox1);
|
|
const float du = eu - su;
|
|
su = cmd.su + du * t0;
|
|
eu = cmd.su + du * t1;
|
|
const float mdu = meu - msu;
|
|
msu = cmd.mu[0] + mdu * t0;
|
|
meu = cmd.mu[0] + mdu * t1;
|
|
}
|
|
if (oy2 > oy1) {
|
|
const float t0 = (cy1 - oy1) / (oy2 - oy1);
|
|
const float t1 = (cy2 - oy1) / (oy2 - oy1);
|
|
const float dv = ev - sv;
|
|
sv = cmd.sv + dv * t0;
|
|
ev = cmd.sv + dv * t1;
|
|
const float mdv = mev - msv;
|
|
msv = cmd.mv[0] + mdv * t0;
|
|
mev = cmd.mv[0] + mdv * t1;
|
|
}
|
|
qx[0] = cx1; qy[0] = cy1;
|
|
qx[1] = cx2; qy[1] = cy1;
|
|
qx[2] = cx1; qy[2] = cy2;
|
|
qx[3] = cx2; qy[3] = cy2;
|
|
mu[0] = msu; mv[0] = msv;
|
|
mu[1] = meu; mv[1] = msv;
|
|
mu[2] = msu; mv[2] = mev;
|
|
mu[3] = meu; mv[3] = mev;
|
|
} else {
|
|
if (has_clip && (x2 <= cmd.clip_x1 || x1 >= cmd.clip_x2 || y2 <= cmd.clip_y1 || y1 >= cmd.clip_y2))
|
|
continue;
|
|
for (int k = 0; k < 4; ++k) {
|
|
qx[k] = cmd.qx[k];
|
|
qy[k] = cmd.qy[k];
|
|
}
|
|
}
|
|
u[0] = su; v[0] = sv;
|
|
u[1] = eu; v[1] = sv;
|
|
u[2] = su; v[2] = ev;
|
|
u[3] = eu; v[3] = ev;
|
|
} else if (cmd.kind == UIRenderCommand::Bar && cmd.quad) {
|
|
// An untextured polygon piece (CPythonGraphic::RenderCoolTimeBox's fan triangles).
|
|
if (has_clip && (x2 <= cmd.clip_x1 || x1 >= cmd.clip_x2 || y2 <= cmd.clip_y1 || y1 >= cmd.clip_y2))
|
|
continue;
|
|
for (int k = 0; k < 4; ++k) {
|
|
qx[k] = cmd.qx[k];
|
|
qy[k] = cmd.qy[k];
|
|
u[k] = 0.0f;
|
|
v[k] = 0.0f;
|
|
}
|
|
} else {
|
|
if (has_clip) {
|
|
x1 = std::max(x1, cmd.clip_x1);
|
|
y1 = std::max(y1, cmd.clip_y1);
|
|
x2 = std::min(x2, cmd.clip_x2);
|
|
y2 = std::min(y2, cmd.clip_y2);
|
|
}
|
|
if (x2 <= x1 || y2 <= y1) continue;
|
|
qx[0] = x1; qy[0] = y1;
|
|
qx[1] = x2; qy[1] = y1;
|
|
qx[2] = x1; qy[2] = y2;
|
|
qx[3] = x2; qy[3] = y2;
|
|
u[0] = su; v[0] = sv;
|
|
u[1] = eu; v[1] = sv;
|
|
u[2] = su; v[2] = ev;
|
|
u[3] = eu; v[3] = ev;
|
|
}
|
|
|
|
const std::string& tex_name = cmd.kind == UIRenderCommand::Image ? cmd.text : std::string();
|
|
const std::string& mask_name = cmd.kind == UIRenderCommand::Image ? cmd.mask : std::string();
|
|
const bool is_masked = !mask_name.empty();
|
|
// CGraphicExpandedImageInstance: SCREEN/COLOR_DODGE use
|
|
// INVDESTCOLOR + ONE; MODULATE uses ZERO + SRCCOLOR.
|
|
const std::uint8_t blend_mode = cmd.kind == UIRenderCommand::Image
|
|
? ((cmd.blend == 1 || cmd.blend == 2) ? 0x28 : (cmd.blend == 3 ? 0x31 : 1))
|
|
: 1;
|
|
const VkPipeline pipeline = get_pipeline(0, 0, blend_mode);
|
|
const VkDescriptorSet descriptor = get_ui_texture_descriptor(tex_name, mask_name, capture_textures);
|
|
|
|
for (int k = 0; k < 4; ++k) {
|
|
Vertex& vert = vertices[v_count + k];
|
|
vert.position[0] = qx[k] * inv_w - 1.0f;
|
|
vert.position[1] = 1.0f - qy[k] * inv_h;
|
|
vert.position[2] = 0.0f;
|
|
vert.normal[0] = 0.0f; vert.normal[1] = 0.0f; vert.normal[2] = 1.0f;
|
|
vert.uv[0] = u[k]; vert.uv[1] = v[k];
|
|
const auto& col = (k < 2) ? top_color : bot_color;
|
|
vert.color[0] = col[0]; vert.color[1] = col[1]; vert.color[2] = col[2]; vert.color[3] = col[3];
|
|
vert.joints[0] = vert.joints[1] = vert.joints[2] = vert.joints[3] = 0;
|
|
vert.weights[0] = 1.0f; vert.weights[1] = vert.weights[2] = vert.weights[3] = 0.0f;
|
|
vert.mask_uv[0] = mu[k]; vert.mask_uv[1] = mv[k];
|
|
vert.rhw = 1.0f;
|
|
}
|
|
indices[i_count + 0] = v_count + 0;
|
|
indices[i_count + 1] = v_count + 1;
|
|
indices[i_count + 2] = v_count + 2;
|
|
indices[i_count + 3] = v_count + 2;
|
|
indices[i_count + 4] = v_count + 1;
|
|
indices[i_count + 3 + 2] = v_count + 3;
|
|
|
|
const float mask_flag = is_masked ? 1.0f : 0.0f;
|
|
if (!batches.empty() &&
|
|
batches.back().behind_3d == cmd.behind_3d &&
|
|
batches.back().pipeline == pipeline &&
|
|
batches.back().descriptor == descriptor &&
|
|
batches.back().constants.params[2] == mask_flag) {
|
|
batches.back().index_count += 6;
|
|
} else {
|
|
UiBatch batch{};
|
|
batch.first_index = i_count;
|
|
batch.index_count = 6;
|
|
batch.pipeline = pipeline;
|
|
batch.descriptor = descriptor;
|
|
batch.behind_3d = cmd.behind_3d;
|
|
batch.constants.mvp = kIdentityMatrix;
|
|
batch.constants.params = {0.0f, 0.0f, mask_flag, 0.0f};
|
|
batches.push_back(batch);
|
|
}
|
|
v_count += 4;
|
|
i_count += 6;
|
|
++out_quad_count;
|
|
}
|
|
}
|
|
|
|
} // namespace mt_host
|