// VulkanWindow::build_ui_batches: UIRenderCommand quads into the frame UI buffer, batched by texture and blend. #include "vulkan_window.h" #include #include #include namespace mt_host { void VulkanWindow::build_ui_batches( std::uint32_t ui_width, std::uint32_t ui_height, const std::vector& commands, const std::unordered_map>& capture_textures, std::vector& batches, std::size_t& out_quad_count) { ensure_ui_capacity(commands.size()); auto* vertices = reinterpret_cast(f_->ui_mapped); auto* indices = reinterpret_cast(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 top_color = unpack_argb(cmd.argb); std::array 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