CI / build (push) Failing after 0s
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]>
373 lines
13 KiB
C++
373 lines
13 KiB
C++
#include "engine.hpp"
|
|
|
|
#include <windows.h>
|
|
|
|
#include <bit>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
#include "log.hpp"
|
|
#include "resources.hpp"
|
|
|
|
namespace t5de {
|
|
|
|
using protocol::Op;
|
|
using protocol::State;
|
|
|
|
namespace {
|
|
|
|
protocol::SetTransform transform_command(const imvu::Transform& t)
|
|
{
|
|
protocol::SetTransform command{};
|
|
std::memcpy(command.m, &t.linear[0][0], sizeof(float) * 9);
|
|
command.m[9] = t.translation.x;
|
|
command.m[10] = t.translation.y;
|
|
command.m[11] = t.translation.z;
|
|
return command;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Engine::Engine(imvu::SharedPtr<imvu::PiRenderEngine> inner, std::shared_ptr<Recorder> recorder)
|
|
: inner_(std::move(inner)), recorder_(std::move(recorder))
|
|
{
|
|
}
|
|
|
|
Engine::~Engine() { log::info("engine: destroyed after {} frames", frames_); }
|
|
|
|
void Engine::setState(State state, std::uint32_t value)
|
|
{
|
|
recorder_->add(Op::SetState, protocol::SetState{state, value});
|
|
}
|
|
|
|
void Engine::resizeFrameBuffer(imvu::Vec2i size) { inner_->resizeFrameBuffer(size); }
|
|
|
|
void Engine::enable2dFrameBuffer() { inner_->enable2dFrameBuffer(); }
|
|
|
|
void Engine::clear(std::uint32_t flags, imvu::Color color)
|
|
{
|
|
if (innerDraws()) {
|
|
inner_->clear(flags, color);
|
|
}
|
|
recorder_->add(Op::Clear, protocol::Clear{flags, color});
|
|
}
|
|
|
|
void Engine::setProjection(imvu::Vec2i viewport, const imvu::Mat4f& projection)
|
|
{
|
|
inner_->setProjection(viewport, projection);
|
|
if (projection.m[2][3] == 0.0f) {
|
|
++stats_.flat;
|
|
}
|
|
protocol::SetProjection command{viewport.x, viewport.y, {}};
|
|
std::memcpy(command.m, &projection.m[0][0], sizeof(command.m));
|
|
recorder_->add(Op::SetProjection, command);
|
|
}
|
|
|
|
void Engine::setModelSpace(const imvu::Transform& model)
|
|
{
|
|
inner_->setModelSpace(model);
|
|
recorder_->add(Op::SetModel, transform_command(model));
|
|
}
|
|
|
|
void Engine::setViewSpace(const imvu::Transform& view)
|
|
{
|
|
inner_->setViewSpace(view);
|
|
recorder_->add(Op::SetView, transform_command(view));
|
|
}
|
|
|
|
void Engine::setTextures(imvu::PiRenderTexture* texture0, imvu::PiRenderTexture* texture1)
|
|
{
|
|
inner_->setTextures(inner(texture0), inner(texture1));
|
|
for (imvu::PiRenderTexture* texture : {texture0, texture1}) {
|
|
if (texture != nullptr && id(texture) == 0 && foreignLogged_ < 20) {
|
|
++foreignLogged_;
|
|
log::info("engine: frame {}: a texture the client renderer made ({})", frames_, static_cast<void*>(texture));
|
|
}
|
|
}
|
|
recorder_->add(Op::SetTextures, protocol::SetTextures{id(texture0), id(texture1)});
|
|
}
|
|
|
|
void Engine::setDepthTestingOn(bool on)
|
|
{
|
|
inner_->setDepthTestingOn(on);
|
|
setState(State::DepthTest, on);
|
|
}
|
|
|
|
void Engine::setDepthWritingOn(bool on)
|
|
{
|
|
inner_->setDepthWritingOn(on);
|
|
setState(State::DepthWrite, on);
|
|
}
|
|
|
|
void Engine::setBlendMode(imvu::RenderBlendMode mode)
|
|
{
|
|
inner_->setBlendMode(mode);
|
|
setState(State::BlendMode, static_cast<std::uint32_t>(mode));
|
|
}
|
|
|
|
void Engine::setAlphaTestingOn(bool on)
|
|
{
|
|
inner_->setAlphaTestingOn(on);
|
|
setState(State::AlphaTest, on);
|
|
}
|
|
|
|
void Engine::setAlphaTestValue(float value)
|
|
{
|
|
inner_->setAlphaTestValue(value);
|
|
setState(State::AlphaReference, static_cast<std::uint32_t>(std::lround(value * 255.0f)));
|
|
}
|
|
|
|
void Engine::setBlendingOn(bool on)
|
|
{
|
|
inner_->setBlendingOn(on);
|
|
setState(State::Blending, on);
|
|
}
|
|
|
|
void Engine::setBackFaceCullingOn(bool on)
|
|
{
|
|
inner_->setBackFaceCullingOn(on);
|
|
setState(State::Culling, on);
|
|
}
|
|
|
|
void Engine::setNormalInvertingOn(bool on)
|
|
{
|
|
inner_->setNormalInvertingOn(on);
|
|
setState(State::NormalInverting, on);
|
|
}
|
|
|
|
void Engine::setLightingOn(bool on)
|
|
{
|
|
inner_->setLightingOn(on);
|
|
setState(State::Lighting, on);
|
|
}
|
|
|
|
void Engine::setLightProperties(std::uint32_t index, const imvu::LightProperties& light)
|
|
{
|
|
inner_->setLightProperties(index, light);
|
|
protocol::SetLight command{index, light.enabled, light.diffuse,
|
|
light.type == imvu::LightType::Directional,
|
|
{light.vector.x, light.vector.y, light.vector.z}};
|
|
// IMVU's Direct3D renderer shines a directional light along -vector, along -x for a zero vector.
|
|
if (command.directional && light.vector.x == 0 && light.vector.y == 0 && light.vector.z == 0) {
|
|
command.vector[0] = 1;
|
|
}
|
|
recorder_->add(Op::SetLight, command);
|
|
}
|
|
|
|
void Engine::setFogOn(bool on)
|
|
{
|
|
inner_->setFogOn(on);
|
|
setState(State::Fog, on);
|
|
}
|
|
|
|
void Engine::setFogAttributes(float start, float end, imvu::Color color)
|
|
{
|
|
inner_->setFogAttributes(start, end, color);
|
|
recorder_->add(Op::SetFog, protocol::SetFog{start, end, color});
|
|
}
|
|
|
|
void Engine::setTextureTransformMatrixIdentity(std::uint32_t stage)
|
|
{
|
|
inner_->setTextureTransformMatrixIdentity(stage);
|
|
recorder_->add(Op::SetTextureTransform, protocol::SetTextureTransform{stage, 1, {1, 0, 0, 1, 0, 0}});
|
|
}
|
|
|
|
void Engine::setTextureTransformMatrix(std::uint32_t stage, const imvu::TextureTransform& transform)
|
|
{
|
|
inner_->setTextureTransformMatrix(stage, transform);
|
|
protocol::SetTextureTransform command{stage, 0, {}};
|
|
std::memcpy(command.m, transform.m, sizeof(command.m));
|
|
recorder_->add(Op::SetTextureTransform, command);
|
|
}
|
|
|
|
void Engine::setEnvironmentAmbient(imvu::Color color)
|
|
{
|
|
inner_->setEnvironmentAmbient(color);
|
|
setState(State::Ambient, color);
|
|
}
|
|
|
|
imvu::PiRenderTexture* Engine::newRenderTexture(const imvu::Pixmap& pixmap, std::uint32_t flags)
|
|
{
|
|
imvu::PiRenderTexture* texture = inner_->newRenderTexture(pixmap, flags);
|
|
if (texture == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return new Texture(texture, recorder_, recorder_->createTexture(pixmap, flags));
|
|
}
|
|
|
|
imvu::PiInternalIndexBuffer* Engine::newIndexBuffer(std::uint32_t count, const std::uint16_t* indices)
|
|
{
|
|
imvu::PiInternalIndexBuffer* buffer = inner_->newIndexBuffer(count, indices);
|
|
if (buffer == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return new IndexBuffer(buffer, recorder_,
|
|
recorder_->createBuffer(protocol::BufferKind::Index, indices, 2, 2, count));
|
|
}
|
|
|
|
imvu::PiInternalVertexBuffer* Engine::newStaticVertexBuffer(const void* data, std::uint32_t stride,
|
|
std::uint32_t elementSize, std::uint32_t elementCount)
|
|
{
|
|
imvu::PiInternalVertexBuffer* buffer = inner_->newStaticVertexBuffer(data, stride, elementSize, elementCount);
|
|
if (buffer == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return new VertexBuffer(
|
|
buffer, recorder_,
|
|
recorder_->createBuffer(protocol::BufferKind::Vertex, data, stride, elementSize, elementCount));
|
|
}
|
|
|
|
imvu::PiInternalVertexBuffer* Engine::newDynamicVertexBuffer(std::uint32_t elementSize, std::uint32_t elementCount)
|
|
{
|
|
imvu::PiInternalVertexBuffer* buffer = inner_->newDynamicVertexBuffer(elementSize, elementCount);
|
|
if (buffer == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return new VertexBuffer(
|
|
buffer, recorder_,
|
|
recorder_->createBuffer(protocol::BufferKind::Vertex, nullptr, 0, elementSize, elementCount));
|
|
}
|
|
|
|
imvu::SharedPtr<imvu::PiPixmapRenderTarget> Engine::newPixmapRenderTarget(imvu::Vec2i size)
|
|
{
|
|
log::info("engine: frame {}: a {}x{} pixmap render target", frames_, size.x, size.y);
|
|
return inner_->newPixmapRenderTarget(size);
|
|
}
|
|
|
|
imvu::SharedPtr<imvu::PiTextureRenderTarget> Engine::newTextureRenderTarget(imvu::Vec2i size)
|
|
{
|
|
log::info("engine: frame {}: a {}x{} texture render target", frames_, size.x, size.y);
|
|
return inner_->newTextureRenderTarget(size);
|
|
}
|
|
|
|
void Engine::beginRendering(imvu::PiRenderTarget* 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) {
|
|
++foreignLogged_;
|
|
log::info("engine: frame {}: drawing into a render target ({})", frames_, static_cast<void*>(target));
|
|
}
|
|
recorder_->add(Op::BeginFrame, protocol::BeginFrame{target != nullptr});
|
|
}
|
|
|
|
void Engine::endRendering()
|
|
{
|
|
inner_->endRendering();
|
|
LARGE_INTEGER now;
|
|
QueryPerformanceCounter(&now);
|
|
recorder_->add(Op::EndFrame, protocol::EndFrame{static_cast<std::uint64_t>(now.QuadPart)});
|
|
recorder_->flush(true);
|
|
++frames_;
|
|
if (frames_ % 300 == 0 && statsLogged_ < 6) {
|
|
++statsLogged_;
|
|
log::info("engine: last 300 frames: drawTriangles {} (and {} with buffers not ours), drawTrianglesFromMemory {} "
|
|
"(and {} without indices), point sprites {}, lines {}, 2D projections {}",
|
|
stats_.triangles, stats_.foreignTriangles, stats_.indexed, stats_.unindexed, stats_.sprites, stats_.lines,
|
|
stats_.flat);
|
|
stats_ = {};
|
|
}
|
|
}
|
|
|
|
void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions,
|
|
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
|
|
imvu::PiInternalVertexBuffer* 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);
|
|
recorder_->add(Op::DrawTriangles,
|
|
protocol::DrawTriangles{id(indices), id(positions), id(normals), id(texCoords), id(colors)});
|
|
}
|
|
|
|
void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color)
|
|
{
|
|
if (innerDraws()) {
|
|
inner_->drawLine(from, to, color);
|
|
}
|
|
++stats_.lines;
|
|
}
|
|
|
|
void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites)
|
|
{
|
|
if (innerDraws()) {
|
|
inner_->drawPointSprites(sprites);
|
|
}
|
|
++stats_.sprites;
|
|
if (sprites.first == nullptr || sprites.last <= sprites.first) {
|
|
return;
|
|
}
|
|
const auto count = static_cast<std::uint32_t>(sprites.last - sprites.first);
|
|
if (std::byte* payload = recorder_->add(Op::DrawPointSprites, protocol::DrawPointSprites{count},
|
|
std::size_t{count} * sizeof(imvu::PointSprite))) {
|
|
std::memcpy(payload, sprites.first, std::size_t{count} * sizeof(imvu::PointSprite));
|
|
}
|
|
}
|
|
|
|
void Engine::drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t indexCount,
|
|
const std::uint16_t* indices,
|
|
const imvu::VertexAttributeArray<imvu::Vec3f>& positions,
|
|
const imvu::VertexAttributeArray<imvu::Vec3f>& normals,
|
|
const imvu::VertexAttributeArray<imvu::Vec2f>& texCoords,
|
|
const imvu::VertexAttributeArray<imvu::Color>& colors)
|
|
{
|
|
if (innerDraws()) {
|
|
inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors);
|
|
}
|
|
++(indices != nullptr ? stats_.indexed : stats_.unindexed);
|
|
if (positions.data == nullptr || vertexCount == 0) {
|
|
return;
|
|
}
|
|
// Without indices: the vertices in order, as a triangle list.
|
|
std::vector<std::uint16_t> sequence;
|
|
if (indices == nullptr) {
|
|
if (vertexCount > 0xffff) {
|
|
return;
|
|
}
|
|
sequence.resize(vertexCount - vertexCount % 3);
|
|
for (std::size_t i = 0; i < sequence.size(); ++i) {
|
|
sequence[i] = static_cast<std::uint16_t>(i);
|
|
}
|
|
indices = sequence.data();
|
|
indexCount = static_cast<std::uint32_t>(sequence.size());
|
|
}
|
|
const bool hasNormals = normals.data != nullptr && normals.count >= vertexCount;
|
|
const bool hasTexCoords = texCoords.data != nullptr && texCoords.count >= vertexCount;
|
|
const bool hasColors = colors.data != nullptr && colors.count >= vertexCount;
|
|
const std::size_t indexBytes = protocol::padded(std::size_t{indexCount} * 2);
|
|
const std::size_t size = indexBytes + std::size_t{vertexCount} * (12 + (hasNormals ? 12 : 0) +
|
|
(hasTexCoords ? 8 : 0) + (hasColors ? 4 : 0));
|
|
std::byte* payload = recorder_->add(Op::DrawArrays,
|
|
protocol::DrawArrays{vertexCount, indexCount, hasNormals, hasTexCoords,
|
|
hasColors},
|
|
size);
|
|
if (payload == nullptr) {
|
|
return;
|
|
}
|
|
std::memcpy(payload, indices, std::size_t{indexCount} * 2);
|
|
std::byte* next = payload + indexBytes;
|
|
pack(next, positions.data, positions.stride, 12, vertexCount);
|
|
next += std::size_t{vertexCount} * 12;
|
|
if (hasNormals) {
|
|
pack(next, normals.data, normals.stride, 12, vertexCount);
|
|
next += std::size_t{vertexCount} * 12;
|
|
}
|
|
if (hasTexCoords) {
|
|
pack(next, texCoords.data, texCoords.stride, 8, vertexCount);
|
|
next += std::size_t{vertexCount} * 8;
|
|
}
|
|
if (hasColors) {
|
|
pack(next, colors.data, colors.stride, 4, vertexCount);
|
|
}
|
|
}
|
|
|
|
} // namespace t5de
|