native renderer: game world at MT_RENDER_SCALE (0.75 on Android), MSAA default 2
Draws recorded inside CPythonApplication::RenderGame are marked Render3DDraw::scene and drawn by a scene pass compatible with pass_ into a reduced-resolution image, composited where RenderGame drew; the UI stays full resolution. --render-scale / MT_RENDER_SCALE, desktop default 1.0 (unchanged path). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b7c215ee42
commit
44a020f01e
@@ -29,6 +29,7 @@ set(MT_NATIVE_RENDER_SOURCES
|
|||||||
vk_device.cpp
|
vk_device.cpp
|
||||||
vk_frame.cpp
|
vk_frame.cpp
|
||||||
vk_pipelines.cpp
|
vk_pipelines.cpp
|
||||||
|
vk_scene.cpp
|
||||||
vk_resources.cpp
|
vk_resources.cpp
|
||||||
vk_textures.cpp
|
vk_textures.cpp
|
||||||
stb_image_impl.cpp
|
stb_image_impl.cpp
|
||||||
|
|||||||
@@ -373,7 +373,8 @@ int run_live_client(
|
|||||||
native_perf::PerfLog perf_log;
|
native_perf::PerfLog perf_log;
|
||||||
if (g_perf_log) {
|
if (g_perf_log) {
|
||||||
perf_log.open("device=" + renderer.device_name() + " present_mode=" + renderer.present_mode_name() +
|
perf_log.open("device=" + renderer.device_name() + " present_mode=" + renderer.present_mode_name() +
|
||||||
" msaa=" + std::to_string(renderer.msaa_samples()) + " size=" + std::to_string(renderer.width()) +
|
" msaa=" + std::to_string(renderer.msaa_samples()) +
|
||||||
|
" render_scale=" + std::to_string(renderer.render_scale()) + " size=" + std::to_string(renderer.width()) +
|
||||||
"x" + std::to_string(renderer.height()) + " gpu_skinning=" + std::to_string(gpu_skinning) +
|
"x" + std::to_string(renderer.height()) + " gpu_skinning=" + std::to_string(gpu_skinning) +
|
||||||
" terrain=" + std::to_string(native_terrain));
|
" terrain=" + std::to_string(native_terrain));
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -147,6 +147,7 @@ int main(int argc, char** argv) {
|
|||||||
else if (arg == "--fake-idle-ms" && i+1 < argc) setenv("MT_FAKE_IDLE_MS", argv[++i], 1);
|
else if (arg == "--fake-idle-ms" && i+1 < argc) setenv("MT_FAKE_IDLE_MS", argv[++i], 1);
|
||||||
else if (arg == "--frames-in-flight" && i+1 < argc) frames_in_flight = std::stoi(argv[++i]);
|
else if (arg == "--frames-in-flight" && i+1 < argc) frames_in_flight = std::stoi(argv[++i]);
|
||||||
else if (arg == "--msaa" && i+1 < argc) setenv("MT_MSAA", argv[++i], 1);
|
else if (arg == "--msaa" && i+1 < argc) setenv("MT_MSAA", argv[++i], 1);
|
||||||
|
else if (arg == "--render-scale" && i+1 < argc) setenv("MT_RENDER_SCALE", argv[++i], 1);
|
||||||
else if (arg == "--cpu-shadow") setenv("MT_CPU_SHADOW", "1", 1);
|
else if (arg == "--cpu-shadow") setenv("MT_CPU_SHADOW", "1", 1);
|
||||||
else throw std::runtime_error(
|
else throw std::runtime_error(
|
||||||
"usage: mt_native_render [--frames N] [--interactive] [--login-screen|--selection-screen] "
|
"usage: mt_native_render [--frames N] [--interactive] [--login-screen|--selection-screen] "
|
||||||
@@ -154,7 +155,7 @@ int main(int argc, char** argv) {
|
|||||||
"[--draws N] [--triangles-per-draw N] [--capture FILE] [--animate-first-draw] "
|
"[--draws N] [--triangles-per-draw N] [--capture FILE] [--animate-first-draw] "
|
||||||
"[--animate-bones] [--no-vsync] [--live-client DIR] [--fake-mobs N] "
|
"[--animate-bones] [--no-vsync] [--live-client DIR] [--fake-mobs N] "
|
||||||
"[--gpu-skinning|--no-gpu-skinning] [--no-terrain] [--mobile] [--capture-out FILE] [--screenshot-out FILE.bmp] [--show-fps] [--perf-log] "
|
"[--gpu-skinning|--no-gpu-skinning] [--no-terrain] [--mobile] [--capture-out FILE] [--screenshot-out FILE.bmp] [--show-fps] [--perf-log] "
|
||||||
"[--fps-cap N] [--refresh-rate HZ] [--fps-mode 0|1|2] [--idle-fps N] [--idle-after S] [--frames-in-flight 1|2] [--msaa 1|2|4|8] [--fake-idle-ms MS] [--cpu-shadow]");
|
"[--fps-cap N] [--refresh-rate HZ] [--fps-mode 0|1|2] [--idle-fps N] [--idle-after S] [--frames-in-flight 1|2] [--msaa 1|2|4|8] [--render-scale 0.25..1] [--fake-idle-ms MS] [--cpu-shadow]");
|
||||||
}
|
}
|
||||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||||
if (native_perf::PerfLog::requested_by_env()) g_perf_log = true;
|
if (native_perf::PerfLog::requested_by_env()) g_perf_log = true;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ void VulkanWindow::select_device() {
|
|||||||
}
|
}
|
||||||
// Multisampled 3D edges; MT_MSAA=1 keeps the single-sample path, 2/4/8 request a count.
|
// Multisampled 3D edges; MT_MSAA=1 keeps the single-sample path, 2/4/8 request a count.
|
||||||
{
|
{
|
||||||
int wanted = 4;
|
int wanted = 2;
|
||||||
if (const char* env = std::getenv("MT_MSAA")) wanted = std::max(1, std::atoi(env));
|
if (const char* env = std::getenv("MT_MSAA")) wanted = std::max(1, std::atoi(env));
|
||||||
const VkSampleCountFlags supported =
|
const VkSampleCountFlags supported =
|
||||||
properties.limits.framebufferColorSampleCounts & properties.limits.framebufferDepthSampleCounts;
|
properties.limits.framebufferColorSampleCounts & properties.limits.framebufferDepthSampleCounts;
|
||||||
@@ -186,6 +186,7 @@ void VulkanWindow::recreate_swapchain() {
|
|||||||
if (depth_image_) { vkDestroyImage(device_, depth_image_, nullptr); depth_image_ = VK_NULL_HANDLE; }
|
if (depth_image_) { vkDestroyImage(device_, depth_image_, nullptr); depth_image_ = VK_NULL_HANDLE; }
|
||||||
if (depth_memory_) { vkFreeMemory(device_, depth_memory_, nullptr); depth_memory_ = VK_NULL_HANDLE; }
|
if (depth_memory_) { vkFreeMemory(device_, depth_memory_, nullptr); depth_memory_ = VK_NULL_HANDLE; }
|
||||||
destroy_msaa_color();
|
destroy_msaa_color();
|
||||||
|
destroy_scene_target();
|
||||||
for (auto view : image_views_) vkDestroyImageView(device_, view, nullptr);
|
for (auto view : image_views_) vkDestroyImageView(device_, view, nullptr);
|
||||||
image_views_.clear();
|
image_views_.clear();
|
||||||
if (swapchain_) { vkDestroySwapchainKHR(device_, swapchain_, nullptr); swapchain_ = VK_NULL_HANDLE; }
|
if (swapchain_) { vkDestroySwapchainKHR(device_, swapchain_, nullptr); swapchain_ = VK_NULL_HANDLE; }
|
||||||
|
|||||||
+52
-8
@@ -110,7 +110,7 @@ void VulkanWindow::render(
|
|||||||
if (timed_frames_ > 1) timings_.steady_prepare_ms += prepare_ms;
|
if (timed_frames_ > 1) timings_.steady_prepare_ms += prepare_ms;
|
||||||
timings_.submit_ms += std::chrono::duration<double, std::milli>(submitted - prepared).count();
|
timings_.submit_ms += std::chrono::duration<double, std::milli>(submitted - prepared).count();
|
||||||
timings_.present_ms += std::chrono::duration<double, std::milli>(presented_at - submitted).count();
|
timings_.present_ms += std::chrono::duration<double, std::milli>(presented_at - submitted).count();
|
||||||
last_draw_count_ = frame.prepared_draws.size();
|
last_draw_count_ = frame.prepared_draws.size() + frame.scene_draws.size();
|
||||||
last_skinned_draw_count_ = frame.skinned_draw_count;
|
last_skinned_draw_count_ = frame.skinned_draw_count;
|
||||||
last_ui_batch_count_ = ui_batches.size();
|
last_ui_batch_count_ = ui_batches.size();
|
||||||
last_ui_quad_count_ = ui_quad_count;
|
last_ui_quad_count_ = ui_quad_count;
|
||||||
@@ -134,7 +134,28 @@ void VulkanWindow::prepare_draws(const std::vector<Render3DDraw>& draws, const T
|
|||||||
}
|
}
|
||||||
ensure_bone_capacity(required_bones);
|
ensure_bone_capacity(required_bones);
|
||||||
|
|
||||||
|
// MT_RENDER_SCALE: the game world into the reduced scene image (vk_scene.cpp).
|
||||||
|
bool scaled_scene = false;
|
||||||
|
if (scene_pass_ && render_scale_ < 1.0f)
|
||||||
|
scaled_scene = std::any_of(draws.begin(), draws.end(),
|
||||||
|
[](const Render3DDraw& d) { return d.scene && !d.render_target; });
|
||||||
|
if (scaled_scene) ensure_scene_target();
|
||||||
|
const float scene_sx = scaled_scene ? float(scene_extent_.width) / float(extent_.width) : 1.0f;
|
||||||
|
const float scene_sy = scaled_scene ? float(scene_extent_.height) / float(extent_.height) : 1.0f;
|
||||||
|
auto to_scene = [&](VkViewport viewport) {
|
||||||
|
viewport.x *= scene_sx; viewport.width *= scene_sx;
|
||||||
|
viewport.y *= scene_sy; viewport.height *= scene_sy;
|
||||||
|
return viewport;
|
||||||
|
};
|
||||||
|
// The draw list a back-buffer draw goes to; the first scene draw leaves the composite in its place.
|
||||||
|
auto back_buffer_list = [&](bool in_scene) -> std::vector<PreparedDraw>& {
|
||||||
|
if (!in_scene) return prepared_draws;
|
||||||
|
if (frame.scene_draws.empty()) prepared_draws.push_back(scene_composite_draw());
|
||||||
|
return frame.scene_draws;
|
||||||
|
};
|
||||||
|
|
||||||
for (const auto& draw : draws) {
|
for (const auto& draw : draws) {
|
||||||
|
const bool in_scene = scaled_scene && draw.scene && !draw.render_target;
|
||||||
RenderTarget* target = nullptr;
|
RenderTarget* target = nullptr;
|
||||||
if (draw.render_target) {
|
if (draw.render_target) {
|
||||||
target = get_render_target(
|
target = get_render_target(
|
||||||
@@ -159,14 +180,15 @@ void VulkanWindow::prepare_draws(const std::vector<Render3DDraw>& draws, const T
|
|||||||
clear.clear_color.color = {{color[0], color[1], color[2], color[3]}};
|
clear.clear_color.color = {{color[0], color[1], color[2], color[3]}};
|
||||||
clear.clear_depth.depthStencil = {draw.clear_z, 0};
|
clear.clear_depth.depthStencil = {draw.clear_z, 0};
|
||||||
// D3D8 Clear with no rectangles clears the current viewport.
|
// D3D8 Clear with no rectangles clears the current viewport.
|
||||||
const auto viewport = draw_viewport(draw);
|
const auto viewport = in_scene ? to_scene(draw_viewport(draw)) : draw_viewport(draw);
|
||||||
const float x0 = std::clamp(viewport.x, 0.0f, float(extent_.width));
|
const VkExtent2D bounds = in_scene ? scene_extent_ : extent_;
|
||||||
const float y0 = std::clamp(viewport.y, 0.0f, float(extent_.height));
|
const float x0 = std::clamp(viewport.x, 0.0f, float(bounds.width));
|
||||||
const float x1 = std::clamp(viewport.x + viewport.width, 0.0f, float(extent_.width));
|
const float y0 = std::clamp(viewport.y, 0.0f, float(bounds.height));
|
||||||
const float y1 = std::clamp(viewport.y + viewport.height, 0.0f, float(extent_.height));
|
const float x1 = std::clamp(viewport.x + viewport.width, 0.0f, float(bounds.width));
|
||||||
|
const float y1 = std::clamp(viewport.y + viewport.height, 0.0f, float(bounds.height));
|
||||||
clear.clear_rect = {{std::int32_t(x0), std::int32_t(y0)},
|
clear.clear_rect = {{std::int32_t(x0), std::int32_t(y0)},
|
||||||
{std::uint32_t(x1 - x0), std::uint32_t(y1 - y0)}};
|
{std::uint32_t(x1 - x0), std::uint32_t(y1 - y0)}};
|
||||||
prepared_draws.push_back(clear);
|
back_buffer_list(in_scene).push_back(clear);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (draw.positions.empty() || draw.indices.empty()) continue;
|
if (draw.positions.empty() || draw.indices.empty()) continue;
|
||||||
@@ -247,6 +269,9 @@ void VulkanWindow::prepare_draws(const std::vector<Render3DDraw>& draws, const T
|
|||||||
draw.viewport_z[0], draw.viewport_z[1]}
|
draw.viewport_z[0], draw.viewport_z[1]}
|
||||||
: VkViewport{0, 0, float(target->width), float(target->height), 0, 1};
|
: VkViewport{0, 0, float(target->width), float(target->height), 0, 1};
|
||||||
offscreen_draws.push_back(prepared_draw);
|
offscreen_draws.push_back(prepared_draw);
|
||||||
|
} else if (in_scene) {
|
||||||
|
prepared_draw.viewport = to_scene(prepared_draw.viewport);
|
||||||
|
back_buffer_list(true).push_back(prepared_draw);
|
||||||
} else prepared_draws.push_back(prepared_draw);
|
} else prepared_draws.push_back(prepared_draw);
|
||||||
frame.vertex_count += geometry.vertex_count;
|
frame.vertex_count += geometry.vertex_count;
|
||||||
frame.index_count += geometry.index_count;
|
frame.index_count += geometry.index_count;
|
||||||
@@ -289,8 +314,15 @@ void VulkanWindow::prepare_ui(std::uint32_t ui_width, std::uint32_t ui_height, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanWindow::write_draw_states(FrameDraws& frame, std::vector<UiBatch>& ui_batches) {
|
void VulkanWindow::write_draw_states(FrameDraws& frame, std::vector<UiBatch>& ui_batches) {
|
||||||
ensure_state_capacity(frame.prepared_draws.size() + frame.offscreen_draws.size() + ui_batches.size());
|
ensure_state_capacity(frame.prepared_draws.size() + frame.offscreen_draws.size() + frame.scene_draws.size() +
|
||||||
|
ui_batches.size());
|
||||||
std::size_t state_index = 0;
|
std::size_t state_index = 0;
|
||||||
|
for (auto& draw : frame.scene_draws) {
|
||||||
|
if (!draw.geometry) continue;
|
||||||
|
draw.state_offset = static_cast<std::uint32_t>(state_index * state_stride_);
|
||||||
|
std::memcpy(f_->state_mapped + draw.state_offset, &draw.fixed_state, sizeof(FixedFunctionState));
|
||||||
|
++state_index;
|
||||||
|
}
|
||||||
for (auto& draw : frame.offscreen_draws) {
|
for (auto& draw : frame.offscreen_draws) {
|
||||||
if (!draw.geometry) continue;
|
if (!draw.geometry) continue;
|
||||||
draw.state_offset = static_cast<std::uint32_t>(state_index * state_stride_);
|
draw.state_offset = static_cast<std::uint32_t>(state_index * state_stride_);
|
||||||
@@ -391,6 +423,18 @@ void VulkanWindow::record_passes(std::uint32_t image_index, const FrameDraws& fr
|
|||||||
vkCmdEndRenderPass(f_->command);
|
vkCmdEndRenderPass(f_->command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The game world at render_scale(), sampled by the composite quad in the back-buffer pass.
|
||||||
|
if (!frame.scene_draws.empty()) {
|
||||||
|
VkRenderPassBeginInfo scene_begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||||
|
scene_begin.renderPass = scene_pass_;
|
||||||
|
scene_begin.framebuffer = scene_framebuffer_;
|
||||||
|
scene_begin.renderArea.extent = scene_extent_;
|
||||||
|
scene_begin.clearValueCount = 2; scene_begin.pClearValues = clears;
|
||||||
|
vkCmdBeginRenderPass(f_->command, &scene_begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
for (const auto& prepared_draw : frame.scene_draws) record_prepared(prepared_draw);
|
||||||
|
vkCmdEndRenderPass(f_->command);
|
||||||
|
}
|
||||||
|
|
||||||
VkRenderPassBeginInfo pass_begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
VkRenderPassBeginInfo pass_begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||||
pass_begin.renderPass = pass_; pass_begin.framebuffer = framebuffers_.at(image_index);
|
pass_begin.renderPass = pass_; pass_begin.framebuffer = framebuffers_.at(image_index);
|
||||||
pass_begin.renderArea.extent = extent_; pass_begin.clearValueCount = samples_ != VK_SAMPLE_COUNT_1_BIT ? 3 : 2; pass_begin.pClearValues = clears;
|
pass_begin.renderArea.extent = extent_; pass_begin.clearValueCount = samples_ != VK_SAMPLE_COUNT_1_BIT ? 3 : 2; pass_begin.pClearValues = clears;
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ void VulkanWindow::create_render_pass_and_layout() {
|
|||||||
|
|
||||||
get_pipeline(0, 2, 0);
|
get_pipeline(0, 2, 0);
|
||||||
create_offscreen_pass();
|
create_offscreen_pass();
|
||||||
|
create_scene_pass();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanWindow::create_offscreen_pass() {
|
void VulkanWindow::create_offscreen_pass() {
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
// VulkanWindow: the reduced-resolution scene pass (MT_RENDER_SCALE). The game world's draws
|
||||||
|
// (Render3DDraw::scene, recorded inside CPythonApplication::RenderGame) go into scene_texture_ at
|
||||||
|
// render_scale_ of the swapchain extent; a quad composites it where RenderGame drew, under the UI.
|
||||||
|
#include "vulkan_window.h"
|
||||||
|
|
||||||
|
#include "host_util.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace mt_host {
|
||||||
|
|
||||||
|
void VulkanWindow::create_scene_pass() {
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
render_scale_ = 0.75f;
|
||||||
|
#else
|
||||||
|
render_scale_ = 1.0f;
|
||||||
|
#endif
|
||||||
|
if (const char* env = std::getenv("MT_RENDER_SCALE")) render_scale_ = float(std::atof(env));
|
||||||
|
render_scale_ = std::clamp(render_scale_, 0.25f, 1.0f);
|
||||||
|
|
||||||
|
VkFormatProperties properties{};
|
||||||
|
vkGetPhysicalDeviceFormatProperties(physical_, swapchain_format_, &properties);
|
||||||
|
const VkFormatFeatureFlags wanted = VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
|
||||||
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
||||||
|
scene_supported_ = (properties.optimalTilingFeatures & wanted) == wanted;
|
||||||
|
if (!scene_supported_ || render_scale_ >= 1.0f) return;
|
||||||
|
|
||||||
|
// Same formats, sample counts and resolve layout as pass_, so its pipelines are compatible.
|
||||||
|
const bool msaa = samples_ != VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
VkAttachmentDescription attachments[3]{};
|
||||||
|
attachments[0].format = swapchain_format_; attachments[0].samples = samples_;
|
||||||
|
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
|
attachments[0].storeOp = msaa ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
|
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
attachments[0].finalLayout = msaa ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
attachments[1].format = VK_FORMAT_D32_SFLOAT; attachments[1].samples = samples_;
|
||||||
|
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||||
|
attachments[2].format = swapchain_format_; attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
|
attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
attachments[2].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
|
||||||
|
VkAttachmentReference color_ref{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||||
|
VkAttachmentReference depth_ref{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||||
|
VkAttachmentReference resolve_ref{2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||||
|
VkSubpassDescription subpass{};
|
||||||
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||||
|
subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color_ref;
|
||||||
|
if (msaa) subpass.pResolveAttachments = &resolve_ref;
|
||||||
|
subpass.pDepthStencilAttachment = &depth_ref;
|
||||||
|
|
||||||
|
VkSubpassDependency dependencies[2]{};
|
||||||
|
// The previous frame's composite samples the image this pass overwrites.
|
||||||
|
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[0].dstSubpass = 0;
|
||||||
|
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||||
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||||
|
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||||
|
dependencies[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
|
||||||
|
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||||
|
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||||
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
|
// The back-buffer pass samples the result.
|
||||||
|
dependencies[1].srcSubpass = 0; dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||||
|
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
|
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||||
|
dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||||
|
|
||||||
|
VkRenderPassCreateInfo pass_info{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||||
|
pass_info.attachmentCount = msaa ? 3 : 2; pass_info.pAttachments = attachments;
|
||||||
|
pass_info.subpassCount = 1; pass_info.pSubpasses = &subpass;
|
||||||
|
pass_info.dependencyCount = 2; pass_info.pDependencies = dependencies;
|
||||||
|
check(vkCreateRenderPass(device_, &pass_info, nullptr, &scene_pass_), "vkCreateRenderPass scene");
|
||||||
|
|
||||||
|
// Two triangles over the unit square; scene_composite_draw maps it to the whole back buffer.
|
||||||
|
Render3DDraw quad{};
|
||||||
|
quad.pretransformed = true;
|
||||||
|
quad.positions = {0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0};
|
||||||
|
quad.rhw = {1, 1, 1, 1};
|
||||||
|
quad.uv0 = {0, 0, 1, 0, 0, 1, 1, 1};
|
||||||
|
quad.diffuse = {0xffffffffu, 0xffffffffu, 0xffffffffu, 0xffffffffu};
|
||||||
|
quad.indices = {0, 1, 2, 2, 1, 3};
|
||||||
|
upload_geometry(quad, scene_quad_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWindow::destroy_scene_target() {
|
||||||
|
if (scene_framebuffer_) vkDestroyFramebuffer(device_, scene_framebuffer_, nullptr);
|
||||||
|
if (scene_msaa_view_) vkDestroyImageView(device_, scene_msaa_view_, nullptr);
|
||||||
|
if (scene_msaa_image_) vkDestroyImage(device_, scene_msaa_image_, nullptr);
|
||||||
|
if (scene_msaa_memory_) vkFreeMemory(device_, scene_msaa_memory_, nullptr);
|
||||||
|
if (scene_depth_view_) vkDestroyImageView(device_, scene_depth_view_, nullptr);
|
||||||
|
if (scene_depth_image_) vkDestroyImage(device_, scene_depth_image_, nullptr);
|
||||||
|
if (scene_depth_memory_) vkFreeMemory(device_, scene_depth_memory_, nullptr);
|
||||||
|
release_texture(scene_texture_);
|
||||||
|
scene_framebuffer_ = VK_NULL_HANDLE;
|
||||||
|
scene_msaa_view_ = VK_NULL_HANDLE; scene_msaa_image_ = VK_NULL_HANDLE; scene_msaa_memory_ = VK_NULL_HANDLE;
|
||||||
|
scene_depth_view_ = VK_NULL_HANDLE; scene_depth_image_ = VK_NULL_HANDLE; scene_depth_memory_ = VK_NULL_HANDLE;
|
||||||
|
scene_extent_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWindow::ensure_scene_target() {
|
||||||
|
const VkExtent2D wanted{std::max(1u, std::uint32_t(std::lround(extent_.width * render_scale_))),
|
||||||
|
std::max(1u, std::uint32_t(std::lround(extent_.height * render_scale_)))};
|
||||||
|
if (scene_framebuffer_ && wanted.width == scene_extent_.width && wanted.height == scene_extent_.height) return;
|
||||||
|
if (scene_framebuffer_) {
|
||||||
|
vkDeviceWaitIdle(device_);
|
||||||
|
destroy_scene_target();
|
||||||
|
}
|
||||||
|
scene_extent_ = wanted;
|
||||||
|
auto make_image = [&](VkFormat format, VkSampleCountFlagBits samples, VkImageUsageFlags usage,
|
||||||
|
VkImageAspectFlags aspect, bool transient,
|
||||||
|
VkImage& image, VkDeviceMemory& memory, VkImageView& view) {
|
||||||
|
VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||||
|
info.imageType = VK_IMAGE_TYPE_2D;
|
||||||
|
info.format = format;
|
||||||
|
info.extent = {wanted.width, wanted.height, 1};
|
||||||
|
info.mipLevels = 1; info.arrayLayers = 1;
|
||||||
|
info.samples = samples;
|
||||||
|
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||||
|
info.usage = usage;
|
||||||
|
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
|
check(vkCreateImage(device_, &info, nullptr, &image), "vkCreateImage scene");
|
||||||
|
VkMemoryRequirements reqs{};
|
||||||
|
vkGetImageMemoryRequirements(device_, image, &reqs);
|
||||||
|
VkMemoryAllocateInfo alloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||||
|
alloc.allocationSize = reqs.size;
|
||||||
|
alloc.memoryTypeIndex = find_memory_type(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||||
|
if (transient) {
|
||||||
|
try {
|
||||||
|
alloc.memoryTypeIndex = find_memory_type(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT);
|
||||||
|
} catch (const std::runtime_error&) {}
|
||||||
|
}
|
||||||
|
check(vkAllocateMemory(device_, &alloc, nullptr, &memory), "vkAllocateMemory scene");
|
||||||
|
check(vkBindImageMemory(device_, image, memory, 0), "vkBindImageMemory scene");
|
||||||
|
VkImageViewCreateInfo view_info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||||
|
view_info.image = image; view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; view_info.format = format;
|
||||||
|
view_info.subresourceRange = {aspect, 0, 1, 0, 1};
|
||||||
|
check(vkCreateImageView(device_, &view_info, nullptr, &view), "vkCreateImageView scene");
|
||||||
|
};
|
||||||
|
const bool msaa = samples_ != VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
make_image(swapchain_format_, VK_SAMPLE_COUNT_1_BIT,
|
||||||
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_ASPECT_COLOR_BIT, false,
|
||||||
|
scene_texture_.image, scene_texture_.memory, scene_texture_.view);
|
||||||
|
if (msaa)
|
||||||
|
make_image(swapchain_format_, samples_,
|
||||||
|
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
|
||||||
|
VK_IMAGE_ASPECT_COLOR_BIT, true, scene_msaa_image_, scene_msaa_memory_, scene_msaa_view_);
|
||||||
|
make_image(VK_FORMAT_D32_SFLOAT, samples_,
|
||||||
|
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
|
||||||
|
VK_IMAGE_ASPECT_DEPTH_BIT, true, scene_depth_image_, scene_depth_memory_, scene_depth_view_);
|
||||||
|
const VkImageView views[3] = {msaa ? scene_msaa_view_ : scene_texture_.view, scene_depth_view_, scene_texture_.view};
|
||||||
|
VkFramebufferCreateInfo fb{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||||
|
fb.renderPass = scene_pass_;
|
||||||
|
fb.attachmentCount = msaa ? 3 : 2; fb.pAttachments = views;
|
||||||
|
fb.width = wanted.width; fb.height = wanted.height; fb.layers = 1;
|
||||||
|
check(vkCreateFramebuffer(device_, &fb, nullptr, &scene_framebuffer_), "vkCreateFramebuffer scene");
|
||||||
|
// Bilinear, clamped: the upscale to the back buffer.
|
||||||
|
Render3DDraw sampling{};
|
||||||
|
sampling.min_filter[0] = sampling.mag_filter[0] = 2; // D3DTEXF_LINEAR
|
||||||
|
sampling.address_u[0] = sampling.address_v[0] = 3; // D3DTADDRESS_CLAMP
|
||||||
|
scene_texture_.descriptor = allocate_texture_descriptor_set(
|
||||||
|
scene_texture_.view, fallback_texture_.view, sampler_for_draw(sampling, 0), VK_NULL_HANDLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanWindow::PreparedDraw VulkanWindow::scene_composite_draw() {
|
||||||
|
// Stage 0 SELECTARG1(TEXTURE) for colour and alpha, stage 1 disabled, no lighting or fog.
|
||||||
|
Render3DDraw quad{};
|
||||||
|
quad.pretransformed = true;
|
||||||
|
quad.texture0 = "scene";
|
||||||
|
quad.diffuse = {0xffffffffu};
|
||||||
|
quad.color_op[0] = quad.alpha_op[0] = 2;
|
||||||
|
quad.color_arg1[0] = quad.alpha_arg1[0] = 2;
|
||||||
|
quad.color_op[1] = quad.alpha_op[1] = 1;
|
||||||
|
for (float* m : {quad.world, quad.view, quad.proj})
|
||||||
|
for (int i = 0; i < 16; ++i) m[i] = i % 5 == 0 ? 1.0f : 0.0f;
|
||||||
|
PreparedDraw composite{};
|
||||||
|
composite.geometry = &scene_quad_;
|
||||||
|
composite.pipeline = get_pipeline(0, 0, 0);
|
||||||
|
composite.descriptor = scene_texture_.descriptor;
|
||||||
|
composite.constants = make_push_constants(quad, 0.0f);
|
||||||
|
// Unit square (y down) to D3D NDC (y up), as the pretransformed draws map screen pixels.
|
||||||
|
composite.constants.mvp = {2, 0, 0, 0,
|
||||||
|
0, -2, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
-1, 1, 0, 1};
|
||||||
|
composite.fixed_state = make_fixed_function_state(quad);
|
||||||
|
composite.viewport = full_viewport();
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mt_host
|
||||||
@@ -120,6 +120,9 @@ VulkanWindow::~VulkanWindow() {
|
|||||||
for (auto& entry : textures_) release_texture(entry.second);
|
for (auto& entry : textures_) release_texture(entry.second);
|
||||||
for (auto& entry : render_targets_) release_render_target(entry.second);
|
for (auto& entry : render_targets_) release_render_target(entry.second);
|
||||||
if (offscreen_pass_) vkDestroyRenderPass(device_, offscreen_pass_, nullptr);
|
if (offscreen_pass_) vkDestroyRenderPass(device_, offscreen_pass_, nullptr);
|
||||||
|
destroy_scene_target();
|
||||||
|
release_geometry(scene_quad_);
|
||||||
|
if (scene_pass_) vkDestroyRenderPass(device_, scene_pass_, nullptr);
|
||||||
release_texture(fallback_texture_);
|
release_texture(fallback_texture_);
|
||||||
for (auto& entry : pipelines_) vkDestroyPipeline(device_, entry.second, nullptr);
|
for (auto& entry : pipelines_) vkDestroyPipeline(device_, entry.second, nullptr);
|
||||||
if (vertex_module_) vkDestroyShaderModule(device_, vertex_module_, nullptr);
|
if (vertex_module_) vkDestroyShaderModule(device_, vertex_module_, nullptr);
|
||||||
@@ -229,6 +232,7 @@ void print_summary(const VulkanWindow& renderer, int completed, double wall_ms,
|
|||||||
};
|
};
|
||||||
std::cout << "device=" << renderer.device_name()
|
std::cout << "device=" << renderer.device_name()
|
||||||
<< " present_mode=" << renderer.present_mode_name() << " msaa=" << renderer.msaa_samples()
|
<< " present_mode=" << renderer.present_mode_name() << " msaa=" << renderer.msaa_samples()
|
||||||
|
<< " render_scale=" << renderer.render_scale()
|
||||||
<< " frames=" << completed
|
<< " frames=" << completed
|
||||||
<< " draws=" << renderer.draw_count()
|
<< " draws=" << renderer.draw_count()
|
||||||
<< " skinned_draws=" << renderer.skinned_draw_count()
|
<< " skinned_draws=" << renderer.skinned_draw_count()
|
||||||
|
|||||||
@@ -136,6 +136,9 @@ public:
|
|||||||
std::uint64_t frame_number() const { return frame_number_; }
|
std::uint64_t frame_number() const { return frame_number_; }
|
||||||
|
|
||||||
int msaa_samples() const { return int(samples_); }
|
int msaa_samples() const { return int(samples_); }
|
||||||
|
// The game world's resolution relative to the swapchain (MT_RENDER_SCALE; 1 = drawn in the
|
||||||
|
// back-buffer pass as before).
|
||||||
|
float render_scale() const { return scene_pass_ ? render_scale_ : 1.0f; }
|
||||||
std::uint32_t width() const { return extent_.width; }
|
std::uint32_t width() const { return extent_.width; }
|
||||||
std::uint32_t height() const { return extent_.height; }
|
std::uint32_t height() const { return extent_.height; }
|
||||||
std::uint32_t logical_width() const;
|
std::uint32_t logical_width() const;
|
||||||
@@ -201,6 +204,9 @@ private:
|
|||||||
// render()'s phases, in call order (vk_frame.cpp).
|
// render()'s phases, in call order (vk_frame.cpp).
|
||||||
struct FrameDraws {
|
struct FrameDraws {
|
||||||
std::vector<PreparedDraw> prepared_draws; // the back-buffer pass
|
std::vector<PreparedDraw> prepared_draws; // the back-buffer pass
|
||||||
|
// Render3DDraw::scene draws at render_scale(): drawn by the scene pass into scene_texture_,
|
||||||
|
// which a quad in prepared_draws (where the first of them was recorded) composites.
|
||||||
|
std::vector<PreparedDraw> scene_draws;
|
||||||
// Draws into offscreen render targets (the shadow map), in recorded order, drawn before the
|
// Draws into offscreen render targets (the shadow map), in recorded order, drawn before the
|
||||||
// back-buffer pass that samples them.
|
// back-buffer pass that samples them.
|
||||||
std::vector<PreparedDraw> offscreen_draws;
|
std::vector<PreparedDraw> offscreen_draws;
|
||||||
@@ -282,6 +288,19 @@ private:
|
|||||||
// the device can render to it) + D32 depth, both loaded and stored, colour left shader-readable.
|
// the device can render to it) + D32 depth, both loaded and stored, colour left shader-readable.
|
||||||
void create_offscreen_pass();
|
void create_offscreen_pass();
|
||||||
|
|
||||||
|
// The reduced-resolution scene pass: pass_'s attachments (so pass_ pipelines draw in it) at
|
||||||
|
// scene_extent_, resolving into the sampled scene_texture_. Unsupported when the swapchain
|
||||||
|
// format cannot be sampled; render_scale() is then 1.
|
||||||
|
void create_scene_pass();
|
||||||
|
|
||||||
|
// scene_texture_ and its attachments at render_scale_ of the current extent.
|
||||||
|
void ensure_scene_target();
|
||||||
|
|
||||||
|
void destroy_scene_target();
|
||||||
|
|
||||||
|
// The composite quad over the whole back buffer sampling scene_texture_.
|
||||||
|
PreparedDraw scene_composite_draw();
|
||||||
|
|
||||||
// "rt:<id>:<w>x<h>" -> its render target, created on first use (by a draw into it or by a draw
|
// "rt:<id>:<w>x<h>" -> its render target, created on first use (by a draw into it or by a draw
|
||||||
// sampling it); null for a malformed name.
|
// sampling it); null for a malformed name.
|
||||||
RenderTarget* get_render_target(const std::string& name);
|
RenderTarget* get_render_target(const std::string& name);
|
||||||
@@ -441,6 +460,19 @@ private:
|
|||||||
std::unordered_map<std::string, PairedDescriptor> paired_descriptors_;
|
std::unordered_map<std::string, PairedDescriptor> paired_descriptors_;
|
||||||
std::unordered_map<std::string, RenderTarget> render_targets_;
|
std::unordered_map<std::string, RenderTarget> render_targets_;
|
||||||
VkRenderPass offscreen_pass_ = VK_NULL_HANDLE;
|
VkRenderPass offscreen_pass_ = VK_NULL_HANDLE;
|
||||||
|
float render_scale_ = 1.0f;
|
||||||
|
bool scene_supported_ = false;
|
||||||
|
VkRenderPass scene_pass_ = VK_NULL_HANDLE;
|
||||||
|
VkExtent2D scene_extent_{};
|
||||||
|
VkImage scene_msaa_image_ = VK_NULL_HANDLE;
|
||||||
|
VkDeviceMemory scene_msaa_memory_ = VK_NULL_HANDLE;
|
||||||
|
VkImageView scene_msaa_view_ = VK_NULL_HANDLE;
|
||||||
|
VkImage scene_depth_image_ = VK_NULL_HANDLE;
|
||||||
|
VkDeviceMemory scene_depth_memory_ = VK_NULL_HANDLE;
|
||||||
|
VkImageView scene_depth_view_ = VK_NULL_HANDLE;
|
||||||
|
VkFramebuffer scene_framebuffer_ = VK_NULL_HANDLE;
|
||||||
|
GpuTexture scene_texture_{};
|
||||||
|
Geometry scene_quad_{};
|
||||||
VkFormat offscreen_format_ = VK_FORMAT_R5G6B5_UNORM_PACK16;
|
VkFormat offscreen_format_ = VK_FORMAT_R5G6B5_UNORM_PACK16;
|
||||||
std::size_t last_offscreen_draw_count_ = 0;
|
std::size_t last_offscreen_draw_count_ = 0;
|
||||||
VkShaderModule vertex_module_ = VK_NULL_HANDLE;
|
VkShaderModule vertex_module_ = VK_NULL_HANDLE;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
namespace {
|
namespace {
|
||||||
std::mutex g_draws_mutex;
|
std::mutex g_draws_mutex;
|
||||||
std::vector<Render3DDraw> g_draws;
|
std::vector<Render3DDraw> g_draws;
|
||||||
|
bool g_in_scene = false;
|
||||||
int g_native_terrain_override = -1;
|
int g_native_terrain_override = -1;
|
||||||
bool g_gpu_render_targets = false;
|
bool g_gpu_render_targets = false;
|
||||||
|
|
||||||
@@ -1256,9 +1257,13 @@ void Render3DBeginFrame()
|
|||||||
void Render3DAdd(Render3DDraw draw)
|
void Render3DAdd(Render3DDraw draw)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_draws_mutex);
|
std::lock_guard<std::mutex> lock(g_draws_mutex);
|
||||||
|
draw.scene = g_in_scene;
|
||||||
g_draws.push_back(std::move(draw));
|
g_draws.push_back(std::move(draw));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Render3DBeginScene() { g_in_scene = true; }
|
||||||
|
void Render3DEndScene() { g_in_scene = false; }
|
||||||
|
|
||||||
const std::vector<Render3DDraw>& Render3DDraws() { return g_draws; }
|
const std::vector<Render3DDraw>& Render3DDraws() { return g_draws; }
|
||||||
|
|
||||||
// D3D8 fixed-function texture coordinate processing for one stage (the native renderer's
|
// D3D8 fixed-function texture coordinate processing for one stage (the native renderer's
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ struct Render3DDraw {
|
|||||||
// PORT: UIRenderOffsetX() when recorded (a draw from a shifted window layer); the host moves the
|
// PORT: UIRenderOffsetX() when recorded (a draw from a shifted window layer); the host moves the
|
||||||
// viewport (and a pretransformed draw's screen positions) right by it.
|
// viewport (and a pretransformed draw's screen positions) right by it.
|
||||||
float ui_offset_x = 0;
|
float ui_offset_x = 0;
|
||||||
|
// PORT: recorded inside CPythonApplication::RenderGame (Render3DBeginScene/EndScene). The native
|
||||||
|
// renderer may draw these into a reduced-resolution scene image composited under the UI.
|
||||||
|
bool scene = false;
|
||||||
|
|
||||||
// IDirect3DDevice8::Clear on the back buffer, kept in draw order. A clear entry has no geometry;
|
// IDirect3DDevice8::Clear on the back buffer, kept in draw order. A clear entry has no geometry;
|
||||||
// clear_flags holds D3DCLEAR_TARGET / D3DCLEAR_ZBUFFER / D3DCLEAR_STENCIL.
|
// clear_flags holds D3DCLEAR_TARGET / D3DCLEAR_ZBUFFER / D3DCLEAR_STENCIL.
|
||||||
@@ -138,4 +141,8 @@ std::string Render3DRenderTargetName(std::uint32_t id, std::uint32_t width, std:
|
|||||||
|
|
||||||
void Render3DBeginFrame();
|
void Render3DBeginFrame();
|
||||||
void Render3DAdd(Render3DDraw draw);
|
void Render3DAdd(Render3DDraw draw);
|
||||||
|
// PORT: brackets the game world's draws (CPythonApplication::RenderGame); Render3DAdd marks the draws
|
||||||
|
// in between with Render3DDraw::scene.
|
||||||
|
void Render3DBeginScene();
|
||||||
|
void Render3DEndScene();
|
||||||
const std::vector<Render3DDraw>& Render3DDraws();
|
const std::vector<Render3DDraw>& Render3DDraws();
|
||||||
|
|||||||
@@ -202,6 +202,8 @@ void CPythonApplication::GetMousePosition(POINT* ppt)
|
|||||||
void CPythonApplication::RenderGame()
|
void CPythonApplication::RenderGame()
|
||||||
{
|
{
|
||||||
MtPerf::CScope kPerf(MtPerf::SECTION_RENDER_GAME);
|
MtPerf::CScope kPerf(MtPerf::SECTION_RENDER_GAME);
|
||||||
|
// PORT: marks the world's draws so the native renderer can draw them at a reduced resolution.
|
||||||
|
struct SceneScope { SceneScope() { Render3DBeginScene(); } ~SceneScope() { Render3DEndScene(); } } kScene;
|
||||||
float fAspect=UI::CWindowManager::Instance().GetAspect();
|
float fAspect=UI::CWindowManager::Instance().GetAspect();
|
||||||
float fFarClip=CPythonBackground::Instance().GetFarClip();
|
float fFarClip=CPythonBackground::Instance().GetFarClip();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user