While the renderer process runs, the adapter makes the client's render targets itself and the renderer process draws frames into them: texture targets are drawn with like any texture, and a pixmap target's frame is read back through the channel into its Pixmap (protocol version 4: BeginFrame names its target, CreateRenderTarget, Channel::readSize and the -read event). Frames into render targets are drawn even while the window is hidden. The client uses pixmap targets for snapshots (outfit pictures, photos), checked to look right. The render target interfaces are declared from IMVU's vtables. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
fa883892db
commit
6e4fe589e5
18 files changed
+523
-58
No files matched your search
+138
-19
@@ -102,12 +102,7 @@ bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||
switch (command->op()) {
|
||||
case Op::BeginFrame:
|
||||
if (const auto begin = command->body<protocol::BeginFrame>()) {
|
||||
onscreen_ = begin->offscreen == 0 && draw_;
|
||||
if (onscreen_) {
|
||||
ITextureView* target = swapChain_->GetCurrentBackBufferRTV();
|
||||
context_->SetRenderTargets(1, &target, swapChain_->GetDepthBufferDSV(),
|
||||
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
}
|
||||
beginFrame(*begin);
|
||||
}
|
||||
break;
|
||||
case Op::EndFrame:
|
||||
@@ -115,18 +110,17 @@ bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||
lastSentAt_ = end->sentAt;
|
||||
}
|
||||
drew = drew || onscreen_;
|
||||
onscreen_ = false;
|
||||
endFrame();
|
||||
break;
|
||||
case Op::Clear:
|
||||
if (const auto clear = command->body<protocol::Clear>(); clear && onscreen_) {
|
||||
if (const auto clear = command->body<protocol::Clear>(); clear && colorView_ != nullptr) {
|
||||
if ((clear->flags & 1) != 0) {
|
||||
float color[4];
|
||||
set_color(color, clear->color);
|
||||
context_->ClearRenderTarget(swapChain_->GetCurrentBackBufferRTV(), color,
|
||||
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
context_->ClearRenderTarget(colorView_, color, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
}
|
||||
if ((clear->flags & 2) != 0) {
|
||||
context_->ClearDepthStencil(swapChain_->GetDepthBufferDSV(), CLEAR_DEPTH_FLAG, 1.0f, 0,
|
||||
context_->ClearDepthStencil(depthView_, CLEAR_DEPTH_FLAG, 1.0f, 0,
|
||||
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
}
|
||||
}
|
||||
@@ -184,6 +178,11 @@ bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||
case Op::UpdateTexture:
|
||||
updateTexture(*command);
|
||||
break;
|
||||
case Op::CreateRenderTarget:
|
||||
if (const auto create = command->body<protocol::CreateRenderTarget>()) {
|
||||
createRenderTarget(*create);
|
||||
}
|
||||
break;
|
||||
case Op::DestroyTexture:
|
||||
if (const auto destroy = command->body<protocol::DestroyTexture>()) {
|
||||
textures_.erase(destroy->id);
|
||||
@@ -201,17 +200,17 @@ bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||
}
|
||||
break;
|
||||
case Op::DrawTriangles:
|
||||
if (const auto triangles = command->body<protocol::DrawTriangles>(); triangles && onscreen_) {
|
||||
if (const auto triangles = command->body<protocol::DrawTriangles>(); triangles && colorView_ != nullptr) {
|
||||
drawTriangles(*triangles);
|
||||
}
|
||||
break;
|
||||
case Op::DrawArrays:
|
||||
if (onscreen_) {
|
||||
if (colorView_ != nullptr) {
|
||||
drawArrays(*command);
|
||||
}
|
||||
break;
|
||||
case Op::DrawPointSprites:
|
||||
if (onscreen_) {
|
||||
if (colorView_ != nullptr) {
|
||||
drawPointSprites(*command);
|
||||
}
|
||||
break;
|
||||
@@ -232,19 +231,107 @@ void Scene::setProjection(const protocol::SetProjection& projection)
|
||||
}
|
||||
constants_.viewport[0] = static_cast<float>(projection.width);
|
||||
constants_.viewport[1] = static_cast<float>(projection.height);
|
||||
if (!onscreen_) {
|
||||
projectionWidth_ = projection.width;
|
||||
projectionHeight_ = projection.height;
|
||||
setViewport();
|
||||
}
|
||||
|
||||
void Scene::setViewport()
|
||||
{
|
||||
if (colorView_ == nullptr || projectionWidth_ <= 0 || projectionHeight_ <= 0) {
|
||||
return;
|
||||
}
|
||||
// The client's projection covers its whole 3D view; the window shows part of it, from offsetX.
|
||||
const SwapChainDesc& window = swapChain_->GetDesc();
|
||||
Viewport viewport;
|
||||
viewport.TopLeftX = static_cast<float>(-offsetX_);
|
||||
viewport.TopLeftX = onscreen_ ? static_cast<float>(-offsetX_) : 0.0f;
|
||||
viewport.TopLeftY = 0;
|
||||
viewport.Width = static_cast<float>(projection.width);
|
||||
viewport.Height = static_cast<float>(projection.height);
|
||||
viewport.Width = static_cast<float>(projectionWidth_);
|
||||
viewport.Height = static_cast<float>(projectionHeight_);
|
||||
viewport.MinDepth = 0;
|
||||
viewport.MaxDepth = 1;
|
||||
context_->SetViewports(1, &viewport, window.Width, window.Height);
|
||||
context_->SetViewports(1, &viewport, onscreen_ ? window.Width : target_->width,
|
||||
onscreen_ ? window.Height : target_->height);
|
||||
}
|
||||
|
||||
void Scene::beginFrame(const protocol::BeginFrame& begin)
|
||||
{
|
||||
onscreen_ = false;
|
||||
target_ = nullptr;
|
||||
colorView_ = nullptr;
|
||||
depthView_ = nullptr;
|
||||
readsBack_ = begin.readBack != 0;
|
||||
if (begin.target == 0) {
|
||||
if (draw_) {
|
||||
onscreen_ = true;
|
||||
colorView_ = swapChain_->GetCurrentBackBufferRTV();
|
||||
depthView_ = swapChain_->GetDepthBufferDSV();
|
||||
}
|
||||
} else if (const auto found = textures_.find(begin.target); found != textures_.end() && found->second.depth) {
|
||||
target_ = &found->second;
|
||||
colorView_ = target_->texture->GetDefaultView(TEXTURE_VIEW_RENDER_TARGET);
|
||||
depthView_ = target_->depth->GetDefaultView(TEXTURE_VIEW_DEPTH_STENCIL);
|
||||
}
|
||||
if (colorView_ != nullptr) {
|
||||
context_->SetRenderTargets(1, &colorView_, depthView_, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
setViewport();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::endFrame()
|
||||
{
|
||||
if (readsBack_ && readBack_) {
|
||||
readBack_(target_ != nullptr ? readTarget(*target_) : std::span<const std::byte>{});
|
||||
}
|
||||
if (target_ != nullptr) {
|
||||
// Unbound, so it can be drawn with (a texture can't be both).
|
||||
context_->SetRenderTargets(0, nullptr, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
}
|
||||
onscreen_ = false;
|
||||
target_ = nullptr;
|
||||
readsBack_ = false;
|
||||
colorView_ = nullptr;
|
||||
depthView_ = nullptr;
|
||||
}
|
||||
|
||||
std::span<const std::byte> Scene::readTarget(const Texture& target)
|
||||
{
|
||||
TextureDesc desc;
|
||||
desc.Name = "Render target read back";
|
||||
desc.Type = RESOURCE_DIM_TEX_2D;
|
||||
desc.Width = target.width;
|
||||
desc.Height = target.height;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = TEX_FORMAT_RGBA8_UNORM;
|
||||
desc.Usage = USAGE_STAGING;
|
||||
desc.CPUAccessFlags = CPU_ACCESS_READ;
|
||||
RefCntAutoPtr<ITexture> staging;
|
||||
device_->CreateTexture(desc, nullptr, &staging);
|
||||
if (!staging) {
|
||||
return {};
|
||||
}
|
||||
CopyTextureAttribs copy(target.texture, RESOURCE_STATE_TRANSITION_MODE_TRANSITION, staging,
|
||||
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||
context_->CopyTexture(copy);
|
||||
MappedTextureSubresource mapped;
|
||||
context_->MapTextureSubresource(staging, 0, 0, MAP_READ, MAP_FLAG_NONE, nullptr, mapped); // waits for the GPU
|
||||
if (mapped.pData == nullptr) {
|
||||
return {};
|
||||
}
|
||||
// RGBA in memory, to the client's 0xAARRGGBB (BGRA in memory).
|
||||
readPixels_.resize(std::size_t{target.width} * target.height * 4);
|
||||
for (std::uint32_t y = 0; y < target.height; ++y) {
|
||||
const auto* from = static_cast<const std::uint8_t*>(mapped.pData) + y * mapped.Stride;
|
||||
auto* to = reinterpret_cast<std::uint8_t*>(readPixels_.data()) + std::size_t{y} * target.width * 4;
|
||||
for (std::uint32_t x = 0; x < target.width; ++x) {
|
||||
to[x * 4 + 0] = from[x * 4 + 2];
|
||||
to[x * 4 + 1] = from[x * 4 + 1];
|
||||
to[x * 4 + 2] = from[x * 4 + 0];
|
||||
to[x * 4 + 3] = from[x * 4 + 3];
|
||||
}
|
||||
}
|
||||
context_->UnmapTextureSubresource(staging, 0, 0);
|
||||
return readPixels_;
|
||||
}
|
||||
|
||||
void Scene::setState(const protocol::SetState& state)
|
||||
@@ -322,6 +409,38 @@ void Scene::createTexture(const protocol::Command& command)
|
||||
textures_[create->id] = std::move(texture);
|
||||
}
|
||||
|
||||
void Scene::createRenderTarget(const protocol::CreateRenderTarget& create)
|
||||
{
|
||||
if (create.width == 0 || create.height == 0) {
|
||||
return;
|
||||
}
|
||||
// The window's formats, so the same pipelines draw into both.
|
||||
const SwapChainDesc& window = swapChain_->GetDesc();
|
||||
TextureDesc desc;
|
||||
desc.Name = "Client render target";
|
||||
desc.Type = RESOURCE_DIM_TEX_2D;
|
||||
desc.Width = create.width;
|
||||
desc.Height = create.height;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = window.ColorBufferFormat;
|
||||
desc.BindFlags = BIND_RENDER_TARGET | BIND_SHADER_RESOURCE;
|
||||
desc.ClearValue.Format = desc.Format;
|
||||
Texture target{.width = create.width, .height = create.height, .flags = 0};
|
||||
device_->CreateTexture(desc, nullptr, &target.texture);
|
||||
TextureDesc depth = desc;
|
||||
depth.Name = "Client render target depth";
|
||||
depth.Format = window.DepthBufferFormat;
|
||||
depth.BindFlags = BIND_DEPTH_STENCIL;
|
||||
depth.ClearValue.Format = depth.Format;
|
||||
depth.ClearValue.DepthStencil.Depth = 1;
|
||||
device_->CreateTexture(depth, nullptr, &target.depth);
|
||||
if (!target.texture || !target.depth) {
|
||||
return;
|
||||
}
|
||||
target.texture->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE)->SetSampler(samplers_[0]);
|
||||
textures_[create.id] = std::move(target);
|
||||
}
|
||||
|
||||
void Scene::updateTexture(const protocol::Command& command)
|
||||
{
|
||||
const auto update = command.body<protocol::UpdateTexture>();
|
||||
|
||||
Reference in new issue
Block a user