mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-29 12:47:30 +01:00
406 lines
16 KiB
C++
406 lines
16 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
// the probe of the occupied cab in the vulkan renderer (the stage A4 of the plan; opengl46_cab_probe and
|
|
// opengl46_renderer::Render_cab_probe()): the maps of vk14::cab_probe_maps, the cab drawn to them by the colour mode of the shared walk
|
|
// of the scene with the programs of the probe (the six faces at once by multiview), their mipmaps and the spherical harmonics of
|
|
// the light the cab reflects (cabprobe_sh.comp)
|
|
|
|
#include "stdafx.h"
|
|
#include "rendering/vulkan14/vulkan14context.h"
|
|
#include "rendering/vulkan14/vulkan14commands.h"
|
|
#include "simulation/simulation.h"
|
|
#include "utilities/Globals.h"
|
|
#include "vehicle/DynObj.h"
|
|
#include "vehicle/Train.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
VkPipelineStageFlags2 const colour_stage{VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT};
|
|
VkAccessFlags2 const colour_access{VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT};
|
|
VkPipelineStageFlags2 const depth_stage{VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT};
|
|
VkAccessFlags2 const depth_access{VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT};
|
|
VkPipelineStageFlags2 const sampled_stage{VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT};
|
|
VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
|
|
|
|
// the formats of the maps: the light reflected (hdr) and the albedo
|
|
VkFormat const LIGHT_FORMAT{VK_FORMAT_R16G16B16A16_SFLOAT};
|
|
VkFormat const ALBEDO_FORMAT{VK_FORMAT_R8G8B8A8_UNORM};
|
|
|
|
// the mipmaps of a cube map whose first level was drawn: each level from the one before it, all of them left in the layout the
|
|
// shading reads them in (the environment map makes its own the same way, render_reflections())
|
|
void make_cube_mipmaps(vk14::command_stream *Commands, vk14::image &Image)
|
|
{
|
|
auto const mips{Image.desc().mips};
|
|
auto const size{static_cast<std::int32_t>(Image.desc().extent.width)};
|
|
auto barrier = [&](std::uint32_t Level, std::uint32_t Count, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) {
|
|
VkImageMemoryBarrier2 entry{};
|
|
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
|
entry.srcStageMask = SrcStage;
|
|
entry.srcAccessMask = SrcAccess;
|
|
entry.dstStageMask = DstStage;
|
|
entry.dstAccessMask = DstAccess;
|
|
entry.oldLayout = From;
|
|
entry.newLayout = To;
|
|
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
entry.image = Image.handle();
|
|
entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, Count, 0, 6};
|
|
VkDependencyInfo dependency{};
|
|
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
|
dependency.imageMemoryBarrierCount = 1;
|
|
dependency.pImageMemoryBarriers = &entry;
|
|
Commands->pipeline_barrier2(&dependency);
|
|
};
|
|
// the first level was drawn, the others are undefined
|
|
barrier(0, 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, colour_stage, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
|
if (mips > 1)
|
|
{
|
|
barrier(1, mips - 1, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
|
}
|
|
for (std::uint32_t level = 1; level < mips; ++level)
|
|
{
|
|
VkImageBlit2 blit{};
|
|
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
|
|
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level - 1, 0, 6};
|
|
blit.srcOffsets[1] = {std::max(size >> (level - 1), 1), std::max(size >> (level - 1), 1), 1};
|
|
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 6};
|
|
blit.dstOffsets[1] = {std::max(size >> level, 1), std::max(size >> level, 1), 1};
|
|
VkBlitImageInfo2 info{};
|
|
info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
|
|
info.srcImage = Image.handle();
|
|
info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
info.dstImage = Image.handle();
|
|
info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
|
info.regionCount = 1;
|
|
info.pRegions = &blit;
|
|
info.filter = VK_FILTER_LINEAR;
|
|
Commands->blit_image2(&info);
|
|
barrier(level, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
|
}
|
|
barrier(0, mips, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT, sampled_stage, sampled_access);
|
|
Image.state = vk14::image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access};
|
|
}
|
|
|
|
VkRenderingAttachmentInfo cleared_attachment(VkImageView const View, VkImageLayout const Layout, VkClearValue const &Value)
|
|
{
|
|
VkRenderingAttachmentInfo info{};
|
|
info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
|
info.imageView = View;
|
|
info.imageLayout = Layout;
|
|
info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
info.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
info.clearValue = Value;
|
|
return info;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace vk14
|
|
{
|
|
|
|
cab_probe_maps::~cab_probe_maps()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void cab_probe_maps::init(VkDevice const Device, allocator &Memory, deletion_queue &Deletions)
|
|
{
|
|
m_device = Device;
|
|
m_memory = &Memory;
|
|
m_deletions = &Deletions;
|
|
}
|
|
|
|
void cab_probe_maps::destroy()
|
|
{
|
|
// the device is idle: the maps go at once
|
|
m_deletions = nullptr;
|
|
let_go();
|
|
}
|
|
|
|
bool cab_probe_maps::gpu_allocate(int const Size)
|
|
{
|
|
if ((m_memory == nullptr) || (Size <= 0))
|
|
{
|
|
return false;
|
|
}
|
|
auto const size{static_cast<std::uint32_t>(Size)};
|
|
// the maps: cube maps with their mipmaps, read by the shading, drawn to through a view of the six layers of the first level
|
|
auto make_map = [&](std::shared_ptr<image> &Map, VkImageView &View, VkFormat const Format, char const *Name) {
|
|
image_desc desc;
|
|
desc.format = Format;
|
|
desc.extent = {size, size, 1};
|
|
desc.layers = 6;
|
|
desc.mips = static_cast<std::uint32_t>(std::floor(std::log2(static_cast<float>(size)))) + 1;
|
|
desc.view_type = VK_IMAGE_VIEW_TYPE_CUBE;
|
|
desc.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
|
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
Map = std::make_shared<image>();
|
|
if (false == Map->create(*m_memory, desc, Name))
|
|
{
|
|
Map.reset();
|
|
return false;
|
|
}
|
|
VkImageViewCreateInfo info{};
|
|
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
info.image = Map->handle();
|
|
info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
info.format = Format;
|
|
info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 6};
|
|
if (vkCreateImageView(m_device, &info, nullptr, &View) != VK_SUCCESS)
|
|
{
|
|
View = VK_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
if ((false == make_map(m_light, m_lightview, LIGHT_FORMAT, "cab probe light")) || (false == make_map(m_albedo, m_albedoview, ALBEDO_FORMAT, "cab probe albedo")))
|
|
{
|
|
let_go();
|
|
return false;
|
|
}
|
|
image_desc depth;
|
|
depth.format = VK_FORMAT_D32_SFLOAT;
|
|
depth.extent = {size, size, 1};
|
|
depth.layers = 6;
|
|
depth.view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
depth.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
|
m_depth = std::make_shared<image>();
|
|
if (false == m_depth->create(*m_memory, depth, "cab probe depth"))
|
|
{
|
|
let_go();
|
|
return false;
|
|
}
|
|
m_target = draw_target{m_light.get(), m_albedo.get(), m_depth.get(), m_lightview, m_albedoview, size};
|
|
m_drawn = false;
|
|
return true;
|
|
}
|
|
|
|
void cab_probe_maps::gpu_release()
|
|
{
|
|
let_go();
|
|
}
|
|
|
|
void cab_probe_maps::let_go()
|
|
{
|
|
m_target = draw_target{};
|
|
m_drawn = false;
|
|
std::vector<VkImageView> views;
|
|
for (auto *view : {&m_lightview, &m_albedoview})
|
|
{
|
|
if (*view != VK_NULL_HANDLE)
|
|
{
|
|
views.push_back(*view);
|
|
}
|
|
*view = VK_NULL_HANDLE;
|
|
}
|
|
if (m_deletions != nullptr)
|
|
{
|
|
// the frames in flight may still read them
|
|
m_deletions->push(m_value, [device = m_device, views = std::move(views), light = std::move(m_light), albedo = std::move(m_albedo), depth = std::move(m_depth)]() {
|
|
for (auto *view : views)
|
|
{
|
|
vkDestroyImageView(device, view, nullptr);
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
for (auto *view : views)
|
|
{
|
|
vkDestroyImageView(m_device, view, nullptr);
|
|
}
|
|
}
|
|
m_light.reset();
|
|
m_albedo.reset();
|
|
m_depth.reset();
|
|
}
|
|
|
|
} // namespace vk14
|
|
|
|
bool vulkan14_renderer::init_cab_probe(std::string &Reason)
|
|
{
|
|
auto &ctx{*m_context};
|
|
auto &scene{ctx.scene};
|
|
auto const device{ctx.device.handle()};
|
|
try
|
|
{
|
|
auto program = [&](char const *Fragment) {
|
|
auto result{std::make_shared<vk14::scene_program>()};
|
|
result->vertex = ctx.shaders.get("cabprobe.vert");
|
|
result->fragment = ctx.shaders.get(Fragment);
|
|
result->fragment_name = Fragment;
|
|
result->vertex_name = "cabprobe.vert";
|
|
result->vertices = vk14::vertex_kind::scene;
|
|
result->name = std::string("cabprobe.vert + ") + Fragment;
|
|
return result;
|
|
};
|
|
scene.cabprobe_program = program("cabprobe.frag");
|
|
scene.cabprobe_alpha_program = program("cabprobe_alpha.frag");
|
|
if (false == scene.cabprobe_sh.create(device, ctx.pipelines, ctx.shaders.get("cabprobe_sh.comp"), scene.pass_layout, {}, "cab probe spherical harmonics"))
|
|
{
|
|
Reason = "no compute pipeline of the cab probe";
|
|
return false;
|
|
}
|
|
}
|
|
catch (gfx::shader_exception const &e)
|
|
{
|
|
Reason = std::string("invalid shader: ") + e.what();
|
|
return false;
|
|
}
|
|
// the light reflected and the albedo, the six faces drawn at once
|
|
scene.cabprobe_formats.colour = {LIGHT_FORMAT, ALBEDO_FORMAT};
|
|
scene.cabprobe_formats.colours = 2;
|
|
scene.cabprobe_formats.depth = VK_FORMAT_D32_SFLOAT;
|
|
scene.cabprobe_formats.view_mask = 0x3f;
|
|
scene.cabprobe_formats.id = ++scene.formats_count;
|
|
// the light the cab lights itself with again, cleared with the stand-ins in the first frame
|
|
if (false == scene.cabprobe_bounce.create(ctx.memory, sizeof(gl::cab_probe_data), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "cab probe bounce"))
|
|
{
|
|
Reason = "no buffer of the cab probe";
|
|
return false;
|
|
}
|
|
// the stand-in of the maps: 1x1 faces of nothing, cleared with the other stand-ins in the first frame
|
|
vk14::image_desc desc;
|
|
desc.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
desc.view_type = VK_IMAGE_VIEW_TYPE_CUBE;
|
|
desc.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
|
desc.layers = 6;
|
|
desc.extent = {1, 1, 1};
|
|
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
scene.cabprobe_empty = std::make_shared<vk14::image>();
|
|
if (false == scene.cabprobe_empty->create(ctx.memory, desc, "empty cab probe"))
|
|
{
|
|
scene.cabprobe_empty.reset();
|
|
Reason = "no stand-in of the cab probe";
|
|
return false;
|
|
}
|
|
scene.cabprobe.init(device, ctx.memory, ctx.deletions);
|
|
return true;
|
|
}
|
|
|
|
void vulkan14_renderer::destroy_cab_probe()
|
|
{
|
|
auto &ctx{*m_context};
|
|
auto &scene{ctx.scene};
|
|
scene.cabprobe.destroy();
|
|
scene.cabprobe_sh.destroy(ctx.device.handle());
|
|
scene.cabprobe_program.reset();
|
|
scene.cabprobe_alpha_program.reset();
|
|
scene.cabprobe_bounce.destroy();
|
|
scene.cabprobe_empty.reset();
|
|
}
|
|
|
|
TDynamicObject *vulkan14_renderer::cab_probe_vehicle() const
|
|
{
|
|
// the conditions of Render_cab()
|
|
if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr))
|
|
{
|
|
return nullptr;
|
|
}
|
|
auto *vehicle{simulation::Train->Dynamic()};
|
|
if ((vehicle == nullptr) || (false == vehicle->bDisplayCab) || (vehicle->mdKabina == nullptr) || (vehicle->mdKabina == vehicle->mdModel))
|
|
{
|
|
return nullptr;
|
|
}
|
|
return vehicle;
|
|
}
|
|
|
|
void vulkan14_renderer::render_cab_probe()
|
|
{
|
|
auto &ctx{*m_context};
|
|
auto &scene{ctx.scene};
|
|
auto &probe{scene.cabprobe};
|
|
auto const commands{ctx.commands};
|
|
auto *vehicle{cab_probe_vehicle()};
|
|
glm::vec3 center{0.f};
|
|
glm::vec3 extent{0.f};
|
|
if ((vehicle != nullptr) && (false == m_locallights.cab_bounds(vehicle->mdKabina, m_geometry, center, extent)))
|
|
{
|
|
vehicle = nullptr;
|
|
}
|
|
// the cab lamp lights the probe when it's a light of the frame
|
|
auto const *lamp{(vehicle != nullptr) && (m_locallights.cab_lamp_vehicle() == vehicle) ? m_locallights.cab_lamp() : nullptr};
|
|
if (false == probe.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), center, extent, lamp, m_framestamp))
|
|
{
|
|
return;
|
|
}
|
|
auto const &target{probe.target()};
|
|
if ((target.light == nullptr) || (target.albedo == nullptr) || (target.depth == nullptr) || (false == bool(scene.cabprobe_program)))
|
|
{
|
|
return;
|
|
}
|
|
ctx.passes.begin_pass("cab probe");
|
|
// the lamp with its shadow map, when the map holds the shadows of the lamp where it is
|
|
auto const shadows{(lamp != nullptr) && scene.lamps.ready(vehicle) && (scene.lamps.lamp() == m_locallights.cab_lamp_local())};
|
|
probe.setup_drawing(scene_ubs, lamp, m_locallights.cab_lamp_local(), shadows, scene.lamps.weights());
|
|
// the cab drawn by the colour mode with the programs of the probe. the camera is put at the vehicle, which Render_cab() places the cab
|
|
// relative to, and the modelview takes the cab model to the space of the probe
|
|
m_renderpass.draw_mode = rendermode::color;
|
|
m_renderpass.pass_camera.position() = vehicle->GetPosition();
|
|
m_renderpass.draw_stats = {};
|
|
OpenGLMatrices.mode(GL_MODELVIEW);
|
|
OpenGLMatrices.load_matrix(glm::mat4{probe.model_to_probe() * glm::inverse(vehicle->mMatrix)});
|
|
probe.begin();
|
|
std::vector<vk14::image_use> const uses{vk14::use_of(*target.light, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access),
|
|
vk14::use_of(*target.albedo, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access),
|
|
vk14::use_of(*target.depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)};
|
|
vk14::barriers(commands, uses);
|
|
VkClearValue nothing{};
|
|
nothing.color = {{0.f, 0.f, 0.f, 0.f}};
|
|
VkClearValue farthest{};
|
|
farthest.depthStencil = {0.f, 0};
|
|
std::array<VkRenderingAttachmentInfo, 2> colours{cleared_attachment(target.light_view, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, nothing),
|
|
cleared_attachment(target.albedo_view, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, nothing)};
|
|
auto depth{cleared_attachment(target.depth->view(), VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, farthest)};
|
|
VkRenderingInfo rendering{};
|
|
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
|
|
rendering.renderArea = {{0, 0}, {target.size, target.size}};
|
|
rendering.layerCount = 1;
|
|
rendering.viewMask = scene.cabprobe_formats.view_mask;
|
|
rendering.colorAttachmentCount = static_cast<std::uint32_t>(colours.size());
|
|
rendering.pColorAttachments = colours.data();
|
|
rendering.pDepthAttachment = &depth;
|
|
commands->begin_rendering(&rendering);
|
|
remember_rendering(rendering, uses);
|
|
// one sided walls face the probe from either side
|
|
start_draws(scene.cabprobe_formats, static_cast<int>(target.size), static_cast<int>(target.size), vk14::scene_state::set_plain, false);
|
|
m_draw->cullface = false;
|
|
walk_blending(false);
|
|
upload_scene_block();
|
|
upload_light_header();
|
|
m_draw->probepass = true;
|
|
m_draw->depthskip = true;
|
|
Render_cab(vehicle, 0.0f, false);
|
|
m_draw->probepass = false;
|
|
m_draw->depthskip = true;
|
|
if (m_draw->rendering)
|
|
{
|
|
commands->end_rendering();
|
|
}
|
|
end_draws();
|
|
// the mipmaps, and the light the cab lights itself with again from a small level of the light reflected
|
|
make_cube_mipmaps(commands, *target.light);
|
|
make_cube_mipmaps(commands, *target.albedo);
|
|
struct
|
|
{
|
|
glm::ivec2 level;
|
|
} constants{glm::ivec2{probe.sh_level(), probe.sh_size()}};
|
|
scene.cabprobe_sh.dispatch(commands,
|
|
{vk14::image_descriptor(ctx.sampling.clamp_linear, target.light->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
|
|
vk14::buffer_descriptor(scene.cabprobe_bounce.handle(), 0, sizeof(gl::cab_probe_data))},
|
|
constants, 1);
|
|
vk14::memory_barrier(commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT);
|
|
probe.drawn();
|
|
probe.end(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
|
|
m_renderpass = m_colorpass;
|
|
ctx.passes.end_pass();
|
|
}
|