Skip to content

Commit a76981f

Browse files
authored
renderer, unity: replaces the single onFrame tick with discrete render passes
This replaces the single `onFrame` tick with discrete render passes, allowing finer-grained control over each stage of rendering. - Introduces `UnityRenderPassEvent` and dispatches per-pass events in `OnUnityRenderEvent`. - Adds new lifecycle methods (`onBeforeRendering`, `onOpaquesRenderPass`, `onTransparentsRenderPass`, `onAfterRendering`) in `TrEmbedder` and `TrConstellation`. - Refactors renderer interfaces and updates the OpenGL example to call the new pass methods.
1 parent bb36f33 commit a76981f

File tree

9 files changed

+168
-50
lines changed

9 files changed

+168
-50
lines changed

docs/api/embedder.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,26 @@ The above fields are used to configure the runtime:
102102

103103
Internally, this method will start the internal components: renderer, content manager, media service and other services.
104104

105-
#### `void onFrame()`
105+
#### `void onBeforeRendering()`
106106

107-
This method should be called in every frame to update the runtime, such as `Update()` in Unity or `Tick()` in Unreal.
107+
Call this method before rendering in each frame, it will prepare the runtime for rendering, such as updating the renderer
108+
and content manager.
108109

109-
Note: This frame in the method name doesn't mean the actual frame especially in the case of XR, it represents an update cycle by the host engine, the frame in OpenXR or WebXR should be configured by the `configureXrDevice(bool enabled, xr::TrDeviceInit &init)` method which will be explained later.
110+
#### `void onOpaquesRenderPass()`
111+
112+
Call this method to render the opaque objects.
113+
114+
This pass is the mainly render pass which renders all the opaque objects of the JSAR applications, and also schedules the draw calls which should be rendered in the next passes, such as transparents, post-processing, and other render-texture rendering.
115+
116+
#### `void onTransparentsRenderPass()`
117+
118+
Call this method to render the transparent objects.
119+
120+
This pass executes the draw calls which enabled the blending, and uses the Weighted Blended OIT algorithm to render all the transparents elements (e.g. glass, water and GUIs) without depth sorting.
121+
122+
#### `void onAfterRendering()`
123+
124+
In this pass, it uses to achieve some post-processing effects or asynchronous rendering tasks.
110125

111126
#### `bool configureXrDevice(bool enabled, xr::TrDeviceInit &init)`
112127

src/examples/desktop_opengl.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ namespace jsar::example
358358
if (embedder_ == nullptr)
359359
continue; // Skip the rendering if the embedder is not ready.
360360

361+
embedder_->onBeforeRendering();
362+
361363
if (xrEnabled)
362364
{
363365
auto xrRenderer = windowCtx_->xrRenderer;
@@ -393,7 +395,10 @@ namespace jsar::example
393395

394396
auto viewerBaseMatrix = const_cast<float *>(glm::value_ptr(xrRenderer->getViewerBaseMatrix()));
395397
xrDevice->updateViewerBaseMatrix(viewerBaseMatrix);
396-
embedder_->onFrame();
398+
399+
// Enqueue passes
400+
embedder_->onOpaquesRenderPass();
401+
embedder_->onTransparentsRenderPass();
397402
}
398403
}
399404
}
@@ -420,16 +425,22 @@ namespace jsar::example
420425
xrDevice->updateViewMatrix(viewIndex, viewMatrix);
421426
xrDevice->updateProjectionMatrix(viewIndex, projectionMatrix);
422427
}
423-
embedder_->onFrame();
428+
429+
embedder_->onOpaquesRenderPass();
430+
embedder_->onTransparentsRenderPass();
424431
}
425432
}
426433
else // Non-XR rendering
427434
{
428435
glViewport(0, 0, drawingViewport.width(), drawingViewport.height());
429436
glGetError(); // Clear the error
430-
embedder_->onFrame();
437+
438+
embedder_->onOpaquesRenderPass();
439+
embedder_->onTransparentsRenderPass();
431440
}
432441

442+
embedder_->onAfterRendering();
443+
433444
// render screen-space panel
434445
panel->render();
435446

src/renderer/renderer.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ namespace renderer
4747
startWatchers();
4848
}
4949

50-
void TrRenderer::tick(analytics::PerformanceCounter &perfCounter)
50+
void TrRenderer::onOpaquesRenderPass(analytics::PerformanceCounter &perfCounter)
5151
{
52-
if (TR_UNLIKELY(api == nullptr))
52+
if (api == nullptr) [[unlikely]]
5353
return; // Skip if api is not ready.
5454

5555
tickingTimepoint = std::chrono::high_resolution_clock::now();
@@ -95,6 +95,12 @@ namespace renderer
9595
perfCounter.record(" renderer.finishedHostContextRestore");
9696
}
9797

