The full view is now the default. While the renderer process covers the whole view (Channel::covering, set after each frame it presents), the adapter skips the client's renderer's clears and draws, decided per frame; the client's renderer still gets state and resources, and draws again as soon as the renderer process stops covering the view. The client's window no longer gets WS_CLIPCHILDREN: the client draws when its window gets WM_PAINT, which a window entirely covered by clipped children never gets (it fell to about one frame a second). The flip model swap chain shows above the client's drawing anyway. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
64e152de3c
commit
2d9da249fa
9 files changed
+74
-27
No files matched your search
+22
-5
@@ -47,7 +47,9 @@ void Engine::enable2dFrameBuffer() { inner_->enable2dFrameBuffer(); }
|
|||||||
|
|
||||||
void Engine::clear(std::uint32_t flags, imvu::Color color)
|
void Engine::clear(std::uint32_t flags, imvu::Color color)
|
||||||
{
|
{
|
||||||
inner_->clear(flags, color);
|
if (innerDraws()) {
|
||||||
|
inner_->clear(flags, color);
|
||||||
|
}
|
||||||
recorder_->add(Op::Clear, protocol::Clear{flags, color});
|
recorder_->add(Op::Clear, protocol::Clear{flags, color});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +244,13 @@ imvu::SharedPtr<imvu::PiTextureRenderTarget> Engine::newTextureRenderTarget(imvu
|
|||||||
void Engine::beginRendering(imvu::PiRenderTarget* target)
|
void Engine::beginRendering(imvu::PiRenderTarget* target)
|
||||||
{
|
{
|
||||||
inner_->beginRendering(target);
|
inner_->beginRendering(target);
|
||||||
|
// Decided per frame: frames into render targets, and frames while the renderer process doesn't cover the view, are
|
||||||
|
// drawn by the client's renderer.
|
||||||
|
const bool takenOver = target == nullptr && recorder_->covering();
|
||||||
|
if (takenOver != takenOver_ && target == nullptr) {
|
||||||
|
log::info("engine: frame {}: {}", frames_, takenOver ? "drawn by the renderer process" : "drawn by the client's renderer");
|
||||||
|
}
|
||||||
|
takenOver_ = takenOver;
|
||||||
if (target != nullptr && foreignLogged_ < 20) {
|
if (target != nullptr && foreignLogged_ < 20) {
|
||||||
++foreignLogged_;
|
++foreignLogged_;
|
||||||
log::info("engine: frame {}: drawing into a render target ({})", frames_, static_cast<void*>(target));
|
log::info("engine: frame {}: drawing into a render target ({})", frames_, static_cast<void*>(target));
|
||||||
@@ -271,7 +280,9 @@ void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInterna
|
|||||||
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
|
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
|
||||||
imvu::PiInternalVertexBuffer* colors)
|
imvu::PiInternalVertexBuffer* colors)
|
||||||
{
|
{
|
||||||
inner_->drawTriangles(inner(indices), inner(positions), inner(normals), inner(texCoords), inner(colors));
|
if (innerDraws()) {
|
||||||
|
inner_->drawTriangles(inner(indices), inner(positions), inner(normals), inner(texCoords), inner(colors));
|
||||||
|
}
|
||||||
++(id(indices) != 0 && id(positions) != 0 ? stats_.triangles : stats_.foreignTriangles);
|
++(id(indices) != 0 && id(positions) != 0 ? stats_.triangles : stats_.foreignTriangles);
|
||||||
recorder_->add(Op::DrawTriangles,
|
recorder_->add(Op::DrawTriangles,
|
||||||
protocol::DrawTriangles{id(indices), id(positions), id(normals), id(texCoords), id(colors)});
|
protocol::DrawTriangles{id(indices), id(positions), id(normals), id(texCoords), id(colors)});
|
||||||
@@ -279,13 +290,17 @@ void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInterna
|
|||||||
|
|
||||||
void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color)
|
void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color)
|
||||||
{
|
{
|
||||||
inner_->drawLine(from, to, color);
|
if (innerDraws()) {
|
||||||
|
inner_->drawLine(from, to, color);
|
||||||
|
}
|
||||||
++stats_.lines;
|
++stats_.lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites)
|
void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites)
|
||||||
{
|
{
|
||||||
inner_->drawPointSprites(sprites);
|
if (innerDraws()) {
|
||||||
|
inner_->drawPointSprites(sprites);
|
||||||
|
}
|
||||||
++stats_.sprites;
|
++stats_.sprites;
|
||||||
if (sprites.first == nullptr || sprites.last <= sprites.first) {
|
if (sprites.first == nullptr || sprites.last <= sprites.first) {
|
||||||
return;
|
return;
|
||||||
@@ -304,7 +319,9 @@ void Engine::drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t in
|
|||||||
const imvu::VertexAttributeArray<imvu::Vec2f>& texCoords,
|
const imvu::VertexAttributeArray<imvu::Vec2f>& texCoords,
|
||||||
const imvu::VertexAttributeArray<imvu::Color>& colors)
|
const imvu::VertexAttributeArray<imvu::Color>& colors)
|
||||||
{
|
{
|
||||||
inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors);
|
if (innerDraws()) {
|
||||||
|
inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors);
|
||||||
|
}
|
||||||
++(indices != nullptr ? stats_.indexed : stats_.unindexed);
|
++(indices != nullptr ? stats_.indexed : stats_.unindexed);
|
||||||
if (positions.data == nullptr || vertexCount == 0) {
|
if (positions.data == nullptr || vertexCount == 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+9
-3
@@ -1,7 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// The adapter's engine: what the client draws with. Everything goes to the client's own Direct3D renderer, which
|
// The adapter's engine: what the client draws with. Everything is recorded for the renderer process, which draws the
|
||||||
// still draws the whole 3D view, and is recorded for the renderer process, which draws its part on top (the right
|
// 3D view. The client's own Direct3D renderer gets everything too (state and resources: it still draws into render
|
||||||
// half for now, to compare). If the renderer process fails, the client's renderer carries on alone.
|
// targets and reads textures back, which the renderer process doesn't yet), but while the renderer process covers the
|
||||||
|
// whole view, the client's renderer skips its frames' clears and draws. When the renderer process doesn't cover it
|
||||||
|
// (it failed, or shows only part of the view, for comparing) the client's renderer draws everything, from the next
|
||||||
|
// frame on.
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -63,10 +66,13 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void setState(protocol::State state, std::uint32_t value);
|
void setState(protocol::State state, std::uint32_t value);
|
||||||
|
// Whether the client's renderer should draw this frame's clears and draws.
|
||||||
|
[[nodiscard]] bool innerDraws() const { return !takenOver_; }
|
||||||
|
|
||||||
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
|
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
|
||||||
std::shared_ptr<Recorder> recorder_;
|
std::shared_ptr<Recorder> recorder_;
|
||||||
std::uint64_t frames_ = 0;
|
std::uint64_t frames_ = 0;
|
||||||
|
bool takenOver_ = false; // this frame is drawn by the renderer process only
|
||||||
int foreignLogged_ = 0; // diagnostics: textures the client renderer made, render targets
|
int foreignLogged_ = 0; // diagnostics: textures the client renderer made, render targets
|
||||||
|
|
||||||
// Diagnostics: what the client draws, summed over 300 frames and logged (the first few times).
|
// Diagnostics: what the client draws, summed over 300 frames and logged (the first few times).
|
||||||
|
|||||||
+3
-3
@@ -33,7 +33,7 @@ imvu::SharedPtr<imvu::PiRenderEngine> create_imvu_engine(imvu::SharedPtr<imvu::I
|
|||||||
return create(std::move(window), imvu::RendererType::Direct3D);
|
return create(std::move(window), imvu::RendererType::Direct3D);
|
||||||
}
|
}
|
||||||
|
|
||||||
// What the renderer process draws: T5DE_RENDERER_VIEW=off, split (the default: the right half), full or compare.
|
// What the renderer process draws: T5DE_RENDERER_VIEW=off, split (the right half), full (the default) or compare.
|
||||||
// (The renderer process can also be switched while it runs: renderer/main.cpp.)
|
// (The renderer process can also be switched while it runs: renderer/main.cpp.)
|
||||||
protocol::View view()
|
protocol::View view()
|
||||||
{
|
{
|
||||||
@@ -41,9 +41,9 @@ protocol::View view()
|
|||||||
GetEnvironmentVariableW(L"T5DE_RENDERER_VIEW", value, 16);
|
GetEnvironmentVariableW(L"T5DE_RENDERER_VIEW", value, 16);
|
||||||
const std::wstring_view setting = value;
|
const std::wstring_view setting = value;
|
||||||
return setting == L"off" ? protocol::View::Off
|
return setting == L"off" ? protocol::View::Off
|
||||||
: setting == L"full" ? protocol::View::Full
|
: setting == L"split" ? protocol::View::Split
|
||||||
: setting == L"compare" ? protocol::View::Compare
|
: setting == L"compare" ? protocol::View::Compare
|
||||||
: protocol::View::Split;
|
: protocol::View::Full;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] bool active() const { return process_ && process_->alive(); }
|
[[nodiscard]] bool active() const { return process_ && process_->alive(); }
|
||||||
|
|
||||||
|
// Whether the renderer process draws the whole 3D view, so the client's renderer needn't.
|
||||||
|
[[nodiscard]] bool covering() const { return process_ && process_->covering(); }
|
||||||
|
|
||||||
// Records a command (if active); returns its payload to fill, or null.
|
// Records a command (if active); returns its payload to fill, or null.
|
||||||
template <class Body>
|
template <class Body>
|
||||||
std::byte* add(protocol::Op op, const Body& body, std::size_t payloadSize = 0)
|
std::byte* add(protocol::Op op, const Body& body, std::size_t payloadSize = 0)
|
||||||
|
|||||||
@@ -84,11 +84,14 @@ std::unique_ptr<RendererProcess> RendererProcess::start(HWND window, protocol::V
|
|||||||
.view = view,
|
.view = view,
|
||||||
.capacity = protocol::channelCapacity,
|
.capacity = protocol::channelCapacity,
|
||||||
.chunkSize = 0,
|
.chunkSize = 0,
|
||||||
.chunkFlags = 0};
|
.chunkFlags = 0,
|
||||||
|
.covering = 0,
|
||||||
// The renderer's window goes on top of the client's 3D view: keep the client's drawing out of it.
|
.reserved = 0};
|
||||||
SetWindowLongW(window, GWL_STYLE, GetWindowLongW(window, GWL_STYLE) | WS_CLIPCHILDREN);
|
|
||||||
|
|
||||||
|
// The renderer's window goes on top of the client's 3D view, as a child window. The client's window mustn't clip
|
||||||
|
// its children (WS_CLIPCHILDREN): the client draws a frame when its window gets WM_PAINT, which a window entirely
|
||||||
|
// covered by clipped children never gets (it then drew about once a second). The renderer's swap chain is a flip
|
||||||
|
// model one, which the compositor shows above the client's drawing anyway.
|
||||||
const auto exe = running_copy(module_folder() / L"t5de-renderer.exe");
|
const auto exe = running_copy(module_folder() / L"t5de-renderer.exe");
|
||||||
std::wstring command = std::format(L"\"{}\" --channel {} --parent {}", exe.wstring(), name, GetCurrentProcessId());
|
std::wstring command = std::format(L"\"{}\" --channel {} --parent {}", exe.wstring(), name, GetCurrentProcessId());
|
||||||
STARTUPINFOW startup{.cb = sizeof(STARTUPINFOW)};
|
STARTUPINFOW startup{.cb = sizeof(STARTUPINFOW)};
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] bool alive() const { return alive_; }
|
[[nodiscard]] bool alive() const { return alive_; }
|
||||||
|
|
||||||
|
// Whether the renderer process covers the whole 3D view (protocol::Channel::covering).
|
||||||
|
[[nodiscard]] bool covering() const { return alive_ && channel_->covering != 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RendererProcess() = default;
|
RendererProcess() = default;
|
||||||
|
|
||||||
|
|||||||
+15
-9
@@ -29,19 +29,25 @@ creates (each 3D view, and again on room changes), the adapter:
|
|||||||
|
|
||||||
The renderer process draws into a child window it creates inside the client's window: window handles are valid in
|
The renderer process draws into a child window it creates inside the client's window: window handles are valid in
|
||||||
every process, and Windows allows child windows across processes (Chrome's GPU process works this way). The child
|
every process, and Windows allows child windows across processes (Chrome's GPU process works this way). The child
|
||||||
is disabled so mouse input goes to the client's window under it, and the adapter gives the client's window
|
is disabled so mouse input goes to the client's window under it. Its swap chain is a flip model one, which the
|
||||||
`WS_CLIPCHILDREN` so the client's own drawing stays out of it. The process exits when the engine is destroyed or the
|
compositor shows above the client's own drawing. The client's window must not clip its children (`WS_CLIPCHILDREN`):
|
||||||
client exits.
|
the client draws a frame when its window gets `WM_PAINT` (`SceneWindow.dll` invalidates it, and draws on the paint),
|
||||||
|
and a window entirely covered by clipped children never gets one, so the client fell to about one frame a second.
|
||||||
|
The process exits when the engine is destroyed or the client exits.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
The renderer process draws the client's whole scene, over the right half of the 3D view, beside the client's own
|
The renderer process draws the whole 3D view in place of the client's renderer (the full view, the default). The
|
||||||
Direct3D renderer on the left (the adapter still forwards everything to it). Checked in rooms with screenshots of
|
adapter still forwards everything to the client's Direct3D renderer (state and resources: it draws render targets and
|
||||||
both renderers side by side (the compare view): they match, particles and the 2D overlay (name tags) included.
|
reads textures back, which the renderer process doesn't yet), but while the renderer process covers the whole view
|
||||||
|
(`Channel::covering`, set after each frame it presents) the client's renderer skips its clears and draws, decided per
|
||||||
|
frame; it draws again as soon as the renderer process stops covering the view (the view changes, or the process
|
||||||
|
fails). Checked in rooms with screenshots of both renderers side by side (the compare view): they match, particles
|
||||||
|
and the 2D overlay (name tags) included.
|
||||||
|
|
||||||
For comparing while the client runs (development):
|
For comparing while the client runs (development):
|
||||||
|
|
||||||
- Views: `T5DE_RENDERER_VIEW` (off, split, full or compare) when the client starts, or
|
- Views: `T5DE_RENDERER_VIEW` (off, split, full (the default) or compare) when the client starts, or
|
||||||
`%TEMP%\t5de-renderer-view.txt` holding one of those at any time. Compare draws the left half's scene over the
|
`%TEMP%\t5de-renderer-view.txt` holding one of those at any time. Compare draws the left half's scene over the
|
||||||
right half, so both renderers show the same thing side by side.
|
right half, so both renderers show the same thing side by side.
|
||||||
- `%TEMP%\t5de-renderer-shaders\fixed_function.hlsl`, if present, replaces the built-in shaders and is reloaded
|
- `%TEMP%\t5de-renderer-shaders\fixed_function.hlsl`, if present, replaces the built-in shaders and is reloaded
|
||||||
@@ -63,8 +69,8 @@ For comparing while the client runs (development):
|
|||||||
the right half visibly trails, mostly from compositing another process's window over Remote Desktop. The
|
the right half visibly trails, mostly from compositing another process's window over Remote Desktop. The
|
||||||
renderer process draws only the newest frame when it falls behind, and keeps at most one frame queued.
|
renderer process draws only the newest frame when it falls behind, and keeps at most one frame queued.
|
||||||
|
|
||||||
Next: render targets (offscreen frames are skipped), the renderer process drawing the whole view in place of the
|
Next: render targets (offscreen frames are skipped, and drawn by the client's renderer), and one renderer process for
|
||||||
client's renderer, and one renderer process for all of a client's views.
|
all of a client's views.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
|
|
||||||
namespace t5de::protocol {
|
namespace t5de::protocol {
|
||||||
|
|
||||||
inline constexpr std::uint32_t version = 2;
|
inline constexpr std::uint32_t version = 3;
|
||||||
|
|
||||||
// Which part of the client's 3D view the renderer process draws (the client's own renderer draws all of it, under).
|
// Which part of the client's 3D view the renderer process draws. Full is the default; the others are for comparing
|
||||||
|
// with the client's renderer, which then draws the whole view under the renderer process's window.
|
||||||
enum class View : std::uint32_t {
|
enum class View : std::uint32_t {
|
||||||
Off, // none
|
Off, // none
|
||||||
Split, // the right half, to compare with the client's renderer on the left
|
Split, // the right half, to compare with the client's renderer on the left
|
||||||
@@ -30,9 +31,13 @@ struct Channel {
|
|||||||
std::uint32_t capacity;
|
std::uint32_t capacity;
|
||||||
std::uint32_t chunkSize;
|
std::uint32_t chunkSize;
|
||||||
std::uint32_t chunkFlags; // chunkEndsFrame
|
std::uint32_t chunkFlags; // chunkEndsFrame
|
||||||
|
// Set by the renderer process while its window covers the whole 3D view and it's presenting frames: the client's
|
||||||
|
// renderer then needn't draw (the adapter skips its draws; it draws again as soon as this is cleared).
|
||||||
|
std::uint32_t covering;
|
||||||
|
std::uint32_t reserved;
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(Channel) == 40, "same layout in 32-bit and 64-bit builds");
|
static_assert(sizeof(Channel) == 48, "same layout in 32-bit and 64-bit builds");
|
||||||
|
|
||||||
inline constexpr std::uint32_t chunkEndsFrame = 1;
|
inline constexpr std::uint32_t chunkEndsFrame = 1;
|
||||||
inline constexpr std::uint32_t channelCapacity = 16 * 1024 * 1024;
|
inline constexpr std::uint32_t channelCapacity = 16 * 1024 * 1024;
|
||||||
|
|||||||
@@ -290,14 +290,17 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
|||||||
info("drawing {}x{} at {} in the client's 3D view, its scene from {}", width, height, place.left, offset);
|
info("drawing {}x{} at {} in the client's 3D view, its scene from {}", width, height, place.left, offset);
|
||||||
} else {
|
} else {
|
||||||
ShowWindow(window, SW_HIDE);
|
ShowWindow(window, SW_HIDE);
|
||||||
|
channel->covering = 0;
|
||||||
info("hidden");
|
info("hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reload_shaders(scene);
|
reload_shaders(scene);
|
||||||
LARGE_INTEGER start, end;
|
LARGE_INTEGER start, end;
|
||||||
QueryPerformanceCounter(&start);
|
QueryPerformanceCounter(&start);
|
||||||
|
const bool covers = current_view(channel->view) == View::Full && width > 0 && height > 0;
|
||||||
if (scene.execute(std::span(frame).first(complete), offset, width > 0 && height > 0)) {
|
if (scene.execute(std::span(frame).first(complete), offset, width > 0 && height > 0)) {
|
||||||
swapChain->Present(0);
|
swapChain->Present(0);
|
||||||
|
channel->covering = covers ? 1 : 0; // the client's renderer can stop drawing (or must start again)
|
||||||
QueryPerformanceCounter(&end);
|
QueryPerformanceCounter(&end);
|
||||||
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
|
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
|
||||||
latency += static_cast<double>(end.QuadPart - static_cast<LONGLONG>(scene.lastSentAt())) /
|
latency += static_cast<double>(end.QuadPart - static_cast<LONGLONG>(scene.lastSentAt())) /
|
||||||
@@ -315,6 +318,7 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
|||||||
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel->covering = 0;
|
||||||
info("exiting");
|
info("exiting");
|
||||||
DestroyWindow(window);
|
DestroyWindow(window);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in new issue
Block a user