From f288752e383956d784a13bf8a488fe648ba788e6 Mon Sep 17 00:00:00 2001 From: Nanako <469449812@qq.com> Date: Sun, 16 Mar 2025 00:39:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B4=E7=90=86=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + CMakeLists.txt | 10 + cmake/compile_shaders.cmake | 22 +- example/src/main.cpp | 4 +- src/CMakeLists.txt | 6 + src/core/render_context.h | 7 +- src/core/render_elements.cpp | 274 ++++++++++++++++++ src/core/render_elements.h | 143 +++++++++ .../android/android_render_window.cpp | 2 +- .../{ => window}/ios/ios_render_window.cpp | 2 +- .../linux/linux_render_window.cpp | 2 +- .../{ => window}/mac/mac_render_window.mm | 2 +- src/core/{ => window}/render_window.cpp | 0 src/core/{ => window}/render_window.h | 0 .../window/windows/pixel_format_convert.h | 74 +++++ .../windows/windows_render_context.cpp | 118 +++----- .../windows/windows_render_context.h | 12 +- .../windows/windows_render_window.cpp | 2 +- .../windows/windows_window_state.cpp | 23 +- .../windows/windows_window_state.h | 5 +- src/mirage.cpp | 13 +- src/misc/angle.h | 9 + src/misc/color.cpp | 5 + src/misc/color.h | 58 ++++ src/misc/mirage_type.h | 171 +++++++++-- src/sokol/sokol_header.h | 3 + .../{mirage_shdc.exe => win_mirage_shdc.exe} | Bin 22845952 -> 22845952 bytes 27 files changed, 802 insertions(+), 166 deletions(-) create mode 100644 src/core/render_elements.cpp create mode 100644 src/core/render_elements.h rename src/core/{ => window}/android/android_render_window.cpp (66%) rename src/core/{ => window}/ios/ios_render_window.cpp (80%) rename src/core/{ => window}/linux/linux_render_window.cpp (80%) rename src/core/{ => window}/mac/mac_render_window.mm (80%) rename src/core/{ => window}/render_window.cpp (100%) rename src/core/{ => window}/render_window.h (100%) create mode 100644 src/core/window/windows/pixel_format_convert.h rename src/core/{ => window}/windows/windows_render_context.cpp (69%) rename src/core/{ => window}/windows/windows_render_context.h (69%) rename src/core/{ => window}/windows/windows_render_window.cpp (99%) rename src/core/{ => window}/windows/windows_window_state.cpp (86%) rename src/core/{ => window}/windows/windows_window_state.h (85%) create mode 100644 src/misc/angle.h create mode 100644 src/misc/color.cpp create mode 100644 src/misc/color.h create mode 100644 src/sokol/sokol_header.h rename tools/{mirage_shdc.exe => win_mirage_shdc.exe} (99%) diff --git a/.gitignore b/.gitignore index 21be2a1..587077d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ fips-files/deploy/ .idea/ CMakeUserPresets.json #setup_surface(&window, false); + app.get_render_context()->setup_surface(&window); app.run(); return 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c6f1a60..5dfd1b8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,12 @@ cmake_minimum_required(VERSION 3.15) project(mirage_core LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 26) +if (MSVC) + # MSVC编译器设置C++标准 + add_compile_options(/std:c++latest) + # 设置utf-8编码 + add_compile_options(/utf-8) +endif () find_package(Freetype REQUIRED) find_package(Eigen3 REQUIRED) diff --git a/src/core/render_context.h b/src/core/render_context.h index 1d86154..baebbda 100644 --- a/src/core/render_context.h +++ b/src/core/render_context.h @@ -1,9 +1,7 @@ #pragma once -#include "sokol/sokol_gfx.h" #include "misc/mirage_type.h" -#include -#include +#include "sokol/sokol_header.h" class mirage_window; @@ -13,10 +11,11 @@ public: virtual ~mirage_render_context() = default; virtual bool init() { return false; } + virtual void end_init() {} virtual void cleanup() { } virtual void tick(const duration_type& in_delta) = 0; virtual sg_environment get_environment() = 0; - virtual bool setup_surface(mirage_window* in_window, bool in_hdr) { return false; } + virtual bool setup_surface(mirage_window* in_window) { return false; } }; mirage_render_context* mirage_create_render_context(); diff --git a/src/core/render_elements.cpp b/src/core/render_elements.cpp new file mode 100644 index 0000000..ecf512a --- /dev/null +++ b/src/core/render_elements.cpp @@ -0,0 +1,274 @@ +#include "render_elements.h" +#include "shaders/mirage_rounded_rect.hlsl.h" + +template +void compute_rect_vertices(const Eigen::MatrixBase& in_pos, + const Eigen::MatrixBase& in_size, + Eigen::Matrix& positions, + float rotation_radians = 0.0f, + const Eigen::Vector2f& scale = Eigen::Vector2f(1.0f, 1.0f), + const Eigen::Vector2f& pivot = Eigen::Vector2f(0.5f, 0.5f)) { + // 计算缩放后的尺寸 + Eigen::Array2f scaled_size = in_size.array() * scale.array(); + + // 计算旋转矩阵 + const Eigen::Rotation2D rotation(rotation_radians); + const Eigen::Matrix2f rot_matrix = rotation.toRotationMatrix(); + + // 计算基于原点的矩形顶点(左上角在原点) + Eigen::Matrix rect_points; + rect_points.col(0) = Eigen::Vector2f(0, 0); // 左上 + rect_points.col(1) = Eigen::Vector2f(scaled_size.x(), 0); // 右上 + rect_points.col(2) = Eigen::Vector2f(0, scaled_size.y()); // 左下 + rect_points.col(3) = Eigen::Vector2f(scaled_size.x(), scaled_size.y()); // 右下 + + // 计算锚点偏移(相对于左上角) + const auto& pivot_offset = Eigen::Vector2f(scaled_size.x() * pivot.x(), + scaled_size.y() * pivot.y()); + + // 应用旋转(绕锚点旋转) + for (int i = 0; i < 4; ++i) { + // 将点移到锚点为中心 + Eigen::Vector2f centered = rect_points.col(i) - pivot_offset; + // 应用旋转 + rect_points.col(i) = rot_matrix * centered + pivot_offset; + // 添加位置偏移 + positions.col(i) = rect_points.col(i) + in_pos; + } +} + +// 开始新一帧 +void render_elements::begin_frame() { + vertices.clear(); + indices.clear(); + batches.clear(); + current_batch_index = -1; + current_key = batch_key{}; + draw_call_count = 0; + total_triangles = 0; +} + +// 设置渲染管线 +void render_elements::set_pipeline(sg_pipeline pipeline) { + batch_key new_key = current_key; + new_key.pipeline = pipeline; + ensure_batch_compatibility(new_key); +} + +// 设置纹理 +void render_elements::set_texture(sg_image image) { + batch_key new_key = current_key; + new_key.image = image; + ensure_batch_compatibility(new_key); +} + +// 确保批次兼容性 +void render_elements::ensure_batch_compatibility(const batch_key& key) { + // 如果当前没有批次或者渲染状态改变了,创建新批次 + if (current_batch_index < 0 || !(current_key == key)) { + current_key = key; + + draw_batch new_batch; + new_batch.key = key; + new_batch.vertex_start = vertices.size(); + new_batch.index_start = indices.size(); + + batches.push_back(new_batch); + current_batch_index = batches.size() - 1; + } +} + +// 添加矩形到当前批次 +void render_elements::add_rect_to_batch( + const Eigen::Vector2f& in_pos, + const Eigen::Vector2f& in_size, + const rect_color& in_color, + const mirage_vertex_param_t& in_param_a, + const mirage_vertex_param_t& in_param_b, + const mirage_vertex_param_t& in_param_c, + const rect_uv& in_uv, + float in_rotation_radians, + const Eigen::Vector2f& in_pivot, + const Eigen::Vector2f& in_scale) { + // 确保有活跃的批次 + if (current_batch_index < 0) { + set_pipeline(sg_pipeline{}); // 使用默认管线 + set_texture(sg_image{}); // 使用默认纹理 + } + + // 计算顶点位置 + Eigen::Matrix positions; + compute_rect_vertices(in_pos, in_size, positions, in_rotation_radians, in_pivot, in_scale); + + // 记录起始顶点索引 + uint32_t base_index = vertices.size(); + + // 添加顶点 + for (int i = 0; i < 4; ++i) { + auto& vertex = vertices.emplace_back(); + vertex.position = positions.col(i); + vertex.color = in_color[i]; + vertex.uv = in_uv[i]; + vertex.param_a = in_param_a; + vertex.param_b = in_param_b; + vertex.param_c = in_param_c; + } + + // 添加索引(两个三角形) + indices.push_back(base_index); + indices.push_back(base_index + 1); + indices.push_back(base_index + 2); + + indices.push_back(base_index + 1); + indices.push_back(base_index + 3); + indices.push_back(base_index + 2); + + // 更新当前批次的计数 + draw_batch& batch = batches[current_batch_index]; + batch.vertex_count = vertices.size() - batch.vertex_start; + batch.index_count = indices.size() - batch.index_start; + + // 更新统计信息 + total_triangles += 2; +} + +// 创建矩形(公开接口) +void render_elements::make_rect( + const Eigen::Vector2f& in_pos, + const Eigen::Vector2f& in_size, + const rect_color& in_color, + const mirage_vertex_param_t& in_param_a, + const mirage_vertex_param_t& in_param_b, + const mirage_vertex_param_t& in_param_c, + const rect_uv& in_uv, + float in_rotation_radians, + const Eigen::Vector2f& in_pivot, + const Eigen::Vector2f& in_scale) { + add_rect_to_batch(in_pos, + in_size, + in_color, + in_param_a, + in_param_b, + in_param_c, + in_uv, + in_rotation_radians, + in_pivot, + in_scale); +} + +void render_elements::make_rounded_rect(const Eigen::Vector2f& in_pos, const Eigen::Vector2f& in_size, + const rect_color& in_color, const rect_uv& in_uv, const rect_round& in_round, + float in_rotation_radians, + const Eigen::Vector2f& in_pivot, const Eigen::Vector2f& in_scale) { + set_pipeline(rounded_rect_pipeline); + mirage_vertex_param_t param_a{ in_size }; + mirage_vertex_param_t param_b(in_round); + make_rect(in_pos, in_size, in_color, param_a, param_b, {}, in_uv, in_rotation_radians, in_scale, in_pivot); +} + +// 确保缓冲区容量 +void render_elements::ensure_buffer_capacity(uint32_t vertex_count, uint32_t index_count) { + // 如果现有缓冲区不够大,重新创建 + if (vertex_count > vertex_buffer_capacity) { + // 销毁旧缓冲区 + if (vertex_buffer.id != SG_INVALID_ID) { sg_destroy_buffer(vertex_buffer); } + + // 创建新缓冲区,容量翻倍以减少重新分配 + vertex_buffer_capacity = vertex_count * 2; + sg_buffer_desc vbuf_desc = {}; + vbuf_desc.size = vertex_buffer_capacity * sizeof(mirage_vertex_t); + vbuf_desc.usage = SG_USAGE_STREAM; + vertex_buffer = sg_make_buffer(&vbuf_desc); + } + + if (index_count > index_buffer_capacity) { + if (index_buffer.id != SG_INVALID_ID) { sg_destroy_buffer(index_buffer); } + + index_buffer_capacity = index_count * 2; + sg_buffer_desc ibuf_desc = {}; + ibuf_desc.size = index_buffer_capacity * sizeof(uint32_t); + ibuf_desc.usage = SG_USAGE_STREAM; + ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER; + index_buffer = sg_make_buffer(&ibuf_desc); + } +} + +// 上传并绘制所有批次 +void render_elements::flush_batches() { + // 如果没有数据,直接返回 + if (vertices.empty() || indices.empty() || batches.empty()) { return; } + + // 确保缓冲区足够大 + ensure_buffer_capacity(vertices.size(), indices.size()); + + // 上传顶点和索引数据 + sg_update_buffer(vertex_buffer, sg_range{ vertices.data(), vertices.size() * sizeof(mirage_vertex_t) }); + sg_update_buffer(index_buffer, sg_range{ indices.data(), indices.size() * sizeof(uint32_t) }); + + // 绑定顶点和索引缓冲区 + sg_bindings bind = {}; + bind.vertex_buffers[0] = vertex_buffer; + bind.index_buffer = index_buffer; + + // 渲染每个批次 + for (const auto& batch: batches) { + // 设置纹理 + bind.images[0] = batch.key.image; + + // 绑定管线 + sg_apply_pipeline(batch.key.pipeline); + sg_apply_bindings(&bind); + + sg_apply_uniforms(0, SG_RANGE(projection_matrix)); + + // 绘制批次 + sg_draw(batch.index_start, batch.index_count, 1); + draw_call_count++; + } +} + +// 结束帧并提交所有批次 +void render_elements::end_frame() { flush_batches(); } + +// 析构函数 +render_elements::~render_elements() { + if (!sg_isvalid()) + return; + if (vertex_buffer.id != SG_INVALID_ID) { sg_destroy_buffer(vertex_buffer); } + if (index_buffer.id != SG_INVALID_ID) { sg_destroy_buffer(index_buffer); } +} + +void render_elements::create_resources() { + constexpr uint32_t default_vertex_buffer_size = 512; // 512kb + constexpr uint32_t default_index_buffer_size = 256; // 256kb + // 计算默认顶点, 使用default_vertex_buffer_size对齐到sizeof(mirage_vertex_t) + vertex_buffer_capacity = default_vertex_buffer_size * 1024 / sizeof(mirage_vertex_t); + index_buffer_capacity = default_index_buffer_size * 1024 / sizeof(uint32_t); + + // 创建顶点缓冲区 + sg_buffer_desc vbuf_desc = {}; + vbuf_desc.size = vertex_buffer_capacity * sizeof(mirage_vertex_t); + vbuf_desc.usage = SG_USAGE_STREAM; + vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; + vertex_buffer = sg_make_buffer(&vbuf_desc); + + // 创建索引缓冲区 + sg_buffer_desc ibuf_desc = {}; + ibuf_desc.size = index_buffer_capacity * sizeof(mirage_triangle_t); + ibuf_desc.usage = SG_USAGE_STREAM; + ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER; + index_buffer = sg_make_buffer(&ibuf_desc); +} + +void render_elements::load_mirage_pipelines() { +#ifdef MIRAGE_USE_HDR + auto format = MIRAGE_HDR_FORMAT; +#else + auto format = MIRAGE_PIXEL_FORMAT; +#endif + auto rounded_rect_shader = sg_make_shader(get_mirage_rounded_rect_shader_desc()); + auto rounded_rect_pipeline_desc = get_mirage_rounded_rect_pipeline_desc(rounded_rect_shader, + format, + 1); + rounded_rect_pipeline = sg_make_pipeline(rounded_rect_pipeline_desc); +} diff --git a/src/core/render_elements.h b/src/core/render_elements.h new file mode 100644 index 0000000..158628c --- /dev/null +++ b/src/core/render_elements.h @@ -0,0 +1,143 @@ +#pragma once + +#include "sokol/sokol_header.h" +#include "misc/color.h" +#include "misc/mirage_type.h" + +enum class draw_effect { +}; + +// 用于标识批次的键 +struct batch_key { + sg_pipeline pipeline{}; + sg_image image{}; + // 可选:添加其他影响状态的元素,如着色器、混合模式等 + + bool operator==(const batch_key& other) const { + return pipeline.id == other.pipeline.id && + image.id == other.image.id; + } + + bool operator<(const batch_key& other) const { + if (pipeline.id != other.pipeline.id) + return pipeline.id < other.pipeline.id; + return image.id < other.image.id; + } +}; + +// 表示一个绘制批次 +struct draw_batch { + batch_key key; + uint32_t vertex_start = 0; // 批次在顶点缓冲区中的起始索引 + uint32_t vertex_count = 0; // 批次包含的顶点数量 + uint32_t index_start = 0; // 批次在索引缓冲区中的起始索引 + uint32_t index_count = 0; // 批次包含的索引数量 +}; + +// 特化类型 +using rect_color = rect_quad; +using rect_uv = rect_quad; +using rect_round = rect_quad; + +// 为rect_uv提供默认构造函数特化 +template<> +inline rect_quad::rect_quad() : std::array{ + Eigen::Vector2f{ 0, 0 }, + Eigen::Vector2f{ 1, 0 }, + Eigen::Vector2f{ 0, 1 }, + Eigen::Vector2f{ 1, 1 } +} { +} + +class render_elements { +public: + render_elements() = default; + ~render_elements(); + + void create_resources(); + void load_mirage_pipelines(); + + // 开始新一帧的渲染 + void begin_frame(); + + // 结束当前帧并提交所有批次 + void end_frame(); + + // 设置当前渲染状态 + void set_pipeline(sg_pipeline pipeline); + void set_texture(sg_image image); + void set_projection_matrix(const Eigen::Matrix4f& in_matrix) { projection_matrix = in_matrix; } + + // 添加矩形(自动加入当前批次) + void make_rect(const Eigen::Vector2f& in_pos, + const Eigen::Vector2f& in_size, + const rect_color& in_color, + const mirage_vertex_param_t& in_param_a = {}, + const mirage_vertex_param_t& in_param_b = {}, + const mirage_vertex_param_t& in_param_c = {}, + const rect_uv& in_uv = {}, + float in_rotation_radians = 0.0f, + const Eigen::Vector2f& in_pivot = Eigen::Vector2f(1.0f, 1.0f), + const Eigen::Vector2f& in_scale = Eigen::Vector2f(0.5f, 0.5f)); + + // 添加圆角矩形 + void make_rounded_rect(const Eigen::Vector2f& in_pos, + const Eigen::Vector2f& in_size, + const rect_color& in_color, + const rect_uv& in_uv = {}, + const rect_round& in_round = {}, + float in_rotation_radians = 0.0f, + const Eigen::Vector2f& in_pivot = Eigen::Vector2f(1.0f, 1.0f), + const Eigen::Vector2f& in_scale = Eigen::Vector2f(0.5f, 0.5f)); + +private: + // 根据顶点和索引数据创建一个矩形 + void add_rect_to_batch(const Eigen::Vector2f& in_pos, + const Eigen::Vector2f& in_size, + const rect_color& in_color, + const mirage_vertex_param_t& in_param_a, + const mirage_vertex_param_t& in_param_b, + const mirage_vertex_param_t& in_param_c, + const rect_uv& in_uv, + float in_rotation_radians, + const Eigen::Vector2f& in_pivot, + const Eigen::Vector2f& in_scale); + + // 确保当前批次与给定的渲染状态兼容 + void ensure_batch_compatibility(const batch_key& key); + + // 将当前批次数据上传到GPU + void flush_batches(); + + // 创建或调整缓冲区大小 + void ensure_buffer_capacity(uint32_t vertex_count, uint32_t index_count); + + // 当前渲染状态 + batch_key current_key{}; + + // 当前批次索引 + int32_t current_batch_index = -1; + + // 所有待渲染的批次 + std::vector batches; + + // 顶点和索引数据 + std::vector vertices; + std::vector indices; + + // GPU缓冲区 + sg_buffer vertex_buffer{}; + sg_buffer index_buffer{}; + + // 缓冲区容量追踪 + uint32_t vertex_buffer_capacity = 0; + uint32_t index_buffer_capacity = 0; + + // 统计信息 + uint32_t draw_call_count = 0; + uint32_t total_triangles = 0; + + Eigen::Matrix4f projection_matrix; + + sg_pipeline rounded_rect_pipeline{}; +}; diff --git a/src/core/android/android_render_window.cpp b/src/core/window/android/android_render_window.cpp similarity index 66% rename from src/core/android/android_render_window.cpp rename to src/core/window/android/android_render_window.cpp index 8d8bf90..9cd5e8e 100644 --- a/src/core/android/android_render_window.cpp +++ b/src/core/window/android/android_render_window.cpp @@ -1,7 +1,7 @@ // // Created by Administrator on 25-3-1. // -#include "core/render_window.h" +#include "core/window/render_window.h" #include diff --git a/src/core/ios/ios_render_window.cpp b/src/core/window/ios/ios_render_window.cpp similarity index 80% rename from src/core/ios/ios_render_window.cpp rename to src/core/window/ios/ios_render_window.cpp index 20e3b8e..df3b412 100644 --- a/src/core/ios/ios_render_window.cpp +++ b/src/core/window/ios/ios_render_window.cpp @@ -1,7 +1,7 @@ // // Created by Administrator on 25-3-1. // -#include "core/render_window.h" +#include "core/window/render_window.h" #include diff --git a/src/core/linux/linux_render_window.cpp b/src/core/window/linux/linux_render_window.cpp similarity index 80% rename from src/core/linux/linux_render_window.cpp rename to src/core/window/linux/linux_render_window.cpp index d57d280..1d3edda 100644 --- a/src/core/linux/linux_render_window.cpp +++ b/src/core/window/linux/linux_render_window.cpp @@ -1,7 +1,7 @@ // // Created by Administrator on 25-3-1. // -#include "core/render_window.h" +#include "core/window/render_window.h" #include diff --git a/src/core/mac/mac_render_window.mm b/src/core/window/mac/mac_render_window.mm similarity index 80% rename from src/core/mac/mac_render_window.mm rename to src/core/window/mac/mac_render_window.mm index bfe9ad7..cfb3b97 100644 --- a/src/core/mac/mac_render_window.mm +++ b/src/core/window/mac/mac_render_window.mm @@ -1,7 +1,7 @@ // // Created by Administrator on 25-3-1. // -#include "core/render_window.h" +#include "core/window/render_window.h" #include diff --git a/src/core/render_window.cpp b/src/core/window/render_window.cpp similarity index 100% rename from src/core/render_window.cpp rename to src/core/window/render_window.cpp diff --git a/src/core/render_window.h b/src/core/window/render_window.h similarity index 100% rename from src/core/render_window.h rename to src/core/window/render_window.h diff --git a/src/core/window/windows/pixel_format_convert.h b/src/core/window/windows/pixel_format_convert.h new file mode 100644 index 0000000..76d37dc --- /dev/null +++ b/src/core/window/windows/pixel_format_convert.h @@ -0,0 +1,74 @@ +#pragma once +#include "sokol/sokol_gfx.h" +#include + +inline DXGI_FORMAT sg_pixel_format_to_dxgi(sg_pixel_format fmt) { + switch (fmt) { + case SG_PIXELFORMAT_R8: return DXGI_FORMAT_R8_UNORM; + case SG_PIXELFORMAT_R8SN: return DXGI_FORMAT_R8_SNORM; + case SG_PIXELFORMAT_R8UI: return DXGI_FORMAT_R8_UINT; + case SG_PIXELFORMAT_R8SI: return DXGI_FORMAT_R8_SINT; + case SG_PIXELFORMAT_R16: return DXGI_FORMAT_R16_UNORM; + case SG_PIXELFORMAT_R16SN: return DXGI_FORMAT_R16_SNORM; + case SG_PIXELFORMAT_R16UI: return DXGI_FORMAT_R16_UINT; + case SG_PIXELFORMAT_R16SI: return DXGI_FORMAT_R16_SINT; + case SG_PIXELFORMAT_R16F: return DXGI_FORMAT_R16_FLOAT; + case SG_PIXELFORMAT_RG8: return DXGI_FORMAT_R8G8_UNORM; + case SG_PIXELFORMAT_RG8SN: return DXGI_FORMAT_R8G8_SNORM; + case SG_PIXELFORMAT_RG8UI: return DXGI_FORMAT_R8G8_UINT; + case SG_PIXELFORMAT_RG8SI: return DXGI_FORMAT_R8G8_SINT; + case SG_PIXELFORMAT_R32UI: return DXGI_FORMAT_R32_UINT; + case SG_PIXELFORMAT_R32SI: return DXGI_FORMAT_R32_SINT; + case SG_PIXELFORMAT_R32F: return DXGI_FORMAT_R32_FLOAT; + case SG_PIXELFORMAT_RG16: return DXGI_FORMAT_R16G16_UNORM; + case SG_PIXELFORMAT_RG16SN: return DXGI_FORMAT_R16G16_SNORM; + case SG_PIXELFORMAT_RG16UI: return DXGI_FORMAT_R16G16_UINT; + case SG_PIXELFORMAT_RG16SI: return DXGI_FORMAT_R16G16_SINT; + case SG_PIXELFORMAT_RG16F: return DXGI_FORMAT_R16G16_FLOAT; + case SG_PIXELFORMAT_RGBA8: return DXGI_FORMAT_R8G8B8A8_UNORM; + case SG_PIXELFORMAT_SRGB8A8: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + case SG_PIXELFORMAT_RGBA8SN: return DXGI_FORMAT_R8G8B8A8_SNORM; + case SG_PIXELFORMAT_RGBA8UI: return DXGI_FORMAT_R8G8B8A8_UINT; + case SG_PIXELFORMAT_RGBA8SI: return DXGI_FORMAT_R8G8B8A8_SINT; + case SG_PIXELFORMAT_BGRA8: return DXGI_FORMAT_B8G8R8A8_UNORM; + case SG_PIXELFORMAT_RGB10A2: return DXGI_FORMAT_R10G10B10A2_UNORM; + case SG_PIXELFORMAT_RG11B10F: return DXGI_FORMAT_R11G11B10_FLOAT; + case SG_PIXELFORMAT_RGB9E5: return DXGI_FORMAT_R9G9B9E5_SHAREDEXP; + case SG_PIXELFORMAT_RG32UI: return DXGI_FORMAT_R32G32_UINT; + case SG_PIXELFORMAT_RG32SI: return DXGI_FORMAT_R32G32_SINT; + case SG_PIXELFORMAT_RG32F: return DXGI_FORMAT_R32G32_FLOAT; + case SG_PIXELFORMAT_RGBA16: return DXGI_FORMAT_R16G16B16A16_UNORM; + case SG_PIXELFORMAT_RGBA16SN: return DXGI_FORMAT_R16G16B16A16_SNORM; + case SG_PIXELFORMAT_RGBA16UI: return DXGI_FORMAT_R16G16B16A16_UINT; + case SG_PIXELFORMAT_RGBA16SI: return DXGI_FORMAT_R16G16B16A16_SINT; + case SG_PIXELFORMAT_RGBA16F: return DXGI_FORMAT_R16G16B16A16_FLOAT; + case SG_PIXELFORMAT_RGBA32UI: return DXGI_FORMAT_R32G32B32A32_UINT; + case SG_PIXELFORMAT_RGBA32SI: return DXGI_FORMAT_R32G32B32A32_SINT; + case SG_PIXELFORMAT_RGBA32F: return DXGI_FORMAT_R32G32B32A32_FLOAT; + case SG_PIXELFORMAT_DEPTH: return DXGI_FORMAT_R32_TYPELESS; + case SG_PIXELFORMAT_DEPTH_STENCIL: return DXGI_FORMAT_R24G8_TYPELESS; + case SG_PIXELFORMAT_BC1_RGBA: return DXGI_FORMAT_BC1_UNORM; + case SG_PIXELFORMAT_BC2_RGBA: return DXGI_FORMAT_BC2_UNORM; + case SG_PIXELFORMAT_BC3_RGBA: return DXGI_FORMAT_BC3_UNORM; + case SG_PIXELFORMAT_BC3_SRGBA: return DXGI_FORMAT_BC3_UNORM_SRGB; + case SG_PIXELFORMAT_BC4_R: return DXGI_FORMAT_BC4_UNORM; + case SG_PIXELFORMAT_BC4_RSN: return DXGI_FORMAT_BC4_SNORM; + case SG_PIXELFORMAT_BC5_RG: return DXGI_FORMAT_BC5_UNORM; + case SG_PIXELFORMAT_BC5_RGSN: return DXGI_FORMAT_BC5_SNORM; + case SG_PIXELFORMAT_BC6H_RGBF: return DXGI_FORMAT_BC6H_SF16; + case SG_PIXELFORMAT_BC6H_RGBUF: return DXGI_FORMAT_BC6H_UF16; + case SG_PIXELFORMAT_BC7_RGBA: return DXGI_FORMAT_BC7_UNORM; + case SG_PIXELFORMAT_BC7_SRGBA: return DXGI_FORMAT_BC7_UNORM_SRGB; + default: return DXGI_FORMAT_UNKNOWN; + }; +} + +inline DXGI_FORMAT sg_pixel_format_to_dxgi_srv(sg_pixel_format fmt) { + if (fmt == SG_PIXELFORMAT_DEPTH) { + return DXGI_FORMAT_R32_FLOAT; + } + if (fmt == SG_PIXELFORMAT_DEPTH_STENCIL) { + return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + } + return sg_pixel_format_to_dxgi(fmt); +} diff --git a/src/core/windows/windows_render_context.cpp b/src/core/window/windows/windows_render_context.cpp similarity index 69% rename from src/core/windows/windows_render_context.cpp rename to src/core/window/windows/windows_render_context.cpp index 6727d19..abf3cc7 100644 --- a/src/core/windows/windows_render_context.cpp +++ b/src/core/window/windows/windows_render_context.cpp @@ -6,16 +6,12 @@ #include #include +#include "misc/angle.h" -#include "core/render_window.h" +#include "core/window/render_window.h" #include #include "windows_window_state.h" -#include "misc/scope_exit.h" -#include "shaders/test.hlsl.h" -#include "shaders/mirage_rounded_rect.hlsl.h" - -windows_mirage_render_context::~windows_mirage_render_context() { cleanup(); } bool windows_mirage_render_context::init() { try { @@ -159,6 +155,12 @@ bool windows_mirage_render_context::init() { } } +void windows_mirage_render_context::end_init() { + mirage_render_context::end_init(); + elements.create_resources(); + elements.load_mirage_pipelines(); +} + // 资源清理函数 void windows_mirage_render_context::cleanup() { mirage_render_context::cleanup(); @@ -193,20 +195,34 @@ void windows_mirage_render_context::tick(const duration_type& in_delta) { pass.swapchain = window_state->swapchain; sg_begin_pass(pass); - sg_apply_pipeline(pip); - sg_apply_viewport(0, 0, window_state->swapchain.width, window_state->swapchain.height, true); - sg_bindings bindings{}; - bindings.vertex_buffers[0] = vertex_buffer; - bindings.index_buffer = index_buffer; - sg_apply_bindings(bindings); - const auto& matrix = window->create_screen_to_dci_matrix(); + elements.set_projection_matrix(matrix); - sg_apply_uniforms(0, SG_RANGE(matrix)); + elements.begin_frame(); - sg_draw(0, 12, 2); + // draw some triangles + Eigen::Vector2f pos{ 0, 0 }; + Eigen::Vector2f pos2{ 200, 200 }; + Eigen::Vector2f size{ 200, 200 }; + elements.make_rounded_rect( + pos, + size, + rect_color({ 1, 0, 0, 1 }, { 0, 1, 1, 1 }), + {}, + rect_round({ 10, 20, 30, 40 }), + 45_deg, + { 0, 0 }, + { 1, 1 } + ); + elements.make_rounded_rect( + pos2, + size, + rect_color({ 1, 1, 0, 1 }) + ); + + elements.end_frame(); sg_end_pass(); sg_commit(); @@ -223,77 +239,9 @@ sg_environment windows_mirage_render_context::get_environment() { }; } -bool windows_mirage_render_context::setup_surface(mirage_window* in_window, bool in_hdr) { +bool windows_mirage_render_context::setup_surface(mirage_window* in_window) { auto state = std::make_unique(); - if (!state->init(device, dxgi_factory, in_window, in_hdr)) { return false; } - - shader = sg_make_shader(get_mirage_rounded_rect_shader_desc()); - - sg_pipeline_desc pipeline_desc = get_mirage_rounded_rect_pipeline_desc(shader, state->swapchain); - pip = sg_make_pipeline(pipeline_desc); - - mirage_triangle_t triangles[4]; - - // 创建顶点缓冲区(一个矩形) - std::vector vertices = { - // x, y, r, g, b, a - { { 0.f, 0.f }, { 0.f, 0.f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, - // 左上 - 黄色 - { { 100.f, 0.f }, { 1.f, 0.f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, - // 右上 - 蓝色 - { { 0.f, 100.f }, { 0.f, 1.f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, - // 左下 - 红色 - { { 100.f, 100.f }, { 1.f, 1.f }, { 1.0f, 1.0f, 1.0f, 1.0f } }, - // 右下 - 绿色 - { { 200.f, 200.f }, { 0.f, 0.f }, { 0.0f, 1.0f, 1.0f, 1.0f } }, - // 左上 - 黄色 - { { 300.f, 200.f }, { 1.f, 0.f }, { 1.0f, 0.0f, 1.0f, 1.0f } }, - // 右上 - 蓝色 - { { 200.f, 300.f }, { 0.f, 1.f }, { 1.0f, 0.0f, 1.0f, 1.0f } }, - // 左下 - 红色 - { { 300.f, 300.f }, { 1.f, 1.f }, { 0.0f, 1.0f, 0.0f, 1.0f } }, - // 右下 - 绿色 - }; - for (auto& v: vertices) { - v.param_a.a = 100; - v.param_a.b = 100; - - v.param_b.a = 0; - v.param_b.b = 5; - v.param_b.c = 10; - v.param_b.d = 20; - } - - // 三角形索引不变 - triangles[0].indices[0] = 0; - triangles[0].indices[1] = 1; - triangles[0].indices[2] = 2; - triangles[1].indices[0] = 1; - triangles[1].indices[1] = 3; - triangles[1].indices[2] = 2; - - triangles[2].indices[0] = 4; - triangles[2].indices[1] = 5; - triangles[2].indices[2] = 6; - triangles[3].indices[0] = 5; - triangles[3].indices[1] = 7; - triangles[3].indices[2] = 6; - - std::span vertex_span{ vertices.data(), vertices.size() }; - sg_buffer_desc vertex_buffer_desc{ - .size = vertex_span.size_bytes() * 2, - .type = SG_BUFFERTYPE_VERTEXBUFFER, - .usage = SG_USAGE_DYNAMIC, - }; - vertex_buffer = sg_make_buffer(vertex_buffer_desc); - std::span vertex_span2{ vertices.data(), vertices.size() }; - sg_update_buffer(vertex_buffer, sg_range{ vertex_span2.data(), vertex_span2.size_bytes() }); - - index_buffer = sg_make_buffer(sg_buffer_desc{ - .type = SG_BUFFERTYPE_INDEXBUFFER, - .usage = SG_USAGE_IMMUTABLE, - .data = SG_RANGE(triangles), - }); + if (!state->init(device, dxgi_factory, in_window)) { return false; } in_window->state = std::move(state); return true; diff --git a/src/core/windows/windows_render_context.h b/src/core/window/windows/windows_render_context.h similarity index 69% rename from src/core/windows/windows_render_context.h rename to src/core/window/windows/windows_render_context.h index 0144b65..01a48b4 100644 --- a/src/core/windows/windows_render_context.h +++ b/src/core/window/windows/windows_render_context.h @@ -1,19 +1,22 @@ #pragma once #include +#include +#include "sokol/sokol_header.h" #include "core/render_context.h" +#include "core/render_elements.h" class windows_mirage_render_context : public mirage_render_context { public: windows_mirage_render_context() = default; - virtual ~windows_mirage_render_context() override; bool init() override; + virtual void end_init() override; void cleanup() override; virtual void tick(const duration_type& in_delta) override; sg_environment get_environment() override; - bool setup_surface(mirage_window* in_window, bool in_hdr) override; + bool setup_surface(mirage_window* in_window) override; private: ID3D11Device* device{}; ID3D11DeviceContext* device_context{}; @@ -21,8 +24,5 @@ private: IDXGIFactory* dxgi_factory{}; D3D_FEATURE_LEVEL feature_level; - sg_shader shader{}; - sg_pipeline pip{}; - sg_buffer vertex_buffer{}; - sg_buffer index_buffer{}; + render_elements elements; }; diff --git a/src/core/windows/windows_render_window.cpp b/src/core/window/windows/windows_render_window.cpp similarity index 99% rename from src/core/windows/windows_render_window.cpp rename to src/core/window/windows/windows_render_window.cpp index 87442d5..9914e60 100644 --- a/src/core/windows/windows_render_window.cpp +++ b/src/core/window/windows/windows_render_window.cpp @@ -3,7 +3,7 @@ // #include -#include "core/render_window.h" +#include "../render_window.h" #include #define WINDOW_HANDLE static_cast(window_handle) diff --git a/src/core/windows/windows_window_state.cpp b/src/core/window/windows/windows_window_state.cpp similarity index 86% rename from src/core/windows/windows_window_state.cpp rename to src/core/window/windows/windows_window_state.cpp index 1ef4ae7..122ae22 100644 --- a/src/core/windows/windows_window_state.cpp +++ b/src/core/window/windows/windows_window_state.cpp @@ -1,18 +1,22 @@ #include "windows_window_state.h" - #include +#include "pixel_format_convert.h" #include "misc/scope_exit.h" -bool windows_window_state::init(ID3D11Device* in_device, IDXGIFactory* in_factory, mirage_window* in_window, - bool in_hdr) { +bool windows_window_state::init(ID3D11Device* in_device, IDXGIFactory* in_factory, mirage_window* in_window) { dx_device = in_device; dxgi_factory = in_factory; const auto size = in_window->get_window_frame_size(); const auto window_handle = in_window->get_window_handle(); - const auto format = in_hdr ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM; +#ifdef MIRAGE_USE_HDR + constexpr auto sg_format = MIRAGE_HDR_FORMAT; +#else + constexpr auto sg_format = MIRAGE_PIXEL_FORMAT; +#endif + const auto format = sg_pixel_format_to_dxgi_srv(sg_format); // 创建D3D11渲染目标视图 DXGI_SWAP_CHAIN_DESC swap_chain_desc = { @@ -62,10 +66,19 @@ bool windows_window_state::init(ID3D11Device* in_device, IDXGIFactory* in_factor return false; } +// #ifdef MIRAGE_USE_HDR +// // 设置HDR元数据 +// DXGI_HDR_METADATA_HDR10 metadata = {}; +// metadata.MaxMasteringLuminance = 1000 * 10000; // 1000尼特 +// metadata.MinMasteringLuminance = 0.001 * 10000; // 0.001尼特 +// // ...设置其他元数据 +// dx_swap_chain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(metadata), &metadata); +// #endif + swapchain.d3d11.render_view = render_target_view; swapchain.width = static_cast(size.x()); swapchain.height = static_cast(size.y()); - swapchain.color_format = in_hdr ? SG_PIXELFORMAT_RGBA16F : SG_PIXELFORMAT_RGBA8; + swapchain.color_format = sg_format; swapchain.depth_format = SG_PIXELFORMAT_NONE; swapchain.sample_count = 1; swapchain.d3d11.resolve_view = nullptr; diff --git a/src/core/windows/windows_window_state.h b/src/core/window/windows/windows_window_state.h similarity index 85% rename from src/core/windows/windows_window_state.h rename to src/core/window/windows/windows_window_state.h index 67b1f82..3138d88 100644 --- a/src/core/windows/windows_window_state.h +++ b/src/core/window/windows/windows_window_state.h @@ -1,13 +1,14 @@ #pragma once -#include "core/render_window.h" +#include "core/window/render_window.h" #include +#include struct windows_window_state final : mirage_window_state { ID3D11RenderTargetView* get_dx_render_target_view() const { return (ID3D11RenderTargetView*)swapchain.d3d11.render_view; } - bool init(ID3D11Device* in_device, IDXGIFactory* in_factory, mirage_window* in_window, bool in_hdr); + bool init(ID3D11Device* in_device, IDXGIFactory* in_factory, mirage_window* in_window); virtual void clear() override; diff --git a/src/mirage.cpp b/src/mirage.cpp index 1b4809c..4a993c8 100644 --- a/src/mirage.cpp +++ b/src/mirage.cpp @@ -1,15 +1,13 @@ -#include "mirage.h" - #define SOKOL_IMPL -#include "sokol/sokol_gfx.h" + +#include "mirage.h" #include #include -#include "core/render_window.h" +#include "core/window/render_window.h" #include "misc/mirage_scoped_duration_timer.h" - void mirage_log(const char* tag, uint32_t log_level, uint32_t log_item_id, const char* message_or_null, uint32_t line_nr, const char* filename_or_null, void* user_data) { if (log_level == 0) // painc @@ -38,6 +36,7 @@ void mirage_app::init() { .environment = render_context->get_environment(), }; sg_setup(desc); + render_context->end_init(); } // 初始化用时 std::cout << "mirage: " << "Initialization took " << std::chrono::duration_cast(duration).count() << "ms" << std::endl; @@ -50,7 +49,9 @@ void mirage_app::run() { render_context->tick(delta_time); last_time = get_current_time(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + // std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::yield(); } + render_context->cleanup(); delete render_context; } diff --git a/src/misc/angle.h b/src/misc/angle.h new file mode 100644 index 0000000..c0f3415 --- /dev/null +++ b/src/misc/angle.h @@ -0,0 +1,9 @@ +#pragma once + +consteval float operator"" _deg(const long double in_degree) { + return static_cast(in_degree * 3.1415926f / 180.0f); +} + +consteval float operator"" _deg(const unsigned long long in_degree) { + return in_degree * 3.1415926f / 180.0f; +} diff --git a/src/misc/color.cpp b/src/misc/color.cpp new file mode 100644 index 0000000..c5af376 --- /dev/null +++ b/src/misc/color.cpp @@ -0,0 +1,5 @@ +#include "color.h" + +const linear_color linear_color::white = { 1.0f, 1.0f, 1.0f, 1.0f }; +const linear_color linear_color::black = { 0.0f, 0.0f, 0.0f, 1.0f }; +const linear_color linear_color::transparent = { 0.0f, 0.0f, 0.0f, 1.0f }; diff --git a/src/misc/color.h b/src/misc/color.h new file mode 100644 index 0000000..194a41e --- /dev/null +++ b/src/misc/color.h @@ -0,0 +1,58 @@ +#pragma once +#include + +class linear_color { +public: + linear_color() : r(1), g(1), b(1), a(1) { + } + + linear_color(float in_r, float in_g, float in_b, float in_a = 1.0f) : r(in_r), g(in_g), b(in_b), a(in_a) { + } + + static linear_color from_srgb(float in_r, float in_g, float in_b, float in_a = 1.0f) { + return linear_color( + in_r <= 0.04045f ? in_r / 12.92f : std::pow((in_r + 0.055f) / 1.055f, 2.4f), + in_g <= 0.04045f ? in_g / 12.92f : std::pow((in_g + 0.055f) / 1.055f, 2.4f), + in_b <= 0.04045f ? in_b / 12.92f : std::pow((in_b + 0.055f) / 1.055f, 2.4f), + in_a + ); + } + + static linear_color from_srgb(const linear_color& in_color) { + return from_srgb(in_color.r, in_color.g, in_color.b, in_color.a); + } + + linear_color& operator+=(const linear_color& in_color) { + r += in_color.r; + g += in_color.g; + b += in_color.b; + a += in_color.a; + return *this; + } + + linear_color& operator-=(const linear_color& in_color) { + r -= in_color.r; + g -= in_color.g; + b -= in_color.b; + a -= in_color.a; + return *this; + } + + linear_color operator+(const linear_color& in_color) const { + return { r + in_color.r, g + in_color.g, b + in_color.b, a + in_color.a }; + } + + linear_color operator-(const linear_color& in_color) const { + return { r - in_color.r, g - in_color.g, b - in_color.b, a - in_color.a }; + } + + bool operator==(const linear_color& in_color) const { + return r == in_color.r && g == in_color.g && b == in_color.b && a == in_color.a; + } + + float r, g, b, a; + + static const linear_color white; + static const linear_color black; + static const linear_color transparent; +}; diff --git a/src/misc/mirage_type.h b/src/misc/mirage_type.h index 2e6a5a8..a138981 100644 --- a/src/misc/mirage_type.h +++ b/src/misc/mirage_type.h @@ -2,43 +2,150 @@ #include #include -using time_type = decltype(std::chrono::high_resolution_clock::now()); +#include "color.h" + +using time_type = decltype(std::chrono::high_resolution_clock::now()); using duration_type = decltype(std::chrono::high_resolution_clock::now() - std::chrono::high_resolution_clock::now()); -inline time_type get_current_time() { - return std::chrono::high_resolution_clock::now(); -} +inline time_type get_current_time() { return std::chrono::high_resolution_clock::now(); } -// struct VSInput { -// float2 position : POSITION; -// float2 uv : TEXCOORD0; -// float4 color : COLOR0; -// float4 param_a : TEXCOORD1; -// float4 param_b : TEXCOORD2; -// float4 param_c : TEXCOORD3; -// }; -struct mirage_vertex_param_t{ - union { - float a; - float x; - }; - union { - float b; - float y; - }; - union { - float c; - float z; - }; - union { - float d; - float w; - }; +// 左上,右上,左下,右下 +template +struct rect_quad : std::array { + // 默认构造函数由具体类型特化提供 + + rect_quad() : std::array() { + } + + // 单值构造 + rect_quad(const T& value) : std::array{ value, value, value, value } { + } + + // 左右构造 + rect_quad(const T& left, const T& right) : std::array{ left, right, left, right } { + } + + // 完整构造 + rect_quad(const T& left_top, const T& right_top, + const T& left_bottom, const T& right_bottom) : std::array{ + left_top, + right_top, + left_bottom, + right_bottom + } { + } }; + +struct mirage_vertex_param_t { + // 默认构造函数 + mirage_vertex_param_t() = default; + + // 通用构造函数 - 处理任何支持[]运算符的容器类型 + template, mirage_vertex_param_t> && + !std::is_arithmetic_v> + >, + typename = decltype(std::declval()[0])> // 检查是否支持索引访问 + mirage_vertex_param_t(const Container& container) { + try { + x = static_cast(container[0]); + + // 对于各种不同类型的容器,尝试安全地获取剩余元素 + // 固定大小容器 (std::array, rect_quad 等) + if constexpr (has_tuple_size::value) { + constexpr size_t N = std::tuple_size>::value; + y = (N > 1) ? static_cast(container[1]) : 0.0f; + z = (N > 2) ? static_cast(container[2]) : 0.0f; + w = (N > 3) ? static_cast(container[3]) : 0.0f; + } + // Eigen::Vector + else if constexpr (is_eigen_vector::value) { + constexpr int N = Container::RowsAtCompileTime; + y = (N > 1) ? static_cast(container[1]) : 0.0f; + z = (N > 2) ? static_cast(container[2]) : 0.0f; + w = (N > 3) ? static_cast(container[3]) : 0.0f; + } + // 动态大小容器 (std::vector 等) + else if constexpr (has_size_method::value) { + y = (container.size() > 1) ? static_cast(container[1]) : 0.0f; + z = (container.size() > 2) ? static_cast(container[2]) : 0.0f; + w = (container.size() > 3) ? static_cast(container[3]) : 0.0f; + } + // 其他情况 - 尝试访问但可能会抛出异常 + else { + try { y = static_cast(container[1]); } catch(...) { y = 0.0f; } + try { z = static_cast(container[2]); } catch(...) { z = 0.0f; } + try { w = static_cast(container[3]); } catch(...) { w = 0.0f; } + } + } catch(...) { + // 如果第一个元素访问失败,设置所有值为0 + x = y = z = w = 0.0f; + } + } + + // 处理标量类型的构造函数 + template>>> + mirage_vertex_param_t(T val) : x(val), y(0), z(0), w(0) {} + + // 处理2个标量的构造函数 + template + mirage_vertex_param_t(T x_val, T y_val) : x(x_val), y(y_val), z(0), w(0) {} + + // 处理3个标量的构造函数 + template + mirage_vertex_param_t(T x_val, T y_val, T z_val) : x(x_val), y(y_val), z(z_val), w(0) {} + + // 处理4个标量的构造函数 + template + mirage_vertex_param_t(T x_val, T y_val, T z_val, T w_val) : + x(x_val), y(y_val), z(z_val), w(w_val) {} + + // 处理两个容器的构造函数 + template()[0]), + typename = decltype(std::declval()[0])> + mirage_vertex_param_t(const A& a, const B& b) : + x(static_cast(a[0])), + y(static_cast(a[1])), + z(static_cast(b[0])), + w(static_cast(b[1])) {} + + // 辅助类型特征检测 + template + struct has_tuple_size : std::false_type {}; + + template + struct has_tuple_size>::value)>> + : std::true_type {}; + + template + struct has_size_method : std::false_type {}; + + template + struct has_size_method().size())>> + : std::true_type {}; + + template + struct is_eigen_vector : std::false_type {}; + + template + struct is_eigen_vector> + : std::true_type {}; + + // 数据成员 + union { float a; float x; }; + union { float b; float y; }; + union { float c; float z; }; + union { float d; float w; }; +}; + + struct mirage_vertex_t { - Eigen::Vector2f position; - Eigen::Vector2f uv; - Eigen::Vector4f color; + Eigen::Vector2f position; + Eigen::Vector2f uv; + linear_color color; mirage_vertex_param_t param_a; mirage_vertex_param_t param_b; mirage_vertex_param_t param_c; diff --git a/src/sokol/sokol_header.h b/src/sokol/sokol_header.h new file mode 100644 index 0000000..5f452e7 --- /dev/null +++ b/src/sokol/sokol_header.h @@ -0,0 +1,3 @@ +#pragma once + +#include "sokol/sokol_gfx.h" diff --git a/tools/mirage_shdc.exe b/tools/win_mirage_shdc.exe similarity index 99% rename from tools/mirage_shdc.exe rename to tools/win_mirage_shdc.exe index 6ae84846d82c5e271c0c5ff0b3d6265113f823a5..f98ff581a10fca25578cb142259842555d3f928a 100644 GIT binary patch delta 21078 zcmZvE1zc3i7e8|^uynIamy5Upf=yUhz%C$|fEcIT$U@qpM>wCUa}5YJAmrj{l1J~H zA)5$MKtUZ5ixgT~TG_Rx^RmsgwB~*#rpnrRSjwQs>&RR}JVAP%{6)rr{sj_6y29WK z5GF_Z67f%bxUntJPjzWsU#F6!DhG}UjLW&lMmvUNKp#|saJ;3J@ z5t2OUeTf9PydrpYb`$=~G|e?O*3x?38n5g%^sh2`MTR-@s1l1D-cFDT1>8%EQjI(ju_& zG!xEpWs@BU3|q}LJmE5$v$P8y%U~a zCc)$gw7x<-+un1*`a|_D_yXAhhFV%}n@sn9)|-9qK8`MEk2dnKBAvZ1?5zsLyV(n@ zgpw;H%0%}2n);=V!<#E)o?yC)4Ti$>t7Nc1q%I7A+gC{o`}?yRo5G6biEVJc*}VIy zf7wzT2d8Vqjf{n$Yb2d)1bU5l>m9pTtz}AeqT$sw(uov+JoYLI`}gLAv-JR}bCV(m}my z8;Wq%n(j6>g%%BT!C9`qY{y1nUFg7iz@=Lc@i$3;oz6qss9YM zC+xmSy6DF7IJ@oO=S>o$JBgT#x&=UqCPF^Yi8ii!A)5WCb!8zgF<|vv#fjqCfp`6T|Z4<2XB)W!b8(>Cx#U8 zd+}wln=?&jZEByV!LQq-m2k)twgC&pYp|-`B^t+`U7PL>7jF<(8&v@te3;m`KJGMD z6yX0tJPXBnFyjtsm3hHNJum+Aab6bO{&!xIMEH;*Ygb$LES#5a*Z|9iG|dfI^8{=j zQgB^eEzwk0sjE-cOIcNs^&^|#cXL>c2e?qZ==DEwNz})~j02CqNe8|1VzyN-ZiQbr zQ9vmECO&$t)t4r2g}7UI={>u29@p+{z42DqcZ-O<197weGqq{M|4eIhQ|Bz41RN{Q zZpW#b+4X_cyTpg@YNiE~ZW9MMgeARig{-@dTcGwf@gQ@+Tx$TOSQG?h#k<=a-F5^(k)~oAAc5 z_@k)rUx+-s;CM(;e{r|^0?oT~DMSestW*NKx^q#it_PO-K=U5lTj!D;xQlRFeA3v|UQutIAI;R)aC&2) zT~98>xoNwm(UR1E8zui~)Bs1Z5t#@bdU38~JVf{6MsZf#>0iCL09trTwNpc zg&QHts`9av52gg*oM-2R_?;^1N^*UUbPHR$(mgWZ1`sK8Xp*^&()k#fGo z7hK0gzHRh!j%%v9hJ=Y63(CG({>6j zmyib7U&{UDJ|;oRMXrrXS|+4iId_U958$j42N6Z%RNQb~@{S%@$Q2TPDO(&?J~?uh{p;3Gz59!Fwo`+L+zn@P znm!_YN*8>Y0p0k&$$V%V#IGWc;8qYH!$#Jf7x5$o!g}#z3z*UZe}D?~|9N;SNbU5S2>#gXOGqe9v)5n9k%iRU zNnfN(=7B`4Uq%|}ezE=}j$~195B(<`SqGLa_5DZ+4Qr`Cgd;p%=cT{ZoW$qGbv~q z2XVowUR&f6uD}RBy|a)&*jtN}qyntpS==Ri=*M>!D>&XtuM`eVP`S{3A1v$%e@*vy z@!Lm>J-xZ_2kDML%l(9OqA8s%w{d*_J_Y<3)X4>UceDJ(kJ+m(HG?rhmf!i%J-_R6 zx?`PWx28l_^{_N1{I^}di`ROx;;Eg#+v@hV{6*GM?O;o1f@>rWv8>ePnl{jb5te?s zWGDUF-_nQ^>|UY3sTTk_U?-%mgDeY;xF*QmJkpYG9%(hu28E8U-0EeHVBgnbY^Mjl zLsOlokhh*WQzXe?m;QIl;9kzMUeED^rWJ#k&@-BDnQPrgpKRP!Wc@>*9D<)J>rgV4 z_6Fw!CLy_P5b#c;8>+7i5b$@Ss%Ve7|SV@JB>i1kQMvKFk~T2CV` zQ1sUN4Czl}-dURwzK20EM3njqp>bHaz4|O{Z?N9Qk$y1tqqT%c=z@>dPjpESnDN6p zhCHWNepuHF*wNsHUfQ^rF9bpf>sn8^lJKxsPZ+~W`of+3hlV1!xU9$u?&}MC7*{qF z;j8x0W1D&~+|RMMqCE|SR|%;BJ0rXaH>Z&>SQpKk{T9MdLaDB$FpLPsJ}c4+#mQY} zXeFYPtb`mPQkrTloI%KWcq0_X2+|+3j(Lb!r)N*ch=d94$QycICiK%IJ3+6%Fo}fG zl>Wj3BeI458ZNZbCe_p;L3n^Cf71&|!UTQ6)O^;%?}dq-ENs#xBWdh(p&>_tVRVM@ z6(5^f1gqY+bcHE$VJTTj^=1ljM`Y6$vxJ8UISel|h408kdM8WRog>B6d9Lt|HrWb4 zbA@SacjgIu5Iw!g>?yq{T)gOS57+aB_2f0w&KJtbBTx=JZrX~1N|QsF7wO~PfuSEghy@LO@(hSMQig*sLwmX16ow09t_Xyyaq zFgtRN-uoe3FCcB8fERs4S(T+N0xPl-Y-~j*$sPK@R%FX-Z@XKl1s9w}&eYLSbjwt5 z>|7y^Q2<-#g;`Q_Z_y%MuJHuj(pFSy$%II-NG(KyMNbGB39+G~rTn(7h2UeEZ%r+v zq6l3Q2Jw-iV|>oKLQoi&TEp9$GE0bx5?$qU*8b-bjp{2B5YiuF`iXw<(W?ug(^Z)V zoz-7-oCuOu7NYgr0wS{lT_Aa&=qh?uL4!o!_{lYe@Fpp>84QmWjpFN<7lPS&m5APr z7G2WgepbS^cu_1_N{vT|@(2-8)kx7;OR|BIsiFn8MjpxR)zmU|FwEv!Iluvh$OMJo zS%qk_KAA8J*g z(vEDW-wkczn~`{k^RWqSFFITBXOJ2_qE;r1p?C1@46FTLY4M2O^06@#krVW3ZyOgq zvWp7(*j&;lcs<(YD=&DlSluT)xL09I-^ba=ZAlk8af;1G^lYi)G@I6jWGL-3*JhL{ z|1GHi%&nC&$U~8xYgTT9WQ$}+&C2bgPm-Q=%wUJaF!Bm6u67tBnA0zpjTX!&gAfLn zEz*Z~gC`l%_1Cmbt#yO=6xu$7!^Bku`0qpD@aGGgG{-ST|oEF9sS6bM@d6OH{Pcr9O zrsMps4C0Zk|UO|ZjQ5r$-X(x+j^1axw$8u zY3@mvb&Xs`J9zijRRVD*Tx(E3pLCTHengu&;GEY^3@N8v-;i)R{Isi(a26J68?V`GZY1|zUz?J@;O$%2I_{kT zkdv|NF)^@A1Y$>iPk1Ba^J+Pp*vgLLkU3ht?r#gG17n9{MWicwXoIwJd+LE z+|{ySo4XFjl*JA#+R_y}+$ZZ06&3Auj|yYkN4NP%LhjQ#LGyhABF@b>ZI0wY^G#b! z8&CF8Zf1+@6}W+m)tTWL3CW6WF8^=p&?)Lg1b2B&sZGvRFh_Cg%!-42(yRD9cVy-B$;r6iGLax z(({E9UtQcg<&qUVrODSsvk=vm$-Yen!7(PmB=_Uo_4E|Ah0z8WGf{7hzVV?LSo0U z6R=8R$&-7z=hjN-xwS2aHA5GtRb9(50{2$a)yI3&c1}ol)U`}^C39)Zw=GQxNu>er zTJ|Eun`Sk%Y(|Jata#sYA6u#qE%y*oPVGLm{NO^CP!pl&R>Jh0$nyv(h^%w1(^xHcAinhlg_d8bSAvveN81jY47>Pot=ds&j}ov1wK7JYsmrn zuBRs&`*u*?+mn(JR2J-6#teeHA)dRKED80TN4WAWa4*Dj2uzcC_Gf!e=Gl+1eGu+x zNSLu3;dxL%F2L&no-t$!4Ib!e(}BG~p{JBE8^6eNfFsi$>pd&XnYFs#bK)ej9Spp^ zDo`xdczcDIkVEuUpjS^bBB!mxyzJ5YSREej#gmCNBGPM(AvsRJ4E8!;Nc8B=L@#|S zv`Y?Vc*Sze z(!k%@dj~m4-#B|m2uK!m_VA8r*Zj&9wz_I_?Eh9ZsA=@omH(@0KEP=YZy)AOd3Z}m zB^9^u4k08G#!I|o$Y**&;vL_HpSNTR99o%f4c+3rw~+m`Ad#BfLLzf&vwL zsvayYkXS?K1n(Y)D%= zW1+XJ4$~dWyz}%Br*&WFeK3$eX*~rF4odW(CQrTR5@Juwo_Q}IB!_xD_ufFbn`Y4B zxwkX@_`XMLt$Y@p1@fYR4iyvpTx@XSc9Geb@GQ1qXM^~i- z-SXtR^p~R5_cDIx^)!t-v=jTF&dk2{yC`*M#Q|4;7iTp0iL@mT>6xxRtKCTeO-=CW zXv{Y0WS=;WUzw8zTSi*C(3a^wJ1t2ay`SeJHs;nOz=J#=3H4F=d~L=g*DW z_>A=+j=72^AF62b?Vu!2>4^ocoox9N;gjLswP0IHHnrY_8%jpEZf-3j+!iU|e_Eg- zX#JclqGsD#_u54U(Sb>AE~k)kXnCtmjEBE#%D?*ww~$0#{@*QxuFb7BT{)(3@3cX` zfm;3D#(O%SymAu!{F>$nB`K+TFlJ!eG5oC+li>UG3I~|lTW$l2vUCHm9Mm?BbcRWT z+TP~lYbI%oo9(QZ@0<${Esz;q7Lt=Rc?2TDAF<*Dl121k=$K z?bZ?IS68*`6-Nf<4*J}V4*J}F`CWEcu5`e|k}JO2fr_to4Bm(uY}_0_#ZZ2WT{0~5 zEw`t35B&P;k}fduncqp04u181d&yq=d1t!UAj~PzB_B+D68YjcW5SbXZz480N3yhM%>rAm2 z&c5|K%@p1{KUYFN(mf4+n>e=RKl-6CK^6DOuaW8M&wkwrPqdTOGMird;s-pN=O2D* zukeSTEzdRmq?;T4#_)m#-^Tu9vpdnwod0)%!3OB6@9z#nwf*0dIEd2mKfy2fG!`Z_ zDr{(5U4N8ZgQ2g${~+_n^!z;uVqabK{a-RAW8m-N$i%vb{|`elng+M^kK~9oRJ8Y> zL(0Iuga1;7T6FMN5p;a|`T3g(`OuAHA&LWcD2VVs#9*L4{s*|_>mj0#zZiTY{TDJ7 z5b1w`@TP0W!q#J|W^kmhe+3`4=67j!KmUb%$!blB_UP}QV$6T791HfHsvO{Qy#Eps z0I4JV&tr_B$4LJ{!~&{E`qz=3FlLm0F~3ta7XA|JJAqAte=2c>!UX>v%x@X(e}Y@F z0E!a)MbtIXzhz4jPs2+6jX36jm;1-+pzhpJ;eV2JgPE27W5_jlR_TA0Xwd^z{@<7K zpAL>;5P%(A2@Dv?r(+474k3-~6yPOfxAYB&!P9%aZ@?r@kiKdR+tGmf+KG-E5D=}y zeO(I2V*^%`K6Jw1fMmkprnmr!$DYNkAWEU z&j}))6NUxEn0=~NKkSeC0IKIAgYM=zeIVYoup8YvJRo@^hRz&K0^c&9%rwxY1<9nX zItE%1ZtM~83JCO~ll%hr5`?Hd0|Fm1V<9lGQlHnMNe~pA83jLj1`g&1?gHnKKnb1Q zE3gY16128=;1EJes8dK_sxxoDAPJToo#_nQ69b=d7UkeOC(xZ9N(xLPOtg;;ykkVF zsO8ka4*LAaj3g%9++adhpfy^+^Rfc*%+8`da{`MU$Xoi?;=sT3dEb6XPVni2sT)!dQg@^tNIj8y(Qku#pL<0=hi}oaz1K z;En>6=kF#5x8z6}wNDRTZooBVz`@L54|0t@%M3p0!2KEuTNeheW0vuv;0ytObzmI$ zb__)4dqr@2Chu1Uk8=~)w~NCQ>H;p2N;_C;K2;lbod_-^2@rBJcrUlE4U9V->`a@T z3JxZuJ54?vY_7}udd6vXU*(11$z&Ndx`>ZOFR9(7;9A1(_K1U7_u`$Y@JcXBivG>x zVCy$UGnjQXcsH2>9j^tiBp2x4*MbAId8JF7TGm1P+rbCW?f8H{+CQD*v>+UikZ9hcXmT)~6!JBBQ z8!a3v?M1lT)$nAbR19B+N$>L4t75=uDazQN!=+#NJ(a&pb|a))_=gq0OARBW@5w3p zc%-yBniwk0RoXg1x{B~u7yNG2I9ggkgsUMDtx;=QktD@+LCro!`dyb?g4>g%PYqt? z$KWy64a=VbQKn^%v_3^z!4oe!J6&py#xX6QB6TDDm$|chPj_=+nMstu#4@$Q%zrI##MfiaMF&ec!?U3}d zKGS(;q+6L$eik3f4{j9$bwAUbX=Iq7CFY<#mm-FS-pvcCKhFT=*V6miWgE;HGTKF!!ZX3rUG`FaYJ15JbNu=3(Hf_g3z3~A zcj0`93`lQC43!;qe+VY04fBm#y_l$~ep(j?g}u4cZvd+<$>?ch%4 zYYK*wWwqRcJWb*KWZ5U~SgxjEk|uk|4EHqI5?u@`mS)Hb7#<^+bu=Q;Fd$F1j%-xr z%bp63r$nod>b;Cn_Ec}atdS?HsGwLDs?GF$8NLHZT~tdWw91~ z`~J}kaq2}6u9mGQeBZv&;Ot!DN{iRZYV{c|wO!_;%|J~mLv(01c>N{Y#9!(Z4flUk zdQk0yvMz)l8yF2y(oAcJt(86FlLMkvhKbfR=8!A|{Yd|R>#U&euGHM=CEt%iqx;JE_OlqQS{zcikVqd}L#cdEU|%i#)+cXuDC32H66 z7A_&o1-Kp_PWTEp&1JyL*0k%*@VDx%{CBt!!H7_&yWu7rKf-<>^m#6A4ypIU-!q8( zK{(}0YzKntFhqOXJ`DfCcHX1#dt@<~J`Vp*YN)}J@DF-qGi`hwKH8KIX&k^-J%-l) z3fJz+*Z(zuiC08&J4HzPGI+ftB2|p@Dc>H^p5qLAgU!JRKX|z_!k3g%hg}iVbvTc1 z?4kqRzAs`H29dgGN_`GQoFHTshz>^F(K{750L7zn3mlq(Qp&V8;;0_ENw1uYFdz&s zITe9eGpsrtv4@1i@H6;R4t8hp2ZM%ZBi8Mm<^vU!}~=ElA_6kjMYZEgo2!=s)v ztRf<60Mn&?qI9&GXzLfXpSkw^q8>UkJ1sRzf?`WMEvnBNwhtZp)|WAlcynJT!bi!X zGX_vl$Jf70qIs>FFIfAIj1Xw$byRu@=o4z!4Q|qK47>#y&E*u^Mah`LO&{NQqB$ zrA_@}hdGi6IxHpDoA56Ap>Th*NKCg+j=dYi+Xsh&>s%oM!#iT*#AFK?-;9lEH_%Pp z#UCs651P*4bboc_|2m7!pz3C9Uxo`|2Miaw6+6!WL&j$x$A*&CAbb*g9uo$hK8bxo z9?-K-W7`n2DYxlaENyx=*myhBpVe`Q+U5@flj=Bc+IL~xCJ$yV9*rv`&b0VwoQXCu zgGDFf%E(OWdn(Suo*y6zhWIDo3K@^%QW>K6IBqAeCk$q5Y)SV%iL+J=58jDyt=>3)$Dbu{;jg>#2bk-8Fa82I+#U|z zjdw?36<@=v?7C~D)WlQOhS2dr{2PN@+wQo{1Y!9T!Tf~E0b~#3>-YsW-65s0syPHb ziXYA!VhQaM8@m5d{5>I?mhbVKm|gHA{ybqO=&yJq!e#%0Fm8l|_HB$85U$@(bqVG* z#c$)L{7@H^@guf#72h?@x{?uyLt-b~2pvM=XcRwUvJUtBJp`GI5Yan&BigGYmBu5y z38sT|Ga0dmMT<;EY-!DN`+^{9yG#g2x{Y|CZ?iWDU!bSB1vHEoIzrgzM7`WgK_iex z(n~?3%*^=HYrAUH@4G${9je=R)F_VG`29zX_h1%q#wgd}81VXWd(=2xh66ktWv)iq zzK>dC!_BXUiw+6yG}JNy(E&t{trF_VbZBRt@Pt{>!i1f=OyfBuSef%D>$<3)L-|$- zkDD{Esec0Hi7{OooiJ)XSpzzc5`L2PlzW`uX3XsR1}qljy1h@JZtq9?+K^c2XOOs# z=?Q~G3pJo(lsK6%XU#Zqr->l-UMGB0Jy>>2X%85QDrApvNNn%M9Kx=NO2U2HOLull z6o{A^HYjnC9^bdR6OgZ!Qdm7av6l3Lr1-=~%mp8jc;B9Zz_Su73I8;s)1Upi4AZja zWF_`xwqjP|c^zIay_3dunH4E&3!icltB4~gauXjJ_@}br@F6Im9^90<(#3g+ZHBYp zSbbu??+6P`B@1`|i?5>C2~}78uh?lz=RZ#zL73cmk*JnCFB3C4@)=INP8@>nz9v<((vq$k65_saJbUUmDjTDYU)dSfF|s|M!#Bq7EPT{3xsa-^X31FFGWhPhrPy zO_DlFw>Igsh+iHMsM#ozLrL@bZ~lSou!!N-p`=s1K9=mv^Ihoi!%2@Q3yRGcgP>aO zl^J8`l^J6NwFCp;0m;{K5&oh(J0(Bm7(&}M`2r!`==h-IzX-G6dL&@hKh zMM!&2)N^Kr_Q;6`$=`5cHLIA;q&;b=53j?Bc$)1Pm1!w z1kCQjly!XVV}uKftHod`N;$(MpeUs`A-S}h-xF|K!ker4`ZmG3u1)SPdhd*`6SL4|JY?0cEU-pm*ul-fs^n81=DsUd=6i+r_AoqIx5lD`8r7@DfDgDH4cBU2CZH^CPs@)4@P$o3rOx!*#MCK-p}{GsEBFeut(fBo9VVx4BS&+e zPfn%JC#U&3vQUd6O~N9(inItn7BM=Rwgv;dold14BwOjvQ)$Nx$xC|pYT9=p|IWk* z8s56v(Ce?#Fo22?lhbW=mx zA@!I(qye{50E^z?kXn39`^9$0r?ksFdyB7WRlE(RD5}T1>s#7HOi6q}{_r4ZWXfhZ*FLKdH=en3Nts$YVH_lKzO2AXhN`Yz^@E7I)TdqHD%x)1GFk)CYK zpzD?CyGeUGZB=@t33*IU?MQEFhYs$?bLmfXSRTWbbkj%{G7wLZ=&ilxt(Ir-W1U+E z8s$EvY>wbmKX&c6d9A%>%5!oM-mIB2fQ+F%*G|DbG!mp6r|f6m>82@zO;OE^JTN7c zn9+>~rZ{V(uBbaSMNdGU!L^f9)({K{pPI6xhhF|0FU^w%J6bvLMLGzC8c*#TTja;&DVK}X!jda9iR+V#@bQ)5^py!BMM5GUI; zWU7^v*; zL!$#gyuMD6k_{xXQ=qvQ6zFU&LEq_JFJ$w4ipikSoY&2$sliVbZ)?OZj$k zonC7xAI$L?=R8<^LPTZW@|`*?yxLZtNf>6;PVQ*PT;;CvDI61G-Q~IH#=znp^1m>| zIHae1FR$F?@yE=a#tC-8k{)t*INM7;o9(XN^3D9_9sjm41gBtmBd6F7K2mJfBSgM{ zySfdA_QW@P8zN6-A^uSLX_5)8r1Bc>_7>>VTW$^YQh5@y#$@t9o^AIC`7oZrgHdur zU9tm_T6qigtj5T1=rE&csQjZY3rHu(c|!KU$I^R)(eJ5p zH+8@$UEWHYkDuegVijUKdAdA;Fi2;He3&tV!SdwT4LWZH@`VH~-oPTcSeqdOrE-~o zCnG(e^lY9ZJyRi{gvq4wzl(#bVmquni+RB^8MV(LI2)DB`f6HCFDN*T_=wb>T~V>fpxa>?JhS@nd|fsPdOw{{-86M`t$b zvORZZ<}p6hr@00u$~ZgoISHW&bu$fea49DBn`N_-w1fPwvmUd!%C}hudhxUOi#6vX zv}@)LX1;dK97Om*d&LZjf|Bl;mXH;c`Gv{2?wMl3neB#1X(nb>^vImglL^o@ICBGk zXuDYbM9PO`b|D7I+r;W|qIEgESD-753(b7Ul&~~2PLC&R#Tp#7u5aeAK=KgI6lbnt zuvJNB84I8*GR1C)XSCdw8N(ts+cFo&YtMhyObhPIaX0p~%DRK85a24z`oyaqH-kmF zDjV8bl+~KU?8=e0S(pQb{(*PaYeTO8VHoF~s#MN$_vIv<^n{CdTLkt8m z)|XjG{xoQCNbC)twq!Ms>SvE(j%5Apl!WItu5_b|Mm(w{G4> zJ|of@TF*_lq9N<&ZRksO!>3>Ko-<>vao)Q1EReGmpY&dm?3%YlwRv-`4Me`aHN z2zLC;p2IHw${vFN>#|?jAxyjd!a7WyZOopH*$JvfybmKkjoC+x*d2yBV=zMIWR%m7 zBQlt5lCy>^r}m~fMfM2cuWgyLQyYzeVLmxAL>u<_Y^arOuIw9=Sr&OU25cCP zd0$b0{&LK7)MdW1I4@F{VWlne{t~eGOq)EZKB~b zndik3cbeBbucrYnMO|dxMP|K5x!*7EBKKpJ zx(+V)&)diH#RlYwjCkEDdyOk_Fh1{;2@%u$NqJHFmYTK7cX{%b{ny+pLu-v;O7e?8c8+VXHgfjOo}@48&@t1qvARRo{U zr906p%?sOGp-o~CSonxpMS+D~ZSX}}^)C#F!=U?^ErrQKQctfQFZ^gsFs12Ap+t15 zx#<7%2<+o(p#&pZ?XDJP3Jmp~S)=aiLlY4^P)r}*DO_ew{9)dk!agiJ^iAPQOqwyE zhu#&2M6nTcFOnGM?-BmfHEcS_3u*uEMMnAb1Rj?x`E4nfOZQ%3EiektDkSs5f zib7-9J?i86pD#%eB3NED@)>hW@{2KSg-u zz^y9Y$o=Y1x2`HK;aD=mT6`Qr-fN2&vSh?{#WfC0*PJMB%kycit<`G?v8RjAvlNaq z#kqa>ANQ>op5P3f>nH2c+jb>MJYq*Zol0&XQ0nMhvRs`T=u(o#PrGFW55k5xLVwqi z3w+*9D=1FDj}kh&mHc2nf3p%jZ7%r=bd{7egV7!(`XkAk}xO|<*rC@N0^05 z6+74@$Q1jzd7GgrT;UFV!W8!jCMJd}>d0nj3|Cy|u5Zv({ftm-<(k&R=01vRoX2|2 z#SM{)t<3j{!iH1Vs;|+wzKV}(bg#c+ts%1)hbevt7|x%lh%{%>_vwmh2pJukuJ}gY z(|t1(uZgyHrlm$v!)du<6hY5yrs9G6vz;sjMu9PLWVXVF=mk!<#I@251BZ7OF0^uv z!U6$0*f3XtAL~KkJjHgF^O>y(<+*055R|PD(q(yyY1$06D^fhy)n1#3kLk8RVWqJb z#S0WsI!q8(DxRTXGqg(4UWd1hvsB0Dz_&&rpbwWQ#t|}s_F1Mt+pZT(TdtVIAC1;D zq1c2vFhuU4;c zm|nLj-N%qp+tOz&+s&@DQ+pBu34KauV z2DH^KTZG?uU(_xuW|1VFGQbZ{zUq{%VXv%Pwt)^)!d+8k28E1S_CCS}@$Pk3dn zHmc|Z(=txT=GVO}mt($(N7(`^HZPsZelii;x$GX_{eU@`D^!*=t4kT;KX>TG&@yLB zmZ=E;^9cV|wS2hZ|7^2@9REW<-lmc{#zHHWSRLMK>IT9&~=cZgm;Pw$9QAYsyv- z-XqE!vv6W9Y4y6YNCc1QtMz4z%+TVQzrU=5+N(NHhQJ*5f3R$$9rwjm8D8d!G1;=d zo%lIV%pgdwR0KVR<*V7P!g3MEt$hd$w&fCNWm|rT#oukq-*cz$YpxmFm*3)!-BTB+ zg+qA)$G896OcT;y=v2OmA92S_BLT-bm!IP&-!@b0QyTA5K8PcjW#?ADP={@S7Ui8d zJ-dr$_`&GR5Lk9yWk+|kEPtWHHk5DqE`HEyGmYbcz$dy5t;_5Ao~O)I-7>9cW}9-{ z6=(ssEyq+1bl2OJpKitn)tG5w=-Z>q@7v-*STMPK3~GtS$>sPplAH#mmruFMvWcV% zc8L+)m^g94GyyxOQx?=rVV^|2UQnvTRQ-nqRU+==JBYfbbYz*ThValpnalO~&lOs3 zsLar3ab0U=19RVn$_gijHMUb0Aga;4z4APMy7jrevXn5(tD~|j5j;6;f``mkty5^d zpR!z?)fj+HSjw036314wvvN4EoxImX3%>VIy3xcSWiP^O?Ju^vnT4YFNmDz4ViK<$7&*!pU7Nj z*+*H(%(Xtsb3B{Meo6}+Vg;4aN_B=)jMA0xnafPTJT=x1CdDg-ba<>1@i8AdYp`;L zHrWB+hGA0_T*H;m_?Rk9ONuQqYX+BOgfi5WDdQAnkouF#G$n3X)Sc*V>zhoN%$K9Cd!C1C;F*Y zxz&Q`!s^RPA50d!epz{s``nx@Sqr#-MY(|ZLHbqYEi!~gUQ-4T&Q1JhIe#Z!?6|I+ zj>aHubxXO6<4-!7Fd^g!+JEB;;?w?C7OCyNd#v_`?eE%;53m-c#X}|41^DDqZaVnVr<>JV3O7C!r`T%d=K zs*XL;SM`YbeZHz&EP&NorE=kWuQk>{*R%VozO#h$eyVF6AG^|+$v7vPI#AWen7n`^ zBURJ+Nni{Y3zb*?) z6{-9=ZZW3zwpLkzSfN_X;<*Y{H-he?cA4r2` zV#~NhwZw&eK~t+j#8)o>wj5K5>FGl%FT(Be)07;Js4(z|KFLwlD8e_dfZzAZNP}$}n8H3o!L$2D>eGL2PNgkGZanN_K-vWOMMlxkiI{51ADMx-9kO->cLUc?U_*D_1T$Uoj2^!S>(m%5<)2&qe(xtw;W78f5(-~{GrV{PbGfdT;QJEW zmDIy~-qYfDyPD_lj9DNrpoW>RFX5C93eIH>5CTCBFpL>S4e$fwaf{xABeQ$o18N0a z^p9|X?aNQ_OP6V+pRj=EzW<;*nqUIQ&;Mo!f)mA#(4DI&Aou7wu40W2=|HFIReUt$ zgPs{`936#KMJK}Fe`2W44}lNX6^HpRj}0~I_>Qomn(*x(8LD%E=|Y=|8-(|JV5r$w z)pqRGtM@dGq0zqLDSz_r?@B|*iVd5Jk3JW_y&OF0^RgP?G zMJo>Bo*C0CW-?oFdc|U1INMNd9?;!0D)1|2+`ICM(;ROpHw2fEKr6Z>vqD=JH^wim@wD@VoLrW%&zE?~PL6GV}_sXaI6KVkFkHanL-#seFwB<+7GXR&L zh)R#2T6tfGS*nO zOc?7@P>Fyx!sdmQwmQ7-SOb_iwb~o5l~%?w`?;)gAH&kiDxYh!Kq*w__~3Jfo~Rsc z#(ixD$FEmTLi@Vojmkv?kLT5!m18hW)%8|o7t$4G-KvZsPw0(Xm3dYKzcYSYIfcJ! zXu#}C7x<1P_ImFsahGF&^?l_$w#nXC-e*SbhswLmVf|RSh-c8!m&ybDJI>&@m4#V= z^x4-+Oev}$2I{;*=<>aCFTX?U-xhTUB;5T`iK~97QJ)!|R^a&^vj?qzRleg_|I*ip zAE(C3@14m4YTK!*iYEx#b*-vHOm&x^>*cU@b(JMm#8jzxq5_X0RWtF^wv|Jw3iU|_v`MUbjPWc{ zQq>@|>0n}E)jYwT3G5k`&^W!q7A%XZPO~htqN-z@{&?1x3q4j`by=Uc8mX_z;MreM zHGv^;l~tI%h(JwM)n#t|a81)@)m0$|YE+;qpQi)#nG$yd>s3`(xFh}HUQv}Z7_6>Z z$^Gg_vFja4T<}Y%wWxNdg=W>hgnRc~U4lpE)jnL+?}EPt7JB|Jlwg5{{w%5o;GccK zpXLx~S)IeZe5UEA-m-cm&&NN;Y!6@j069ri{Xm-=c@GYXt0gqe0ej-~|JIbwIaS-} zaL2E!OJFXp-as&&UtFC*xYVnfCZn2H_cB2{bzQq^^@l+1s(nOzj_cuc{V%SB2}$j% z-7MK>av{}~m?3pNqy*+<@x8_&F=}G+EzK1FNqh zuyo0Yr!asyx|-N9i+56Wfi7G7sW{G~@O*0ZR(8~;Rlh}e zj22{6*9&<6X?id;2fsnNKeyV1U?_iX^;(v(J+Jz`4(FNz6Q)*Af?xBi4LJOM>c!OR zzA(6;dMNi{A}pL%?MQDFRDV`Wn&RrhmV8uqJ@p4Nbnc#NG)aX3d#k_m_TBW<;dx5; zRh#Rw`0}CZr`#_;*m@Xm)=~drnDk?Bj%o)tz)jl@|ZMNuEmso>%YTMW;;uf3@3l4PI9xEks&G4PGw} zePrGZ{}#~m1r{TkSYAs=!TfHyqb@8#T8gxcj=HeiH=DEo<$#*GWChp`tjR=(wsK(2 zUSd9`2LFmtzRvsco3)Oxw7(cSD!S&J88acRZJS|f zE~)Y3Cyh%OHgaP6gfxi@?en_k6bCNvYDSp-w|$_2mKIdKs|hy#PxC+nXna?51^>>K zR&HHG4blpvl}M|QRwJ!JT8p#}X+6>gq>V_MkTxT2LE4J64QV^l4y2t(yO1c-Zlpa( zdy)1b?MFI*^cT`Wq*|myNQaS*ARR?IhIAb11ky>QQ%I+g&LEvdszW-5bROvf(nX|8 zNSBeWAYDbehIAe22GUKWTS&K&?jZe*bQkF!(tV@{NDq-7Aw5QVg7g&W8B#scbEFqY zFOgm$y+(S2^cLwIQUlU^qz_0Rkv<`PM*4#E73mw&ccdRkKaqYRH6k??)HRS2B*r+# zc*e9DLsz%10j-O=1_5JwjOjCGz?dOpMvNIVX2O^$V`hw*GiJe`Vt*!yTx~^*wGv>}%bH+RvYr&X=v6hT^ zGUmmYH)E|B^I^=FvDS>WVXQ4;BGUmsaKVt!m1v1u&vCfQjVXP}TSl7^v zcw7$fnR+!5cd!=zAuz2tq)}l#$fyoI*k5YX`lrqa|Bru0Osf{_YX8)gv^o|<4z@bh zok&xN&XOUkU5GtI4qst)a1FL8_aQM$I*sl@+6$s>-0U8j=`WdvB{u;v3|Psxd3|sh zmR$5r&2_cR+v#hWw=te%G~6(9$%TnMh{zzT!|*#xw6rI4y-OPK0tzi<0^gWc?W%z}P^> k1~C@RSPWya)=djJ3c delta 21049 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 z