98+
void TrRenderer::onTransparentsRenderPass(analytics::PerformanceCounter &perfCounter)
99+
{
100+
if (api == nullptr) [[unlikely]]
101+
return; // Skip if api is not ready.
102+
}
103+
98104
void TrRenderer::shutdown()
99105
{
100106
stopWatchers();

src/renderer/renderer.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ namespace renderer
6767

6868
public:
6969
void initialize();
70-
void tick(analytics::PerformanceCounter &perfCounter);
70+
void onOpaquesRenderPass(analytics::PerformanceCounter &);
71+
void onTransparentsRenderPass(analytics::PerformanceCounter &);
7172
void shutdown();
7273
void setLogFilter(string filterExpr);
7374
/**

src/runtime/constellation.cpp

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,30 +90,48 @@ void TrConstellation::shutdown()
9090
initialized = false;
9191
}
9292

93-
void TrConstellation::tick(analytics::PerformanceCounter &perfCounter)
93+
void TrConstellation::onBeforeRendering(analytics::PerformanceCounter &perfCounter)
9494
{
95-
if (TR_UNLIKELY(initialized == false || disableTicking))
95+
if (initialized == false || disableTicking) [[unlikely]]
9696
return;
9797

9898
contentManager->tickOnFrame();
9999
perfCounter.record("finishContentManager");
100-
renderer->tick(perfCounter);
100+
}
101+
102+
void TrConstellation::onOpaquesRenderPass(analytics::PerformanceCounter &perfCounter)
103+
{
104+
if (initialized == false || disableTicking) [[unlikely]]
105+
return;
106+
107+
renderer->onOpaquesRenderPass(perfCounter);
101108
perfCounter.record("finishRenderer");
102109

103-
if (xrDevice->enabled())
110+
// TODO(yorkie): move the input sources sync to the metrices update?
111+
if (xrDevice->enabled()) [[likely]]
104112
{
105113
xrDevice->tick();
106114
perfCounter.record("finishInputSourcesSync");
107115
}
116+
}
108117

109-
#ifdef TR_ENABLE_INSPECTOR
110-
inspector->tick();
111-
#endif
118+
void TrConstellation::onTransparentsRenderPass(analytics::PerformanceCounter &perfCounter)
119+
{
120+
if (initialized == false || disableTicking) [[unlikely]]
121+
return;
122+
123+
renderer->onTransparentsRenderPass(perfCounter);
124+
perfCounter.record("finishRenderer");
112125
}
113126

114-
TrEmbedder *TrConstellation::getEmbedder()
127+
void TrConstellation::onAfterRendering()
115128
{
116-
return embedder;
129+
if (initialized == false || disableTicking) [[unlikely]]
130+
return;
131+
132+
#ifdef TR_ENABLE_INSPECTOR
133+
inspector->tick();
134+
#endif
117135
}
118136

119137
uint32_t TrConstellation::open(string url, optional<TrDocumentRequestInit> init)

src/runtime/constellation.hpp

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -215,32 +215,20 @@ class TrConstellation
215215
* @returns If the constellation is initialized successfully.
216216
*/
217217
bool initialize();
218-
/**
219-
* Shutdown the constellation.
220-
*/
221218
void shutdown();
222-
/**
223-
* This method `tick()` should be called in the main loop of the runtime, it will update the sub-systems.
224-
*
225-
* @param perfCounter The performance counter to record the time.
226-
*/
227-
void tick(analytics::PerformanceCounter &perfCounter);
228-
/**
229-
* @returns The reference to the constellation options.
230-
*/
219+
220+
void onBeforeRendering(analytics::PerformanceCounter &);
221+
void onOpaquesRenderPass(analytics::PerformanceCounter &);
222+
void onTransparentsRenderPass(analytics::PerformanceCounter &);
223+
void onAfterRendering();
224+
231225
TrConstellationInit &getOptions();
232-
/**
233-
* @returns If the constellation is initialized.
234-
*/
235226
bool isInitialized();
236-
/**
237-
* @returns If the runtime is ready.
238-
*/
239227
bool isRuntimeReady();
240-
/**
241-
* @returns The embedder pointer.
242-
*/
243-
TrEmbedder *getEmbedder();
228+
inline TrEmbedder *getEmbedder()
229+
{
230+
return embedder;
231+
}
244232

245233
public:
246234
/**

src/runtime/embedder.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,39 @@ void TrEmbedder::setRequestAuthorizationHeaders(std::string rawHeaders, std::vec
4747
constellation->contentManager->setRequestAuthorizationHeaders(rawHeaders, allowedOrigins);
4848
}
4949

50-
bool TrEmbedder::onFrame()
50+
void TrEmbedder::onBeforeRendering()
5151
{
52-
analytics::PerformanceCounter perfCounter("HostTick");
53-
constellation->tick(perfCounter);
52+
analytics::PerformanceCounter perfCounter("RenderingPreparation");
53+
constellation->onBeforeRendering(perfCounter);
54+
perfCounter.end();
55+
}
56+
57+
void TrEmbedder::onOpaquesRenderPass()
58+
{
59+
analytics::PerformanceCounter perfCounter("OpaquesRenderPass");
60+
constellation->onOpaquesRenderPass(perfCounter);
5461
perfCounter.end();
5562

5663
#ifdef TR_ENABLE_PERF_COUNTER
5764
auto frameDuration = perfCounter.duration();
5865
if (frameDuration > 2.0)
59-
DEBUG(LOG_TAG_ERROR, "Detected a long tick(>=2ms) in host frame: \n%s", perfCounter.toString().c_str());
66+
{
67+
DEBUG(LOG_TAG_ERROR,
68+
"Detected a long tick(>=2ms) in opaques renderpass: \n%s",
69+
perfCounter.toString().c_str());
70+
}
6071
constellation->perfFs->setFrameDuration(frameDuration);
6172
#endif
62-
return true;
73+
}
74+
75+
void TrEmbedder::onTransparentsRenderPass()
76+
{
77+
analytics::PerformanceCounter perfCounter("TransparentsRenderPass");
78+
constellation->onTransparentsRenderPass(perfCounter);
79+
perfCounter.end();
80+
}
81+
82+
void TrEmbedder::onAfterRendering()
83+
{
84+
constellation->onAfterRendering();
6385
}

src/runtime/embedder.hpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,40 @@ class TrEmbedder
104104

105105
public: // API for lifecycle
106106
/**
107-
* The lifecycle `onFrame` should be called when your application is to render a frame.
108-
*
109-
* @returns `true` if the frame is rendered, `false` otherwise.
107+
* The lifecycle `onBeforeRendering`.
108+
*
109+
* In this pass, JSAR internally prepares the rendering pass, such as collecting viewports, cameras, and other
110+
* rendering-related data.
111+
*/
112+
void onBeforeRendering();
113+
/**
114+
* The lifecycle `onOpaqueRenderPass`.
115+
*
116+
* The main render pass which renders all the opaque objects of the JSAR applications, and also schedules the draw
117+
* calls which should be rendered in the next passes, such as transparents, post-processing, and other render-texture
118+
* rendering.
119+
*
120+
* NOTE(yorkie): In tile-based architectural GPU, the multisampled framebuffer's buffers are bound to the on-tile
121+
* memory, so the multisampled framebuffer can't be switched in the middle of the rendering pass, for example, if you
122+
* render to a new framebuffer and then switch back the multisampled framebuffer, it will cause the buffers in the
123+
* multisampled framebuffer to be corrupted. To resolve this issue, JSAR will find the draw calls which render target
124+
* is not the current framebuffer, and schedule them to the `onAfterRenderPass` lifecycle, at which all the render
125+
* passes has been achieved and the multisampled framebuffer is also free to resolved.
126+
*/
127+
void onOpaquesRenderPass();
128+
/**
129+
* The lifecycle `onTransparentsRenderPass`.
130+
*
131+
* This pass executes the draw calls which enabled the blending, and uses the Weighted Blended OIT algorithm to
132+
* render all the transparents elements (e.g. glass, water and GUIs) without depth sorting.
133+
*/
134+
void onTransparentsRenderPass();
135+
/**
136+
* The lifecycle `onAfterRendering`.
137+
*
138+
* In this pass, it uses to achieve some post-processing effects or asynchronous rendering tasks.
110139
*/
111-
bool onFrame();
140+
void onAfterRendering();
112141
/**
113142
* The lifecycle `onEvent` should be called once an event is received from a specfic (content) source.
114143
*

src/runtime/unity_entry.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,40 @@ extern "C"
305305
TR_ENSURE_EMBEDDER(/** void */, { embedder->unload(); });
306306
}
307307

