-
-
Notifications
You must be signed in to change notification settings - Fork 55
/
Copy pathParallelRenderingApp.cpp
565 lines (473 loc) · 26.3 KB
/
ParallelRenderingApp.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
/******************************************************************************
Copyright 2022 Evgeny Gorodetskiy
Licensed under the Apache License, Version 2.0 (the "License"),
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*******************************************************************************
FILE: ParallelRenderingApp.cpp
Tutorial demonstrating parallel rendering with Methane graphics API
******************************************************************************/
#include "ParallelRenderingApp.h"
#include "ParallelRenderingAppController.h"
#include <Methane/Tutorials/TextureLabeler.h>
#include <Methane/Tutorials/AppSettings.h>
#include <Methane/Graphics/CubeMesh.hpp>
#include <Methane/Data/TimeAnimation.h>
#include <Methane/Instrumentation.h>
#include <taskflow/algorithm/for_each.hpp>
#include <taskflow/algorithm/sort.hpp>
#include <cmath>
#include <random>
#include <algorithm>
namespace Methane::Tutorials
{
#define EXPLICIT_PARALLEL_RENDERING_ENABLED
namespace gui = Methane::UserInterface;
struct CubeVertex
{
gfx::Mesh::Position position;
gfx::Mesh::TexCoord texcoord;
inline static const gfx::Mesh::VertexLayout layout{
gfx::Mesh::VertexField::Position,
gfx::Mesh::VertexField::TexCoord,
};
};
static const gfx::Dimensions g_texture_size{ 320U, 320U };
static const float g_scene_scale = 22.F;
namespace pin = Methane::Platform::Input;
static const std::map<pin::Keyboard::State, ParallelRenderingAppAction> g_parallel_rendering_action_by_keyboard_state{
{ { pin::Keyboard::Key::P }, ParallelRenderingAppAction::SwitchParallelRendering },
{ { pin::Keyboard::Key::Equal }, ParallelRenderingAppAction::IncreaseCubesGridSize },
{ { pin::Keyboard::Key::Minus }, ParallelRenderingAppAction::DecreaseCubesGridSize },
{ { pin::Keyboard::Key::RightBracket }, ParallelRenderingAppAction::IncreaseRenderThreadsCount },
{ { pin::Keyboard::Key::LeftBracket }, ParallelRenderingAppAction::DecreaseRenderThreadsCount },
};
#ifdef ROOT_CONSTANTS_ENABLED
constexpr char const* g_app_variant_name = "Root Constants";
#else
constexpr char const* g_app_variant_name = "Buffer Views";
#endif
uint32_t ParallelRenderingApp::Settings::GetTotalCubesCount() const noexcept
{
META_FUNCTION_TASK();
return static_cast<uint32_t>(std::pow(cubes_grid_size, 3U));
}
uint32_t ParallelRenderingApp::Settings::GetActiveRenderThreadCount() const noexcept
{
META_FUNCTION_TASK();
return parallel_rendering_enabled ? render_thread_count : 1U;
}
ParallelRenderingApp::ParallelRenderingApp()
: UserInterfaceApp(
GetGraphicsTutorialAppSettings(fmt::format("Methane Parallel Rendering ({})", g_app_variant_name),
AppOptions::GetDefaultWithColorDepthAndAnim()),
GetUserInterfaceTutorialAppSettings(AppOptions::GetDefaultWithColorDepthAndAnim()),
"Methane tutorial of parallel rendering")
{
META_FUNCTION_TASK();
m_camera.ResetOrientation({ { 13.F, 13.F, -13.F }, { 0.F, 0.F, 0.F }, { 0.F, 1.F, 0.F } });
AddInputControllers({
std::make_shared<ParallelRenderingAppController>(*this, g_parallel_rendering_action_by_keyboard_state)
});
const std::string options_group = "Parallel Rendering Options";
add_option_group(options_group);
add_option("-p,--parallel-render", m_settings.parallel_rendering_enabled, "enable parallel rendering")->group(options_group);
add_option("-g,--cubes-grid-size", m_settings.cubes_grid_size, "cubes grid size")->group(options_group);
add_option("-t,--threads-count", m_settings.render_thread_count, "render threads count")->group(options_group);
// Setup animations
GetAnimations().emplace_back(std::make_shared<Data::TimeAnimation>(std::bind(&ParallelRenderingApp::Animate, this, std::placeholders::_1, std::placeholders::_2)));
ShowParameters();
}
ParallelRenderingApp::~ParallelRenderingApp()
{
META_FUNCTION_TASK();
// Wait for GPU rendering is completed to release resources
WaitForRenderComplete();
}
void ParallelRenderingApp::Init()
{
META_FUNCTION_TASK();
UserInterfaceApp::Init();
const rhi::CommandQueue render_cmd_queue = GetRenderContext().GetRenderCommandKit().GetQueue();
m_camera.Resize(GetRenderContext().GetSettings().frame_size);
// Create cube mesh
gfx::CubeMesh<CubeVertex> cube_mesh(CubeVertex::layout);
// Create render state with program
rhi::RenderState::Settings render_state_settings
{
GetRenderContext().CreateProgram(
rhi::Program::Settings
{
rhi::Program::ShaderSet
{
{ rhi::ShaderType::Vertex, { Data::ShaderProvider::Get(), { "ParallelRendering", "CubeVS" } } },
{ rhi::ShaderType::Pixel, { Data::ShaderProvider::Get(), { "ParallelRendering", "CubePS" } } },
},
rhi::ProgramInputBufferLayouts
{
rhi::Program::InputBufferLayout
{
rhi::Program::InputBufferLayout::ArgumentSemantics { cube_mesh.GetVertexLayout().GetSemantics() }
}
},
rhi::ProgramArgumentAccessors
{
// Addressable argument is manually defined
#ifdef ROOT_CONSTANTS_ENABLED
META_PROGRAM_ARG_ROOT_BUFFER_MUTABLE(rhi::ShaderType::All, "g_uniforms")
#else
META_PROGRAM_ARG_BUFFER_ADDRESS_MUTABLE(rhi::ShaderType::All, "g_uniforms")
#endif
// Other arguments are defined in shader register spaces
},
GetScreenRenderPattern().GetAttachmentFormats()
}
),
GetScreenRenderPattern()
};
render_state_settings.program.SetName("Render Pipeline State");
render_state_settings.depth.enabled = true;
m_render_state = GetRenderContext().CreateRenderState( render_state_settings);
// Create cube mesh buffer resources
const uint32_t cubes_count = m_settings.GetTotalCubesCount();
const gfx::Mesh::Subsets mesh_subsets(cubes_count,
gfx::Mesh::Subset(gfx::Mesh::Type::Box,
gfx::Mesh::Subset::Slice(0U, cube_mesh.GetVertexCount()),
gfx::Mesh::Subset::Slice(0U, cube_mesh.GetIndexCount()),
false));
m_cube_array_buffers_ptr = std::make_unique<MeshBuffers>(render_cmd_queue, std::move(cube_mesh), "Cube", mesh_subsets);
// Create cube-map render target texture
m_texture_array = GetRenderContext().CreateTexture(
rhi::Texture::Settings::ForImage(g_texture_size, m_settings.render_thread_count, gfx::PixelFormat::RGBA8Unorm, false,
rhi::ResourceUsageMask({ rhi::ResourceUsage::RenderTarget, rhi::ResourceUsage::ShaderRead })));
m_texture_array.SetName("Per-Thread Texture Array");
// Create sampler for image texture
m_texture_sampler = GetRenderContext().CreateSampler(
rhi::Sampler::Settings
{
rhi::Sampler::Filter { rhi::Sampler::Filter::MinMag::Linear },
rhi::Sampler::Address { rhi::Sampler::Address::Mode::ClampToEdge }
}
);
// Create frame buffer resources
tf::Taskflow program_bindings_task_flow;
for(ParallelRenderingFrame& frame : GetFrames())
{
#ifndef ROOT_CONSTANTS_ENABLED
// Create buffer for uniforms array related to all cube instances
frame.cubes_array.uniforms_buffer = GetRenderContext().CreateBuffer(
rhi::BufferSettings::ForConstantBuffer(m_cube_array_buffers_ptr->GetUniformsBufferSize(), true, true));
frame.cubes_array.uniforms_buffer.SetName(fmt::format("Uniforms Buffer {}", frame.index));
#endif
// Configure program resource bindings
#ifdef ROOT_CONSTANTS_ENABLED
frame.cubes_program_bindings.resize(cubes_count);
frame.cubes_uniform_argument_binding_ptrs.resize(cubes_count);
frame.cubes_program_bindings[0] = render_state_settings.program.CreateBindings({
{ { rhi::ShaderType::Pixel, "g_texture_array" }, m_texture_array.GetResourceView() },
{ { rhi::ShaderType::Pixel, "g_sampler" }, m_texture_sampler.GetResourceView() },
}, frame.index);
frame.cubes_uniform_argument_binding_ptrs[0] = &frame.cubes_program_bindings[0].Get({ rhi::ShaderType::All, "g_uniforms" });
frame.cubes_program_bindings[0].SetName(fmt::format("Cube 0 Bindings {}", frame.index));
#else
static const Data::Size uniform_data_size = MeshBuffers::GetUniformSize();
frame.cubes_array.program_bindings_per_instance.resize(cubes_count);
frame.cubes_array.program_bindings_per_instance[0] = render_state_settings.program.CreateBindings({
{
{ rhi::ShaderType::All, "g_uniforms" },
frame.cubes_array.uniforms_buffer.GetBufferView(
m_cube_array_buffers_ptr->GetUniformsBufferOffset(0U), uniform_data_size)
},
{ { rhi::ShaderType::Pixel, "g_texture_array" }, m_texture_array.GetResourceView() },
{ { rhi::ShaderType::Pixel, "g_sampler" }, m_texture_sampler.GetResourceView() },
}, frame.index);
frame.cubes_array.program_bindings_per_instance[0].SetName(fmt::format("Cube 0 Bindings {}", frame.index));
const MeshBuffers& cube_array_buffers = *m_cube_array_buffers_ptr;
#endif
program_bindings_task_flow.for_each_index(1U, cubes_count, 1U,
#ifdef ROOT_CONSTANTS_ENABLED
[&frame](const uint32_t cube_index)
{
rhi::ProgramBindings& cube_program_bindings = frame.cubes_program_bindings[cube_index];
cube_program_bindings = rhi::ProgramBindings(frame.cubes_program_bindings[0], {}, frame.index);
frame.cubes_uniform_argument_binding_ptrs[cube_index] = &cube_program_bindings.Get({ rhi::ShaderType::All, "g_uniforms" });
cube_program_bindings.SetName(fmt::format("Cube {} Bindings {}", cube_index, frame.index));
}
#else
[&frame, &cube_array_buffers](const uint32_t cube_index)
{
rhi::ProgramBindings& cube_program_bindings = frame.cubes_array.program_bindings_per_instance[cube_index];
cube_program_bindings = rhi::ProgramBindings(frame.cubes_array.program_bindings_per_instance[0], {
{
{ rhi::ShaderType::All, "g_uniforms" },
frame.cubes_array.uniforms_buffer.GetBufferView(
cube_array_buffers.GetUniformsBufferOffset(cube_index),
uniform_data_size)
}
}, frame.index);
cube_program_bindings.SetName(fmt::format("Cube {} Bindings {}", cube_index, frame.index));
}
#endif
);
if (m_settings.parallel_rendering_enabled)
{
// Create parallel command list for rendering to the screen pass
frame.parallel_render_cmd_list = render_cmd_queue.CreateParallelRenderCommandList(frame.screen_pass);
frame.parallel_render_cmd_list.SetParallelCommandListsCount(m_settings.GetActiveRenderThreadCount());
frame.parallel_render_cmd_list.SetValidationEnabled(false);
frame.parallel_render_cmd_list.SetName(fmt::format("Parallel Cubes Rendering {}", frame.index));
frame.execute_cmd_list_set = rhi::CommandListSet({ frame.parallel_render_cmd_list.GetInterface() }, frame.index);
}
else
{
// Create serial command list for rendering to the screen pass
frame.serial_render_cmd_list = render_cmd_queue.CreateRenderCommandList(frame.screen_pass);
frame.serial_render_cmd_list.SetName(fmt::format("Serial Cubes Rendering {}", frame.index));
frame.serial_render_cmd_list.SetValidationEnabled(false);
frame.execute_cmd_list_set = rhi::CommandListSet({ frame.serial_render_cmd_list.GetInterface() }, frame.index);
}
}
// Execute parallel program bindings copy initialization for all cubes
GetRenderContext().GetParallelExecutor().run(program_bindings_task_flow).get();
// Create all resources for texture labels rendering before resources upload in UserInterfaceApp::CompleteInitialization()
TextureLabeler::Settings texture_labeler_settings;
texture_labeler_settings.font_size_pt = g_texture_size.GetWidth() / 4U;
texture_labeler_settings.border_width_px = 10U;
TextureLabeler cube_texture_labeler(GetUIContext(), GetFontContext(), m_texture_array,
rhi::ResourceState::ShaderResource, texture_labeler_settings);
// Upload all resources, including font texture and text mesh buffers required for rendering
UserInterfaceApp::CompleteInitialization();
// Encode and execute texture labels rendering commands when all resources are uploaded and ready on GPU
cube_texture_labeler.Render();
// Initialize cube parameters
m_cube_array_parameters = InitializeCubeArrayParameters();
// Update initial resource states before asteroids drawing without applying barriers on GPU to let automatic state propagation from Common state work
m_cube_array_buffers_ptr->CreateBeginningResourceBarriers().ApplyTransitions();
GetRenderContext().WaitForGpu(rhi::IContext::WaitFor::RenderComplete);
}
ParallelRenderingApp::CubeArrayParameters ParallelRenderingApp::InitializeCubeArrayParameters() const
{
META_FUNCTION_TASK();
const uint32_t cubes_count = m_settings.GetTotalCubesCount();
const auto cbrt_count = static_cast<size_t>(std::floor(std::cbrt(static_cast<float>(cubes_count))));
const size_t cbrt_count_sqr = cbrt_count * cbrt_count;
const float cbrt_count_half = static_cast<float>(cbrt_count - 1) / 2.F;
const float ts = g_scene_scale / static_cast<float>(cbrt_count);
const float median_cube_scale = ts / 2.F;
const float cube_scale_delta = median_cube_scale / 3.F;
std::mt19937 rng(1234U); // NOSONAR - using pseudorandom generator is safe here
std::uniform_real_distribution<float> cube_scale_distribution(median_cube_scale - cube_scale_delta, median_cube_scale + cube_scale_delta);
std::uniform_real_distribution<double> rotation_speed_distribution(-0.8F, 0.8F);
std::uniform_int_distribution<uint32_t> thread_index_distribution(0U, m_settings.render_thread_count);
CubeArrayParameters cube_array_parameters(cubes_count);
// Position all cubes in a cube grid and assign to random threads
tf::Taskflow task_flow;
tf::Task init_task = task_flow.for_each_index(0U, cubes_count, 1U,
[&rng, &cube_array_parameters, &cube_scale_distribution, &rotation_speed_distribution, &thread_index_distribution,
ts, cbrt_count, cbrt_count_sqr, cbrt_count_half](const uint32_t cube_index)
{
const float tx = static_cast<float>(cube_index % cbrt_count) - cbrt_count_half;
const float ty = static_cast<float>(cube_index % cbrt_count_sqr / cbrt_count) - cbrt_count_half;
const float tz = static_cast<float>(cube_index / cbrt_count_sqr) - cbrt_count_half;
const float cs = cube_scale_distribution(rng);
const hlslpp::float4x4 scale_matrix = hlslpp::float4x4::scale(cs);
const hlslpp::float4x4 translation_matrix = hlslpp::float4x4::translation(tx * ts, ty * ts, tz * ts);
CubeParameters& cube_params = cube_array_parameters[cube_index];
cube_params.model_matrix = hlslpp::mul(scale_matrix, translation_matrix);
cube_params.rotation_speed_y = rotation_speed_distribution(rng);
cube_params.rotation_speed_z = rotation_speed_distribution(rng);
// Distribute cubes randomly between threads
cube_params.thread_index = thread_index_distribution(rng);
});
// Sort cubes parameters by thread index
// to make sure that actual cubes distribution by render threads will match thread_index in parameters
// NOTE-1: thread index is displayed on cube faces as text label using an element of Texture 2D Array.
// NOTE-2: Sorting also improves rendering performance because it ensures using one texture for all cubes per thread.
tf::Task sort_task = task_flow.sort(cube_array_parameters.begin(), cube_array_parameters.end(),
[](const CubeParameters& left, const CubeParameters& right)
{ return left.thread_index < right.thread_index; });
// Fixup even distribution of cubes between threads
const auto cubes_count_per_thread = static_cast<uint32_t>(std::ceil(static_cast<double>(cubes_count) / m_settings.render_thread_count));
tf::Task even_task = task_flow.for_each_index(0U, cubes_count, 1U,
[&cube_array_parameters, cubes_count_per_thread](const uint32_t cube_index)
{
cube_array_parameters[cube_index].thread_index = cube_index / cubes_count_per_thread;
});
init_task.precede(sort_task);
sort_task.precede(even_task);
// Execute parallel initialization of cube array parameters
GetRenderContext().GetParallelExecutor().run(task_flow).get();
return cube_array_parameters;
}
bool ParallelRenderingApp::Animate(double, double delta_seconds)
{
META_FUNCTION_TASK();
m_camera.Rotate(m_camera.GetOrientation().up, static_cast<float>(delta_seconds * 360.0 / 16.0));
const double delta_angle_rad = delta_seconds * gfx::ConstDouble::Pi;
tf::Taskflow task_flow;
task_flow.for_each(m_cube_array_parameters.begin(), m_cube_array_parameters.end(),
[delta_angle_rad](CubeParameters& cube_params)
{
const hlslpp::float4x4 rotate_matrix = hlslpp::mul(hlslpp::float4x4::rotation_z(static_cast<float>(delta_angle_rad * cube_params.rotation_speed_z)),
hlslpp::float4x4::rotation_y(static_cast<float>(delta_angle_rad * cube_params.rotation_speed_y)));
cube_params.model_matrix = hlslpp::mul(rotate_matrix, cube_params.model_matrix);
});
GetRenderContext().GetParallelExecutor().run(task_flow).get();
return true;
}
bool ParallelRenderingApp::Resize(const gfx::FrameSize& frame_size, bool is_minimized)
{
META_FUNCTION_TASK();
// Resize screen color and depth textures
if (!UserInterfaceApp::Resize(frame_size, is_minimized))
return false;
m_camera.Resize(frame_size);
return true;
}
bool ParallelRenderingApp::Update()
{
META_FUNCTION_TASK();
if (!UserInterfaceApp::Update())
return false;
const ParallelRenderingFrame& frame = GetCurrentFrame();
// Update MVP-matrices for all cube instances so that they are positioned in a cube grid
tf::Taskflow task_flow;
task_flow.for_each_index(0U, static_cast<uint32_t>(m_cube_array_parameters.size()), 1U,
[this, &frame](const uint32_t cube_index)
{
const CubeParameters& cube_params = m_cube_array_parameters[cube_index];
hlslpp::Uniforms uniforms{};
uniforms.mvp_matrix = hlslpp::transpose(hlslpp::mul(cube_params.model_matrix, m_camera.GetViewProjMatrix()));
uniforms.texture_index = cube_params.thread_index;
#ifdef ROOT_CONSTANTS_ENABLED
frame.cubes_uniform_argument_binding_ptrs[cube_index]->SetRootConstant(rhi::RootConstant(uniforms));
#else
META_UNUSED(frame);
m_cube_array_buffers_ptr->SetFinalPassUniforms(std::move(uniforms), cube_index);
#endif
});
GetRenderContext().GetParallelExecutor().run(task_flow).get();
return true;
}
bool ParallelRenderingApp::Render()
{
META_FUNCTION_TASK();
if (!UserInterfaceApp::Render())
return false;
const ParallelRenderingFrame& frame = GetCurrentFrame();
const rhi::CommandQueue render_cmd_queue = GetRenderContext().GetRenderCommandKit().GetQueue();
#ifdef ROOT_CONSTANTS_ENABLED
const auto& cubes_program_bindings = frame.cubes_program_bindings;
#else
// Update uniforms buffer related to current frame
frame.cubes_array.uniforms_buffer.SetData(render_cmd_queue, m_cube_array_buffers_ptr->GetFinalPassUniformsSubresource());
const auto& cubes_program_bindings = frame.cubes_array.program_bindings_per_instance;
#endif
// Render cube instances of 'CUBE_MAP_ARRAY_SIZE' count
if (m_settings.parallel_rendering_enabled)
{
META_DEBUG_GROUP_VAR(s_debug_group, "Parallel Cubes Rendering");
frame.parallel_render_cmd_list.ResetWithState(m_render_state, &s_debug_group);
frame.parallel_render_cmd_list.SetViewState(GetViewState());
#ifdef EXPLICIT_PARALLEL_RENDERING_ENABLED
const std::vector<rhi::RenderCommandList>& render_cmd_lists = frame.parallel_render_cmd_list.GetParallelCommandLists();
const uint32_t instance_count_per_command_list = Data::DivCeil(m_cube_array_buffers_ptr->GetInstanceCount(), static_cast<uint32_t>(render_cmd_lists.size()));
// Generate thread tasks for each of parallel render command lists to encode cubes rendering commands
tf::Taskflow render_task_flow;
render_task_flow.for_each_index(0U, static_cast<uint32_t>(render_cmd_lists.size()), 1U,
[this, &cubes_program_bindings, &render_cmd_lists, instance_count_per_command_list](const uint32_t cmd_list_index)
{
const uint32_t begin_instance_index = cmd_list_index * instance_count_per_command_list;
const uint32_t end_instance_index = std::min(begin_instance_index + instance_count_per_command_list, m_cube_array_buffers_ptr->GetInstanceCount());
RenderCubesRange(render_cmd_lists[cmd_list_index], cubes_program_bindings, begin_instance_index, end_instance_index);
}
);
// Execute rendering in multiple threads
GetRenderContext().GetParallelExecutor().run(render_task_flow).get();
#else
// The same parallel rendering is done inside MeshBuffers::DrawParallel helper function
m_cube_array_buffers_ptr->DrawParallel(frame.parallel_render_cmd_list, cubes_program_bindings);
#endif
RenderOverlay(frame.parallel_render_cmd_list.GetParallelCommandLists().back());
frame.parallel_render_cmd_list.Commit();
}
else
{
META_DEBUG_GROUP_VAR(s_debug_group, "Serial Cubes Rendering");
frame.serial_render_cmd_list.ResetWithState(m_render_state, &s_debug_group);
frame.serial_render_cmd_list.SetViewState(GetViewState());
#ifdef EXPLICIT_PARALLEL_RENDERING_ENABLED
RenderCubesRange(frame.serial_render_cmd_list, cubes_program_bindings, 0U, m_cube_array_buffers_ptr->GetInstanceCount());
#else
m_cube_array_buffers_ptr->Draw(frame.serial_render_cmd_list, cubes_program_bindings);
#endif
RenderOverlay(frame.serial_render_cmd_list);
frame.serial_render_cmd_list.Commit();
}
// Execute command lists on render queue and present frame to screen
render_cmd_queue.Execute(frame.execute_cmd_list_set);
GetRenderContext().Present();
return true;
}
void ParallelRenderingApp::RenderCubesRange(const rhi::RenderCommandList& render_cmd_list,
const std::vector<rhi::ProgramBindings>& program_bindings_per_instance,
uint32_t begin_instance_index, const uint32_t end_instance_index) const
{
META_FUNCTION_TASK();
// Resource barriers are not set for vertex and index buffers, since it works with automatic state propagation from Common state
render_cmd_list.SetVertexBuffers(m_cube_array_buffers_ptr->GetVertexBuffers(), false);
render_cmd_list.SetIndexBuffer(m_cube_array_buffers_ptr->GetIndexBuffer(), false);
for (uint32_t instance_index = begin_instance_index; instance_index < end_instance_index; ++instance_index)
{
// Constant argument bindings are applied once per command list, mutables are applied always
// Bound resources are retained by command list during its lifetime, but only for the first binding instance (since all binding instances use the same resource objects)
rhi::ProgramBindingsApplyBehaviorMask bindings_apply_behavior;
bindings_apply_behavior.SetBitOn(rhi::ProgramBindingsApplyBehavior::ConstantOnce);
if (instance_index == begin_instance_index)
bindings_apply_behavior.SetBitOn(rhi::ProgramBindingsApplyBehavior::RetainResources);
render_cmd_list.SetProgramBindings(program_bindings_per_instance[instance_index], bindings_apply_behavior);
render_cmd_list.DrawIndexed(rhi::RenderPrimitive::Triangle);
}
}
std::string ParallelRenderingApp::GetParametersString()
{
META_FUNCTION_TASK();
std::stringstream ss;
ss << "Parallel Rendering parameters:"
<< std::endl << " - parallel rendering: " << (m_settings.parallel_rendering_enabled ? "ON" : "OFF")
<< std::endl << " - render threads count: " << m_settings.GetActiveRenderThreadCount()
<< std::endl << " - cubes grid size: " << m_settings.cubes_grid_size
<< std::endl << " - total cubes count: " << m_settings.GetTotalCubesCount()
<< std::endl << " - texture array size: " << g_texture_size.GetWidth() <<
" x " << g_texture_size.GetHeight() <<
" [" << m_settings.render_thread_count << "]";
return ss.str();
}
void ParallelRenderingApp::SetSettings(const Settings& settings)
{
META_FUNCTION_TASK();
if (m_settings == settings)
return;
m_settings = settings;
GetRenderContext().Reset();
}
void ParallelRenderingApp::OnContextReleased(rhi::IContext& context)
{
META_FUNCTION_TASK();
m_cube_array_buffers_ptr.reset();
m_texture_array = {};
m_texture_sampler = {};
m_render_state = {};
UserInterfaceApp::OnContextReleased(context);
}
} // namespace Methane::Tutorials
int main(int argc, const char* argv[])
{
return Methane::Tutorials::ParallelRenderingApp().Run({ argc, argv });
}