308+
enum UnityRenderPassEvent
309+
{
310+
kBeforeRenderingPass = 0,
311+
kOpaquesRenderPass = 1,
312+
kTransparentsRenderPass = 2,
313+
kPostProcessingRenderPass = 3,
314+
kAfterRenderingPass = 4,
315+
};
316+
308317
/**
309318
* The render event hook for the plugin, it will be called when the plugin is rendering.
310319
*/
311320
static void OnUnityRenderEvent(int eventID)
312321
{
313-
TR_ENSURE_EMBEDDER(/** void */, { embedder->onFrame(); });
322+
TR_ENSURE_EMBEDDER(/** void */, {
323+
switch ((UnityRenderPassEvent)eventID)
324+
{
325+
case kBeforeRenderingPass:
326+
embedder->onBeforeRendering();
327+
break;
328+
case kOpaquesRenderPass:
329+
embedder->onOpaquesRenderPass();
330+
break;
331+
case kTransparentsRenderPass:
332+
embedder->onTransparentsRenderPass();
333+
break;
334+
case kAfterRenderingPass:
335+
embedder->onAfterRendering();
336+
break;
337+
default:
338+
// TODO(yorkie): ignore the event.
339+
break;
340+
}
341+
});
314342
}
315343

316344
/**

0 commit comments

Comments
 (0)