Draw the client's scene in the renderer process
The adapter records everything the client draws (state, transforms, lights, fog, textures and buffers) as a command stream, sent in chunks through the shared memory channel, while IMVU's Direct3D renderer still draws the whole view. The renderer process draws it with Diligent, with shaders reproducing IMVU's Direct3D 9 fixed-function setup, over the right half of the 3D view to compare. It skips to the newest frame when it falls behind, and logs its errors and frame times. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5c35c8b63a
commit
42fc55cb91
23 files changed
+2120
-471
No files matched your search
@@ -1,11 +1,14 @@
|
|||||||
# t5deRenderEngine.dll, the adapter: a renderer plugin for IMVU Classic (32-bit).
|
# t5deRenderEngine.dll, the adapter: a renderer plugin for IMVU Classic (32-bit).
|
||||||
|
|
||||||
add_library(t5de_render_engine SHARED
|
add_library(t5de_render_engine SHARED
|
||||||
|
engine.cpp
|
||||||
log.cpp
|
log.cpp
|
||||||
passthrough_engine.cpp
|
pixmap.cpp
|
||||||
plugin.cpp
|
plugin.cpp
|
||||||
plugin.def
|
plugin.def
|
||||||
renderer_process.cpp)
|
recorder.cpp
|
||||||
|
renderer_process.cpp
|
||||||
|
resources.cpp)
|
||||||
set_target_properties(t5de_render_engine PROPERTIES OUTPUT_NAME t5deRenderEngine)
|
set_target_properties(t5de_render_engine PROPERTIES OUTPUT_NAME t5deRenderEngine)
|
||||||
target_link_libraries(t5de_render_engine PRIVATE t5de::imvu t5de::protocol t5de::options)
|
target_link_libraries(t5de_render_engine PRIVATE t5de::imvu t5de::protocol t5de::options)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,296 @@
|
|||||||
|
#include "engine.hpp"
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#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)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
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));
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return inner_->newPixmapRenderTarget(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
imvu::SharedPtr<imvu::PiTextureRenderTarget> Engine::newTextureRenderTarget(imvu::Vec2i size)
|
||||||
|
{
|
||||||
|
return inner_->newTextureRenderTarget(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::beginRendering(imvu::PiRenderTarget* target)
|
||||||
|
{
|
||||||
|
inner_->beginRendering(target);
|
||||||
|
recorder_->add(Op::BeginFrame, protocol::BeginFrame{target != nullptr});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::endRendering()
|
||||||
|
{
|
||||||
|
inner_->endRendering();
|
||||||
|
recorder_->add(Op::EndFrame, protocol::EndFrame{});
|
||||||
|
recorder_->flush(true);
|
||||||
|
++frames_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions,
|
||||||
|
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
|
||||||
|
imvu::PiInternalVertexBuffer* colors)
|
||||||
|
{
|
||||||
|
inner_->drawTriangles(inner(indices), inner(positions), inner(normals), inner(texCoords), inner(colors));
|
||||||
|
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) { inner_->drawLine(from, to, color); }
|
||||||
|
|
||||||
|
void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites) { inner_->drawPointSprites(sprites); }
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors);
|
||||||
|
if (indices == nullptr || positions.data == nullptr || vertexCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// A renderer that forwards every call to another one (IMVU's Direct3D engine) and logs what a frame is made of: proof
|
// The adapter's engine: what the client draws with. Everything goes to the client's own Direct3D renderer, which
|
||||||
// that our declaration of the interface matches IMVU's, and a way to see how the client draws.
|
// still draws the whole 3D view, and is recorded for the renderer process, which draws its part on top (the right
|
||||||
|
// half for now, to compare). If the renderer process fails, the client's renderer carries on alone.
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "renderer_process.hpp"
|
#include "recorder.hpp"
|
||||||
#include "t5de/imvu/render_engine.hpp"
|
#include "t5de/imvu/render_engine.hpp"
|
||||||
|
|
||||||
namespace t5de {
|
namespace t5de {
|
||||||
|
|
||||||
class PassthroughEngine final : public imvu::PiRenderEngine {
|
class Engine final : public imvu::PiRenderEngine {
|
||||||
public:
|
public:
|
||||||
// renderer may be null.
|
// recorder may be inactive (no renderer process).
|
||||||
PassthroughEngine(imvu::SharedPtr<imvu::PiRenderEngine> inner, std::unique_ptr<RendererProcess> renderer);
|
Engine(imvu::SharedPtr<imvu::PiRenderEngine> inner, std::shared_ptr<Recorder> recorder);
|
||||||
~PassthroughEngine() override;
|
~Engine() override;
|
||||||
|
|
||||||
void resizeFrameBuffer(imvu::Vec2i size) override;
|
void resizeFrameBuffer(imvu::Vec2i size) override;
|
||||||
void enable2dFrameBuffer() override;
|
void enable2dFrameBuffer() override;
|
||||||
@@ -53,7 +53,7 @@ public:
|
|||||||
void drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions,
|
void drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions,
|
||||||
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
|
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
|
||||||
imvu::PiInternalVertexBuffer* colors) override;
|
imvu::PiInternalVertexBuffer* colors) override;
|
||||||
void drawLine(imvu::Vec3f from, imvu::Vec3f to, std::uint32_t color) override;
|
void drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color) override;
|
||||||
void drawPointSprites(const imvu::PointSpriteVector& sprites) override;
|
void drawPointSprites(const imvu::PointSpriteVector& sprites) override;
|
||||||
void drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t indexCount, const std::uint16_t* indices,
|
void 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>& positions,
|
||||||
@@ -61,15 +61,11 @@ public:
|
|||||||
const imvu::VertexAttributeArray<imvu::Vec2f>& texCoords,
|
const imvu::VertexAttributeArray<imvu::Vec2f>& texCoords,
|
||||||
const imvu::VertexAttributeArray<imvu::Color>& colors) override;
|
const imvu::VertexAttributeArray<imvu::Color>& colors) override;
|
||||||
|
|
||||||
// The interface's methods, by vtable slot (slot 0, the destructor, isn't counted).
|
|
||||||
static constexpr std::size_t slots = 35;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void count(std::size_t slot);
|
void setState(protocol::State state, std::uint32_t value);
|
||||||
|
|
||||||
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
|
imvu::SharedPtr<imvu::PiRenderEngine> inner_;
|
||||||
std::unique_ptr<RendererProcess> renderer_;
|
std::shared_ptr<Recorder> recorder_;
|
||||||
std::array<std::uint32_t, slots> calls_{};
|
|
||||||
std::uint64_t frames_ = 0;
|
std::uint64_t frames_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1,395 +0,0 @@
|
|||||||
#include "passthrough_engine.hpp"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "log.hpp"
|
|
||||||
|
|
||||||
namespace t5de {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr std::array<const char*, PassthroughEngine::slots> names = {
|
|
||||||
"~PiRenderEngine",
|
|
||||||
"resizeFrameBuffer",
|
|
||||||
"enable2dFrameBuffer",
|
|
||||||
"clear",
|
|
||||||
"setProjection",
|
|
||||||
"setModelSpace",
|
|
||||||
"setViewSpace",
|
|
||||||
"setTextures",
|
|
||||||
"setDepthTestingOn",
|
|
||||||
"setDepthWritingOn",
|
|
||||||
"setBlendMode",
|
|
||||||
"setAlphaTestingOn",
|
|
||||||
"setAlphaTestValue",
|
|
||||||
"setBlendingOn",
|
|
||||||
"setBackFaceCullingOn",
|
|
||||||
"setNormalInvertingOn",
|
|
||||||
"setLightingOn",
|
|
||||||
"setLightProperties",
|
|
||||||
"setFogOn",
|
|
||||||
"setFogAttributes",
|
|
||||||
"setTextureTransformMatrixIdentity",
|
|
||||||
"setTextureTransformMatrix",
|
|
||||||
"setEnvironmentAmbient",
|
|
||||||
"newRenderTexture",
|
|
||||||
"newIndexBuffer",
|
|
||||||
"newStaticVertexBuffer",
|
|
||||||
"newDynamicVertexBuffer",
|
|
||||||
"newPixmapRenderTarget",
|
|
||||||
"newTextureRenderTarget",
|
|
||||||
"beginRendering",
|
|
||||||
"endRendering",
|
|
||||||
"drawTriangles",
|
|
||||||
"drawLine",
|
|
||||||
"drawPointSprites",
|
|
||||||
"drawTrianglesFromMemory",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Frames whose calls are logged: the first few, then one a minute or so at 30 FPS.
|
|
||||||
bool logged(std::uint64_t frame) { return frame < 5 || frame % 1800 == 0; }
|
|
||||||
|
|
||||||
// Whether to log a call's arguments: the first few calls of each method, in the whole process. Enough to check that
|
|
||||||
// our layouts of IMVU's types read sensible values.
|
|
||||||
bool sampled(std::size_t slot)
|
|
||||||
{
|
|
||||||
static std::array<std::atomic<int>, PassthroughEngine::slots> calls{};
|
|
||||||
return calls[slot]++ < 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string text(const imvu::Vec3f& v) { return std::format("({:.3f}, {:.3f}, {:.3f})", v.x, v.y, v.z); }
|
|
||||||
|
|
||||||
std::string text(const imvu::Transform& t)
|
|
||||||
{
|
|
||||||
const auto& m = t.linear;
|
|
||||||
return std::format("[{:.3f} {:.3f} {:.3f}; {:.3f} {:.3f} {:.3f}; {:.3f} {:.3f} {:.3f}] + {}", m[0][0], m[0][1],
|
|
||||||
m[0][2], m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2], text(t.translation));
|
|
||||||
}
|
|
||||||
|
|
||||||
// A pixmap's size, from pixmap.dll's exported Pixmap::width and Pixmap::height.
|
|
||||||
imvu::Vec2i pixmap_size(const imvu::Pixmap& pixmap)
|
|
||||||
{
|
|
||||||
using Getter = unsigned(__thiscall*)(const imvu::Pixmap*);
|
|
||||||
struct Getters {
|
|
||||||
Getter width;
|
|
||||||
Getter height;
|
|
||||||
};
|
|
||||||
static const Getters getters = [] {
|
|
||||||
HMODULE module = GetModuleHandleW(L"pixmap.dll");
|
|
||||||
if (module == nullptr) {
|
|
||||||
return Getters{};
|
|
||||||
}
|
|
||||||
return Getters{reinterpret_cast<Getter>(GetProcAddress(module, "?width@Pixmap@@QBEIXZ")),
|
|
||||||
reinterpret_cast<Getter>(GetProcAddress(module, "?height@Pixmap@@QBEIXZ"))};
|
|
||||||
}();
|
|
||||||
if (getters.width == nullptr || getters.height == nullptr) {
|
|
||||||
return {-1, -1};
|
|
||||||
}
|
|
||||||
return {static_cast<int>(getters.width(&pixmap)), static_cast<int>(getters.height(&pixmap))};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
PassthroughEngine::PassthroughEngine(imvu::SharedPtr<imvu::PiRenderEngine> inner,
|
|
||||||
std::unique_ptr<RendererProcess> renderer)
|
|
||||||
: inner_(std::move(inner)), renderer_(std::move(renderer))
|
|
||||||
{
|
|
||||||
log::info("passthrough: wrapping the engine at {}", static_cast<void*>(inner_.get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
PassthroughEngine::~PassthroughEngine() { log::info("passthrough: destroyed after {} frames", frames_); }
|
|
||||||
|
|
||||||
void PassthroughEngine::count(std::size_t slot) { ++calls_[slot]; }
|
|
||||||
|
|
||||||
void PassthroughEngine::resizeFrameBuffer(imvu::Vec2i size)
|
|
||||||
{
|
|
||||||
log::info("resizeFrameBuffer({}, {})", size.x, size.y);
|
|
||||||
count(1);
|
|
||||||
inner_->resizeFrameBuffer(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::enable2dFrameBuffer()
|
|
||||||
{
|
|
||||||
count(2);
|
|
||||||
inner_->enable2dFrameBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::clear(std::uint32_t flags, imvu::Color color)
|
|
||||||
{
|
|
||||||
if (sampled(3)) {
|
|
||||||
log::info("clear(flags {}, color {:08x})", flags, color);
|
|
||||||
}
|
|
||||||
count(3);
|
|
||||||
inner_->clear(flags, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setProjection(imvu::Vec2i viewport, const imvu::Mat4f& projection)
|
|
||||||
{
|
|
||||||
if (sampled(4)) {
|
|
||||||
const auto& m = projection.m;
|
|
||||||
log::info("setProjection({}x{}, [{:.3f} {:.3f} {:.3f} {:.3f}; {:.3f} {:.3f} {:.3f} {:.3f}; "
|
|
||||||
"{:.3f} {:.3f} {:.3f} {:.3f}; {:.3f} {:.3f} {:.3f} {:.3f}])",
|
|
||||||
viewport.x, viewport.y, m[0][0], m[0][1], m[0][2], m[0][3], m[1][0], m[1][1], m[1][2],
|
|
||||||
m[1][3], m[2][0], m[2][1], m[2][2], m[2][3], m[3][0], m[3][1], m[3][2], m[3][3]);
|
|
||||||
}
|
|
||||||
count(4);
|
|
||||||
inner_->setProjection(viewport, projection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setModelSpace(const imvu::Transform& model)
|
|
||||||
{
|
|
||||||
if (sampled(5)) {
|
|
||||||
log::info("setModelSpace({})", text(model));
|
|
||||||
}
|
|
||||||
count(5);
|
|
||||||
inner_->setModelSpace(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setViewSpace(const imvu::Transform& view)
|
|
||||||
{
|
|
||||||
if (sampled(6)) {
|
|
||||||
log::info("setViewSpace({})", text(view));
|
|
||||||
}
|
|
||||||
count(6);
|
|
||||||
inner_->setViewSpace(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setTextures(imvu::PiRenderTexture* texture0, imvu::PiRenderTexture* texture1)
|
|
||||||
{
|
|
||||||
count(7);
|
|
||||||
inner_->setTextures(texture0, texture1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setDepthTestingOn(bool on)
|
|
||||||
{
|
|
||||||
count(8);
|
|
||||||
inner_->setDepthTestingOn(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setDepthWritingOn(bool on)
|
|
||||||
{
|
|
||||||
count(9);
|
|
||||||
inner_->setDepthWritingOn(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setBlendMode(imvu::RenderBlendMode mode)
|
|
||||||
{
|
|
||||||
if (sampled(10)) {
|
|
||||||
log::info("setBlendMode({})", static_cast<int>(mode));
|
|
||||||
}
|
|
||||||
count(10);
|
|
||||||
inner_->setBlendMode(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setAlphaTestingOn(bool on)
|
|
||||||
{
|
|
||||||
count(11);
|
|
||||||
inner_->setAlphaTestingOn(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setAlphaTestValue(float value)
|
|
||||||
{
|
|
||||||
if (sampled(12)) {
|
|
||||||
log::info("setAlphaTestValue({:.3f})", value);
|
|
||||||
}
|
|
||||||
count(12);
|
|
||||||
inner_->setAlphaTestValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setBlendingOn(bool on)
|
|
||||||
{
|
|
||||||
count(13);
|
|
||||||
inner_->setBlendingOn(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setBackFaceCullingOn(bool on)
|
|
||||||
{
|
|
||||||
count(14);
|
|
||||||
inner_->setBackFaceCullingOn(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setNormalInvertingOn(bool on)
|
|
||||||
{
|
|
||||||
count(15);
|
|
||||||
inner_->setNormalInvertingOn(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setLightingOn(bool on)
|
|
||||||
{
|
|
||||||
count(16);
|
|
||||||
inner_->setLightingOn(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setLightProperties(std::uint32_t index, const imvu::LightProperties& light)
|
|
||||||
{
|
|
||||||
if (sampled(17)) {
|
|
||||||
log::info("setLightProperties({}, enabled {}, diffuse {:08x}, vector {}, type {})", index,
|
|
||||||
light.enabled, light.diffuse, text(light.vector), static_cast<int>(light.type));
|
|
||||||
}
|
|
||||||
count(17);
|
|
||||||
inner_->setLightProperties(index, light);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setFogOn(bool on)
|
|
||||||
{
|
|
||||||
count(18);
|
|
||||||
inner_->setFogOn(on);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setFogAttributes(float start, float end, imvu::Color color)
|
|
||||||
{
|
|
||||||
if (sampled(19)) {
|
|
||||||
log::info("setFogAttributes(start {:.3f}, end {:.3f}, color {:08x})", start, end, color);
|
|
||||||
}
|
|
||||||
count(19);
|
|
||||||
inner_->setFogAttributes(start, end, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setTextureTransformMatrixIdentity(std::uint32_t stage)
|
|
||||||
{
|
|
||||||
count(20);
|
|
||||||
inner_->setTextureTransformMatrixIdentity(stage);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setTextureTransformMatrix(std::uint32_t stage, const imvu::TextureTransform& transform)
|
|
||||||
{
|
|
||||||
if (sampled(21)) {
|
|
||||||
const auto& m = transform.m;
|
|
||||||
log::info("setTextureTransformMatrix({}, [{:.3f} {:.3f} {:.3f} {:.3f} {:.3f} {:.3f}])", stage, m[0],
|
|
||||||
m[1], m[2], m[3], m[4], m[5]);
|
|
||||||
}
|
|
||||||
count(21);
|
|
||||||
inner_->setTextureTransformMatrix(stage, transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::setEnvironmentAmbient(imvu::Color color)
|
|
||||||
{
|
|
||||||
if (sampled(22)) {
|
|
||||||
log::info("setEnvironmentAmbient({:08x})", color);
|
|
||||||
}
|
|
||||||
count(22);
|
|
||||||
inner_->setEnvironmentAmbient(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
imvu::PiRenderTexture* PassthroughEngine::newRenderTexture(const imvu::Pixmap& pixmap, std::uint32_t flags)
|
|
||||||
{
|
|
||||||
if (sampled(23)) {
|
|
||||||
const auto size = pixmap_size(pixmap);
|
|
||||||
log::info("newRenderTexture({}x{} pixmap, flags {:x})", size.x, size.y, flags);
|
|
||||||
}
|
|
||||||
count(23);
|
|
||||||
return inner_->newRenderTexture(pixmap, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
imvu::PiInternalIndexBuffer* PassthroughEngine::newIndexBuffer(std::uint32_t indexCount, const std::uint16_t* indices)
|
|
||||||
{
|
|
||||||
if (sampled(24)) {
|
|
||||||
log::info("newIndexBuffer({} indices)", indexCount);
|
|
||||||
}
|
|
||||||
count(24);
|
|
||||||
return inner_->newIndexBuffer(indexCount, indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
imvu::PiInternalVertexBuffer* PassthroughEngine::newStaticVertexBuffer(const void* data, std::uint32_t stride,
|
|
||||||
std::uint32_t elementSize, std::uint32_t elementCount)
|
|
||||||
{
|
|
||||||
if (sampled(25)) {
|
|
||||||
log::info("newStaticVertexBuffer(stride {}, element size {}, {} elements)", stride, elementSize,
|
|
||||||
elementCount);
|
|
||||||
}
|
|
||||||
count(25);
|
|
||||||
return inner_->newStaticVertexBuffer(data, stride, elementSize, elementCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
imvu::PiInternalVertexBuffer* PassthroughEngine::newDynamicVertexBuffer(std::uint32_t elementSize, std::uint32_t elementCount)
|
|
||||||
{
|
|
||||||
if (sampled(26)) {
|
|
||||||
log::info("newDynamicVertexBuffer(element size {}, {} elements)", elementSize, elementCount);
|
|
||||||
}
|
|
||||||
count(26);
|
|
||||||
return inner_->newDynamicVertexBuffer(elementSize, elementCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
imvu::SharedPtr<imvu::PiPixmapRenderTarget> PassthroughEngine::newPixmapRenderTarget(imvu::Vec2i size)
|
|
||||||
{
|
|
||||||
log::info("newPixmapRenderTarget({}, {})", size.x, size.y);
|
|
||||||
count(27);
|
|
||||||
return inner_->newPixmapRenderTarget(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
imvu::SharedPtr<imvu::PiTextureRenderTarget> PassthroughEngine::newTextureRenderTarget(imvu::Vec2i size)
|
|
||||||
{
|
|
||||||
log::info("newTextureRenderTarget({}, {})", size.x, size.y);
|
|
||||||
count(28);
|
|
||||||
return inner_->newTextureRenderTarget(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::beginRendering(imvu::PiRenderTarget* target)
|
|
||||||
{
|
|
||||||
count(29);
|
|
||||||
inner_->beginRendering(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::endRendering()
|
|
||||||
{
|
|
||||||
count(30);
|
|
||||||
inner_->endRendering();
|
|
||||||
if (renderer_) {
|
|
||||||
renderer_->frameDrawn();
|
|
||||||
}
|
|
||||||
if (logged(frames_)) {
|
|
||||||
std::string line = std::format("frame {}:", frames_);
|
|
||||||
for (std::size_t slot = 1; slot < slots; ++slot) {
|
|
||||||
if (calls_[slot] != 0) {
|
|
||||||
line += std::format(" {} x{},", names[slot], calls_[slot]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
line.pop_back();
|
|
||||||
log::write(line);
|
|
||||||
}
|
|
||||||
calls_ = {};
|
|
||||||
++frames_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions,
|
|
||||||
imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords,
|
|
||||||
imvu::PiInternalVertexBuffer* colors)
|
|
||||||
{
|
|
||||||
count(31);
|
|
||||||
inner_->drawTriangles(indices, positions, normals, texCoords, colors);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::drawLine(imvu::Vec3f from, imvu::Vec3f to, std::uint32_t color)
|
|
||||||
{
|
|
||||||
if (sampled(32)) {
|
|
||||||
log::info("drawLine({}, {}, {:08x})", text(from), text(to), color);
|
|
||||||
}
|
|
||||||
count(32);
|
|
||||||
inner_->drawLine(from, to, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::drawPointSprites(const imvu::PointSpriteVector& sprites)
|
|
||||||
{
|
|
||||||
count(33);
|
|
||||||
inner_->drawPointSprites(sprites);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PassthroughEngine::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 (sampled(34)) {
|
|
||||||
log::info("drawTrianglesFromMemory({} vertices, {} indices; positions {} stride {}, colors {} stride {})",
|
|
||||||
vertexCount, indexCount, positions.count, positions.stride, colors.count, colors.stride);
|
|
||||||
}
|
|
||||||
count(34);
|
|
||||||
inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace t5de
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include "pixmap.hpp"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace t5de {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using Getter = unsigned(__thiscall*)(const imvu::Pixmap*);
|
||||||
|
using PixelsGetter = std::uint32_t*(__thiscall*)(const imvu::Pixmap*);
|
||||||
|
|
||||||
|
struct Methods {
|
||||||
|
Getter width = nullptr;
|
||||||
|
Getter height = nullptr;
|
||||||
|
Getter rowBytes = nullptr;
|
||||||
|
PixelsGetter pixels = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Methods& methods()
|
||||||
|
{
|
||||||
|
static const Methods methods = [] {
|
||||||
|
Methods found;
|
||||||
|
if (HMODULE module = GetModuleHandleW(L"pixmap.dll")) {
|
||||||
|
found.width = reinterpret_cast<Getter>(GetProcAddress(module, "?width@Pixmap@@QBEIXZ"));
|
||||||
|
found.height = reinterpret_cast<Getter>(GetProcAddress(module, "?height@Pixmap@@QBEIXZ"));
|
||||||
|
found.rowBytes = reinterpret_cast<Getter>(GetProcAddress(module, "?rowBytes@Pixmap@@QBEIXZ"));
|
||||||
|
found.pixels = reinterpret_cast<PixelsGetter>(GetProcAddress(module, "?pixels@Pixmap@@QBEPAIXZ"));
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}();
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::optional<PixmapPixels> pixmap_pixels(const imvu::Pixmap& pixmap)
|
||||||
|
{
|
||||||
|
const Methods& m = methods();
|
||||||
|
if (m.width == nullptr || m.height == nullptr || m.rowBytes == nullptr || m.pixels == nullptr) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return PixmapPixels{m.width(&pixmap), m.height(&pixmap), m.rowBytes(&pixmap), m.pixels(&pixmap)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace t5de
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
// Reading IMVU's images (Pixmap) through pixmap.dll's exported methods, rather than depending on their layout.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "t5de/imvu/types.hpp"
|
||||||
|
|
||||||
|
namespace t5de {
|
||||||
|
|
||||||
|
struct PixmapPixels {
|
||||||
|
std::uint32_t width;
|
||||||
|
std::uint32_t height;
|
||||||
|
std::uint32_t rowBytes;
|
||||||
|
const std::uint32_t* pixels; // 0xAARRGGBB, rows rowBytes apart
|
||||||
|
};
|
||||||
|
|
||||||
|
// Null if pixmap.dll's methods can't be found.
|
||||||
|
std::optional<PixmapPixels> pixmap_pixels(const imvu::Pixmap& pixmap);
|
||||||
|
|
||||||
|
} // namespace t5de
|
||||||
+18
-5
@@ -1,12 +1,15 @@
|
|||||||
// The renderer plugin's entry points, which SceneWindow.dll's createDLLRenderer looks up by name (exported
|
// The renderer plugin's entry points, which SceneWindow.dll's createDLLRenderer looks up by name (exported
|
||||||
// undecorated by plugin.def). For now the renderer is the pass-through: IMVU's Direct3D engine, wrapped.
|
// undecorated by plugin.def). The engine is the adapter's (engine.hpp): IMVU's Direct3D engine draws, and the
|
||||||
|
// renderer process draws over part of it.
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "passthrough_engine.hpp"
|
#include "engine.hpp"
|
||||||
|
#include "recorder.hpp"
|
||||||
#include "renderer_process.hpp"
|
#include "renderer_process.hpp"
|
||||||
#include "t5de/imvu/render_engine.hpp"
|
#include "t5de/imvu/render_engine.hpp"
|
||||||
|
|
||||||
@@ -30,6 +33,15 @@ 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) or full.
|
||||||
|
protocol::View view()
|
||||||
|
{
|
||||||
|
wchar_t value[16] = {};
|
||||||
|
GetEnvironmentVariableW(L"T5DE_RENDERER_VIEW", value, 16);
|
||||||
|
const std::wstring_view setting = value;
|
||||||
|
return setting == L"off" ? protocol::View::Off : setting == L"full" ? protocol::View::Full : protocol::View::Split;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
extern "C" int __cdecl isRenderEngineSupported() { return 1; }
|
extern "C" int __cdecl isRenderEngineSupported() { return 1; }
|
||||||
@@ -45,9 +57,10 @@ extern "C" imvu::SharedPtr<imvu::PiRenderEngine> __cdecl createRenderEngine(imvu
|
|||||||
if (!inner) {
|
if (!inner) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto renderer = hwnd != nullptr ? RendererProcess::start(hwnd) : nullptr;
|
const protocol::View shown = view();
|
||||||
return imvu::SharedPtr<imvu::PiRenderEngine>(
|
auto renderer = hwnd != nullptr && shown != protocol::View::Off ? RendererProcess::start(hwnd, shown) : nullptr;
|
||||||
std::make_unique<PassthroughEngine>(std::move(inner), std::move(renderer)));
|
return imvu::SharedPtr<imvu::PiRenderEngine>(std::make_unique<Engine>(
|
||||||
|
std::move(inner), std::make_shared<Recorder>(std::move(renderer))));
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
// Exceptions mustn't cross into IMVU's code (a different C++ runtime).
|
// Exceptions mustn't cross into IMVU's code (a different C++ runtime).
|
||||||
log::info("createRenderEngine failed: {}", e.what());
|
log::info("createRenderEngine failed: {}", e.what());
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#include "recorder.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "pixmap.hpp"
|
||||||
|
|
||||||
|
namespace t5de {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Between frames, recorded commands (mostly textures and buffers being loaded) are sent once they reach this size.
|
||||||
|
constexpr std::size_t flushSize = 4 * 1024 * 1024;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void pack(std::byte* destination, const void* source, std::uint32_t stride, std::uint32_t elementSize,
|
||||||
|
std::uint32_t count)
|
||||||
|
{
|
||||||
|
const auto* from = static_cast<const std::byte*>(source);
|
||||||
|
if (stride == 0 || stride == elementSize) {
|
||||||
|
std::memcpy(destination, from, std::size_t{elementSize} * count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (std::uint32_t i = 0; i < count; ++i) {
|
||||||
|
std::memcpy(destination + std::size_t{i} * elementSize, from + std::size_t{i} * stride, elementSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Recorder::Recorder(std::unique_ptr<RendererProcess> process) : process_(std::move(process)) {}
|
||||||
|
|
||||||
|
void Recorder::flush(bool endsFrame)
|
||||||
|
{
|
||||||
|
if (!active()) {
|
||||||
|
writer_.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
process_->send(writer_.bytes(), endsFrame);
|
||||||
|
writer_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::flushIfLarge()
|
||||||
|
{
|
||||||
|
if (writer_.size() >= flushSize) {
|
||||||
|
flush(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::addPixels(protocol::Op op, std::uint32_t id, const imvu::Pixmap& pixmap, std::uint32_t flags)
|
||||||
|
{
|
||||||
|
const auto pixels = pixmap_pixels(pixmap);
|
||||||
|
if (!pixels || pixels->pixels == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::size_t rowSize = std::size_t{pixels->width} * 4;
|
||||||
|
std::byte* payload = nullptr;
|
||||||
|
if (op == protocol::Op::CreateTexture) {
|
||||||
|
payload = add(op, protocol::CreateTexture{id, pixels->width, pixels->height, flags}, rowSize * pixels->height);
|
||||||
|
} else {
|
||||||
|
payload = add(op, protocol::UpdateTexture{id, pixels->width, pixels->height}, rowSize * pixels->height);
|
||||||
|
}
|
||||||
|
if (payload == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* rows = reinterpret_cast<const std::byte*>(pixels->pixels);
|
||||||
|
for (std::uint32_t y = 0; y < pixels->height; ++y) {
|
||||||
|
std::memcpy(payload + y * rowSize, rows + std::size_t{y} * pixels->rowBytes, rowSize);
|
||||||
|
}
|
||||||
|
flushIfLarge();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t Recorder::createTexture(const imvu::Pixmap& pixmap, std::uint32_t flags)
|
||||||
|
{
|
||||||
|
const std::uint32_t id = nextId_++;
|
||||||
|
addPixels(protocol::Op::CreateTexture, id, pixmap, flags);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::updateTexture(std::uint32_t id, const imvu::Pixmap& pixmap)
|
||||||
|
{
|
||||||
|
addPixels(protocol::Op::UpdateTexture, id, pixmap, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::destroyTexture(std::uint32_t id) { add(protocol::Op::DestroyTexture, protocol::DestroyTexture{id}); }
|
||||||
|
|
||||||
|
std::uint32_t Recorder::createBuffer(protocol::BufferKind kind, const void* data, std::uint32_t stride,
|
||||||
|
std::uint32_t elementSize, std::uint32_t count)
|
||||||
|
{
|
||||||
|
const std::uint32_t id = nextId_++;
|
||||||
|
const std::size_t size = data != nullptr ? std::size_t{elementSize} * count : 0;
|
||||||
|
if (std::byte* payload = add(protocol::Op::CreateBuffer, protocol::CreateBuffer{id, kind, elementSize, count}, size);
|
||||||
|
payload != nullptr && size != 0) {
|
||||||
|
pack(payload, data, stride, elementSize, count);
|
||||||
|
}
|
||||||
|
flushIfLarge();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::updateBuffer(std::uint32_t id, const void* data, std::uint32_t stride, std::uint32_t elementSize,
|
||||||
|
std::uint32_t count)
|
||||||
|
{
|
||||||
|
const std::size_t size = std::size_t{elementSize} * count;
|
||||||
|
if (std::byte* payload = add(protocol::Op::UpdateBuffer, protocol::UpdateBuffer{id, elementSize, count}, size);
|
||||||
|
payload != nullptr && data != nullptr) {
|
||||||
|
pack(payload, data, stride, elementSize, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::destroyBuffer(std::uint32_t id) { add(protocol::Op::DestroyBuffer, protocol::DestroyBuffer{id}); }
|
||||||
|
|
||||||
|
} // namespace t5de
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
// Records what the client draws as commands (include/t5de/protocol/commands.hpp) and sends them to the renderer
|
||||||
|
// process. Shared by an engine and the resources it creates (which can outlive it); once the renderer process is
|
||||||
|
// gone, it records nothing.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "renderer_process.hpp"
|
||||||
|
#include "t5de/imvu/types.hpp"
|
||||||
|
#include "t5de/protocol/commands.hpp"
|
||||||
|
|
||||||
|
namespace t5de {
|
||||||
|
|
||||||
|
class Recorder {
|
||||||
|
public:
|
||||||
|
explicit Recorder(std::unique_ptr<RendererProcess> process);
|
||||||
|
|
||||||
|
[[nodiscard]] bool active() const { return process_ && process_->alive(); }
|
||||||
|
|
||||||
|
// Records a command (if active); returns its payload to fill, or null.
|
||||||
|
template <class Body>
|
||||||
|
std::byte* add(protocol::Op op, const Body& body, std::size_t payloadSize = 0)
|
||||||
|
{
|
||||||
|
if (!active()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::byte* payload = writer_.add(op, body, payloadSize);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends what's recorded: at the end of each frame, and between frames when a lot is waiting (resources).
|
||||||
|
void flush(bool endsFrame);
|
||||||
|
void flushIfLarge();
|
||||||
|
|
||||||
|
std::uint32_t createTexture(const imvu::Pixmap& pixmap, std::uint32_t flags);
|
||||||
|
void updateTexture(std::uint32_t id, const imvu::Pixmap& pixmap);
|
||||||
|
void destroyTexture(std::uint32_t id);
|
||||||
|
|
||||||
|
// Copies count elements of elementSize bytes, stride bytes apart (0: packed), from data (null: none yet).
|
||||||
|
std::uint32_t createBuffer(protocol::BufferKind kind, const void* data, std::uint32_t stride,
|
||||||
|
std::uint32_t elementSize, std::uint32_t count);
|
||||||
|
void updateBuffer(std::uint32_t id, const void* data, std::uint32_t stride, std::uint32_t elementSize,
|
||||||
|
std::uint32_t count);
|
||||||
|
void destroyBuffer(std::uint32_t id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void addPixels(protocol::Op op, std::uint32_t id, const imvu::Pixmap& pixmap, std::uint32_t flags);
|
||||||
|
|
||||||
|
std::unique_ptr<RendererProcess> process_;
|
||||||
|
protocol::Writer writer_;
|
||||||
|
std::uint32_t nextId_ = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copies count elements of elementSize bytes, stride bytes apart (0: packed), into destination, packed.
|
||||||
|
void pack(std::byte* destination, const void* source, std::uint32_t stride, std::uint32_t elementSize,
|
||||||
|
std::uint32_t count);
|
||||||
|
|
||||||
|
} // namespace t5de
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "renderer_process.hpp"
|
#include "renderer_process.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -11,6 +13,9 @@ namespace t5de {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// How long to wait for the renderer process to take a chunk before giving up on it.
|
||||||
|
constexpr DWORD consumeTimeout = 5000;
|
||||||
|
|
||||||
// The folder this DLL is in: the client's program folder.
|
// The folder this DLL is in: the client's program folder.
|
||||||
std::filesystem::path module_folder()
|
std::filesystem::path module_folder()
|
||||||
{
|
{
|
||||||
@@ -22,31 +27,39 @@ std::filesystem::path module_folder()
|
|||||||
return std::filesystem::path(path).parent_path();
|
return std::filesystem::path(path).parent_path();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string narrow(const std::wstring& text) { return std::filesystem::path(text).string(); }
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::unique_ptr<RendererProcess> RendererProcess::start(HWND window)
|
std::unique_ptr<RendererProcess> RendererProcess::start(HWND window, protocol::View view)
|
||||||
{
|
{
|
||||||
static std::atomic<int> engines = 0;
|
static std::atomic<int> engines = 0;
|
||||||
const std::wstring name = std::format(L"Local\\t5de-renderer-{}-{}", GetCurrentProcessId(), engines++);
|
const std::wstring name = std::format(L"Local\\t5de-renderer-{}-{}", GetCurrentProcessId(), engines++);
|
||||||
|
constexpr DWORD size = sizeof(protocol::Channel) + protocol::channelCapacity;
|
||||||
|
|
||||||
std::unique_ptr<RendererProcess> renderer(new RendererProcess());
|
std::unique_ptr<RendererProcess> renderer(new RendererProcess());
|
||||||
renderer->mapping_ = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
|
renderer->mapping_ = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size, name.c_str());
|
||||||
sizeof(protocol::Channel), name.c_str());
|
|
||||||
if (renderer->mapping_ == nullptr) {
|
if (renderer->mapping_ == nullptr) {
|
||||||
log::info("renderer process: can't create the channel: error {}", GetLastError());
|
log::info("renderer process: can't create the channel: error {}", GetLastError());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
renderer->channel_ = static_cast<protocol::Channel*>(
|
renderer->channel_ =
|
||||||
MapViewOfFile(renderer->mapping_, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(protocol::Channel)));
|
static_cast<protocol::Channel*>(MapViewOfFile(renderer->mapping_, FILE_MAP_ALL_ACCESS, 0, 0, size));
|
||||||
renderer->frameEvent_ = CreateEventW(nullptr, FALSE, FALSE, (name + protocol::frameEventSuffix).c_str());
|
renderer->ready_ = CreateEventW(nullptr, FALSE, FALSE, (name + protocol::readyEventSuffix).c_str());
|
||||||
if (renderer->channel_ == nullptr || renderer->frameEvent_ == nullptr) {
|
renderer->consumed_ = CreateEventW(nullptr, FALSE, TRUE, (name + protocol::consumedEventSuffix).c_str());
|
||||||
|
if (renderer->channel_ == nullptr || renderer->ready_ == nullptr || renderer->consumed_ == nullptr) {
|
||||||
log::info("renderer process: can't set up the channel: error {}", GetLastError());
|
log::info("renderer process: can't set up the channel: error {}", GetLastError());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
renderer->data_ = reinterpret_cast<std::byte*>(renderer->channel_ + 1);
|
||||||
*renderer->channel_ = {.version = protocol::version,
|
*renderer->channel_ = {.version = protocol::version,
|
||||||
.closing = 0,
|
.closing = 0,
|
||||||
.window = reinterpret_cast<std::uintptr_t>(window),
|
.window = reinterpret_cast<std::uintptr_t>(window),
|
||||||
.frames = 0};
|
.frames = 0,
|
||||||
|
.view = view,
|
||||||
|
.capacity = protocol::channelCapacity,
|
||||||
|
.chunkSize = 0,
|
||||||
|
.chunkFlags = 0};
|
||||||
|
|
||||||
// The renderer's window goes on top of the client's 3D view: keep the client's drawing out of it.
|
// The renderer's window goes on top of the client's 3D view: keep the client's drawing out of it.
|
||||||
SetWindowLongW(window, GWL_STYLE, GetWindowLongW(window, GWL_STYLE) | WS_CLIPCHILDREN);
|
SetWindowLongW(window, GWL_STYLE, GetWindowLongW(window, GWL_STYLE) | WS_CLIPCHILDREN);
|
||||||
@@ -62,8 +75,7 @@ std::unique_ptr<RendererProcess> RendererProcess::start(HWND window)
|
|||||||
}
|
}
|
||||||
CloseHandle(process.hThread);
|
CloseHandle(process.hThread);
|
||||||
renderer->process_ = process.hProcess;
|
renderer->process_ = process.hProcess;
|
||||||
log::info("renderer process: started {} (pid {}) on channel {}", exe.string(), process.dwProcessId,
|
log::info("renderer process: started {} (pid {}) on channel {}", exe.string(), process.dwProcessId, narrow(name));
|
||||||
std::filesystem::path(name).string());
|
|
||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,22 +83,42 @@ RendererProcess::~RendererProcess()
|
|||||||
{
|
{
|
||||||
if (channel_ != nullptr) {
|
if (channel_ != nullptr) {
|
||||||
channel_->closing = 1;
|
channel_->closing = 1;
|
||||||
if (frameEvent_ != nullptr) {
|
if (ready_ != nullptr) {
|
||||||
SetEvent(frameEvent_);
|
SetEvent(ready_);
|
||||||
}
|
}
|
||||||
UnmapViewOfFile(channel_);
|
UnmapViewOfFile(channel_);
|
||||||
}
|
}
|
||||||
for (HANDLE handle : {frameEvent_, mapping_, process_}) {
|
for (HANDLE handle : {ready_, consumed_, mapping_, process_}) {
|
||||||
if (handle != nullptr) {
|
if (handle != nullptr) {
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererProcess::frameDrawn()
|
bool RendererProcess::send(std::span<const std::byte> commands, bool endsFrame)
|
||||||
{
|
{
|
||||||
|
if (!alive_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
const HANDLE waits[] = {consumed_, process_};
|
||||||
|
const DWORD result = WaitForMultipleObjects(2, waits, FALSE, consumeTimeout);
|
||||||
|
if (result != WAIT_OBJECT_0) {
|
||||||
|
log::info("renderer process: {}; giving up on it", result == WAIT_OBJECT_0 + 1 ? "exited" : "not responding");
|
||||||
|
alive_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::size_t size = std::min<std::size_t>(commands.size(), channel_->capacity);
|
||||||
|
std::memcpy(data_, commands.data(), size);
|
||||||
|
commands = commands.subspan(size);
|
||||||
|
channel_->chunkSize = static_cast<std::uint32_t>(size);
|
||||||
|
channel_->chunkFlags = endsFrame && commands.empty() ? protocol::chunkEndsFrame : 0;
|
||||||
|
if (endsFrame && commands.empty()) {
|
||||||
++channel_->frames;
|
++channel_->frames;
|
||||||
SetEvent(frameEvent_);
|
}
|
||||||
|
SetEvent(ready_);
|
||||||
|
} while (!commands.empty());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace t5de
|
} // namespace t5de
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// The renderer process (t5de-renderer.exe, 64-bit) an engine draws through: started with the engine, told about
|
// The renderer process (t5de-renderer.exe, 64-bit) an engine draws through: started with the engine, sent the
|
||||||
// each frame through a shared memory channel (include/t5de/protocol/channel.hpp), and told to exit with it.
|
// command stream through a shared memory channel (include/t5de/protocol/channel.hpp), and told to exit with it.
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "t5de/protocol/channel.hpp"
|
#include "t5de/protocol/channel.hpp"
|
||||||
|
|
||||||
@@ -13,23 +14,29 @@ namespace t5de {
|
|||||||
|
|
||||||
class RendererProcess {
|
class RendererProcess {
|
||||||
public:
|
public:
|
||||||
// Starts t5de-renderer.exe from this DLL's folder, drawing into window; null if it can't.
|
// Starts t5de-renderer.exe from this DLL's folder, drawing into part of window; null if it can't.
|
||||||
static std::unique_ptr<RendererProcess> start(HWND window);
|
static std::unique_ptr<RendererProcess> start(HWND window, protocol::View view);
|
||||||
|
|
||||||
RendererProcess(const RendererProcess&) = delete;
|
RendererProcess(const RendererProcess&) = delete;
|
||||||
RendererProcess& operator=(const RendererProcess&) = delete;
|
RendererProcess& operator=(const RendererProcess&) = delete;
|
||||||
~RendererProcess();
|
~RendererProcess();
|
||||||
|
|
||||||
// The client drew a frame.
|
// Sends commands, in as many chunks as they take; endsFrame marks the end of a frame. False if the renderer
|
||||||
void frameDrawn();
|
// process is gone or stopped taking them (it's then given up on).
|
||||||
|
bool send(std::span<const std::byte> commands, bool endsFrame);
|
||||||
|
|
||||||
|
[[nodiscard]] bool alive() const { return alive_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RendererProcess() = default;
|
RendererProcess() = default;
|
||||||
|
|
||||||
HANDLE mapping_ = nullptr;
|
HANDLE mapping_ = nullptr;
|
||||||
HANDLE frameEvent_ = nullptr;
|
HANDLE ready_ = nullptr;
|
||||||
|
HANDLE consumed_ = nullptr;
|
||||||
HANDLE process_ = nullptr;
|
HANDLE process_ = nullptr;
|
||||||
protocol::Channel* channel_ = nullptr;
|
protocol::Channel* channel_ = nullptr;
|
||||||
|
std::byte* data_ = nullptr;
|
||||||
|
bool alive_ = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace t5de
|
} // namespace t5de
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#include "resources.hpp"
|
||||||
|
|
||||||
|
namespace t5de {
|
||||||
|
|
||||||
|
Texture::Texture(imvu::PiRenderTexture* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||||
|
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::~Texture()
|
||||||
|
{
|
||||||
|
delete inner_; // through its virtual destructor: IMVU's code frees it
|
||||||
|
recorder_->destroyTexture(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
imvu::Vec2i Texture::size() const { return inner_->size(); }
|
||||||
|
|
||||||
|
void Texture::updateRegions(const imvu::Pixmap& pixmap, const imvu::CompoundRegion& regions)
|
||||||
|
{
|
||||||
|
inner_->updateRegions(pixmap, regions);
|
||||||
|
recorder_->updateTexture(id_, pixmap); // all of it: the regions' layout isn't known
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::update(const imvu::Pixmap& pixmap)
|
||||||
|
{
|
||||||
|
inner_->update(pixmap);
|
||||||
|
recorder_->updateTexture(id_, pixmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
imvu::Pixmap* Texture::read(imvu::Pixmap* result) const { return inner_->read(result); }
|
||||||
|
|
||||||
|
IndexBuffer::IndexBuffer(imvu::PiInternalIndexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||||
|
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexBuffer::~IndexBuffer()
|
||||||
|
{
|
||||||
|
delete inner_;
|
||||||
|
recorder_->destroyBuffer(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexBuffer::VertexBuffer(imvu::PiInternalVertexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id)
|
||||||
|
: inner_(inner), recorder_(std::move(recorder)), id_(id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
VertexBuffer::~VertexBuffer()
|
||||||
|
{
|
||||||
|
delete inner_;
|
||||||
|
recorder_->destroyBuffer(id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexBuffer::update(const void* data, std::uint32_t stride, std::uint32_t elementSize, std::uint32_t elementCount)
|
||||||
|
{
|
||||||
|
inner_->update(data, stride, elementSize, elementCount);
|
||||||
|
recorder_->updateBuffer(id_, data, stride, elementSize, elementCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexBuffer::unknown2() { inner_->unknown2(); }
|
||||||
|
|
||||||
|
} // namespace t5de
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
#pragma once
|
||||||
|
// The textures and buffers the adapter's engine hands to the client: each wraps the client renderer's own (which the
|
||||||
|
// client renderer still draws with) and mirrors it, by id, to the renderer process. The client deletes them through
|
||||||
|
// their virtual destructors.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "recorder.hpp"
|
||||||
|
#include "t5de/imvu/resources.hpp"
|
||||||
|
|
||||||
|
namespace t5de {
|
||||||
|
|
||||||
|
class Texture final : public imvu::PiRenderTexture {
|
||||||
|
public:
|
||||||
|
Texture(imvu::PiRenderTexture* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id);
|
||||||
|
~Texture() override;
|
||||||
|
|
||||||
|
imvu::Vec2i size() const override;
|
||||||
|
void updateRegions(const imvu::Pixmap& pixmap, const imvu::CompoundRegion& regions) override;
|
||||||
|
void update(const imvu::Pixmap& pixmap) override;
|
||||||
|
imvu::Pixmap* read(imvu::Pixmap* result) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] imvu::PiRenderTexture* inner() const { return inner_; }
|
||||||
|
[[nodiscard]] std::uint32_t id() const { return id_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
imvu::PiRenderTexture* inner_;
|
||||||
|
std::shared_ptr<Recorder> recorder_;
|
||||||
|
std::uint32_t id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IndexBuffer final : public imvu::PiInternalIndexBuffer {
|
||||||
|
public:
|
||||||
|
IndexBuffer(imvu::PiInternalIndexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id);
|
||||||
|
~IndexBuffer() override;
|
||||||
|
|
||||||
|
[[nodiscard]] imvu::PiInternalIndexBuffer* inner() const { return inner_; }
|
||||||
|
[[nodiscard]] std::uint32_t id() const { return id_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
imvu::PiInternalIndexBuffer* inner_;
|
||||||
|
std::shared_ptr<Recorder> recorder_;
|
||||||
|
std::uint32_t id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VertexBuffer final : public imvu::PiInternalVertexBuffer {
|
||||||
|
public:
|
||||||
|
VertexBuffer(imvu::PiInternalVertexBuffer* inner, std::shared_ptr<Recorder> recorder, std::uint32_t id);
|
||||||
|
~VertexBuffer() override;
|
||||||
|
|
||||||
|
void update(const void* data, std::uint32_t stride, std::uint32_t elementSize,
|
||||||
|
std::uint32_t elementCount) override;
|
||||||
|
void unknown2() override;
|
||||||
|
|
||||||
|
[[nodiscard]] imvu::PiInternalVertexBuffer* inner() const { return inner_; }
|
||||||
|
[[nodiscard]] std::uint32_t id() const { return id_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
imvu::PiInternalVertexBuffer* inner_;
|
||||||
|
std::shared_ptr<Recorder> recorder_;
|
||||||
|
std::uint32_t id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The client renderer's object and our id for something the engine made; null and 0 for null.
|
||||||
|
inline imvu::PiRenderTexture* inner(imvu::PiRenderTexture* texture)
|
||||||
|
{
|
||||||
|
return texture != nullptr ? static_cast<Texture*>(texture)->inner() : nullptr;
|
||||||
|
}
|
||||||
|
inline std::uint32_t id(imvu::PiRenderTexture* texture)
|
||||||
|
{
|
||||||
|
return texture != nullptr ? static_cast<Texture*>(texture)->id() : 0;
|
||||||
|
}
|
||||||
|
inline imvu::PiInternalIndexBuffer* inner(imvu::PiInternalIndexBuffer* buffer)
|
||||||
|
{
|
||||||
|
return buffer != nullptr ? static_cast<IndexBuffer*>(buffer)->inner() : nullptr;
|
||||||
|
}
|
||||||
|
inline std::uint32_t id(imvu::PiInternalIndexBuffer* buffer)
|
||||||
|
{
|
||||||
|
return buffer != nullptr ? static_cast<IndexBuffer*>(buffer)->id() : 0;
|
||||||
|
}
|
||||||
|
inline imvu::PiInternalVertexBuffer* inner(imvu::PiInternalVertexBuffer* buffer)
|
||||||
|
{
|
||||||
|
return buffer != nullptr ? static_cast<VertexBuffer*>(buffer)->inner() : nullptr;
|
||||||
|
}
|
||||||
|
inline std::uint32_t id(imvu::PiInternalVertexBuffer* buffer)
|
||||||
|
{
|
||||||
|
return buffer != nullptr ? static_cast<VertexBuffer*>(buffer)->id() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace t5de
|
||||||
@@ -7,19 +7,36 @@
|
|||||||
|
|
||||||
namespace t5de::protocol {
|
namespace t5de::protocol {
|
||||||
|
|
||||||
inline constexpr std::uint32_t version = 1;
|
inline constexpr std::uint32_t version = 2;
|
||||||
|
|
||||||
// A shared memory section the adapter creates for each engine. Its name is passed to the renderer process as
|
// Which part of the client's 3D view the renderer process draws (the client's own renderer draws all of it, under).
|
||||||
// `--channel <name>`; the event "<name>-frame" (auto-reset) is set after each frame the client draws.
|
enum class View : std::uint32_t {
|
||||||
|
Off, // none
|
||||||
|
Split, // the right half, to compare with the client's renderer on the left
|
||||||
|
Full,
|
||||||
|
};
|
||||||
|
|
||||||
|
// A shared memory section the adapter creates for each engine, named by the renderer process's `--channel <name>`
|
||||||
|
// argument: this header, then `capacity` bytes of data. The adapter sends the command stream (commands.hpp) through
|
||||||
|
// it in chunks: it waits for the event "<name>-consumed", writes a chunk and sets "<name>-ready"; the renderer
|
||||||
|
// process copies the chunk out and sets "<name>-consumed" (both auto-reset; consumed starts set).
|
||||||
struct Channel {
|
struct Channel {
|
||||||
std::uint32_t version;
|
std::uint32_t version;
|
||||||
std::uint32_t closing; // nonzero: the engine is gone, the renderer process should exit
|
std::uint32_t closing; // nonzero: the engine is gone, the renderer process should exit
|
||||||
std::uint64_t window; // IMVU's 3D window (an HWND; window handles are valid in every process)
|
std::uint64_t window; // IMVU's 3D window (an HWND; window handles are valid in every process)
|
||||||
std::uint64_t frames; // frames the client has drawn
|
std::uint64_t frames; // frames the client has drawn
|
||||||
|
View view;
|
||||||
|
std::uint32_t capacity;
|
||||||
|
std::uint32_t chunkSize;
|
||||||
|
std::uint32_t chunkFlags; // chunkEndsFrame
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(Channel) == 24, "same layout in 32-bit and 64-bit builds");
|
static_assert(sizeof(Channel) == 40, "same layout in 32-bit and 64-bit builds");
|
||||||
|
|
||||||
inline constexpr wchar_t frameEventSuffix[] = L"-frame";
|
inline constexpr std::uint32_t chunkEndsFrame = 1;
|
||||||
|
inline constexpr std::uint32_t channelCapacity = 16 * 1024 * 1024;
|
||||||
|
|
||||||
|
inline constexpr wchar_t readyEventSuffix[] = L"-ready";
|
||||||
|
inline constexpr wchar_t consumedEventSuffix[] = L"-consumed";
|
||||||
|
|
||||||
} // namespace t5de::protocol
|
} // namespace t5de::protocol
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
#pragma once
|
||||||
|
// The command stream: what the adapter records from IMVU's renderer interface, and the renderer process carries out.
|
||||||
|
// Each command is a Header, its fixed-size body, then for some a variable-size payload (pixels, vertex data),
|
||||||
|
// padded to 4 bytes. Built for 32-bit and 64-bit: fixed-size fields only.
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace t5de::protocol {
|
||||||
|
|
||||||
|
enum class Op : std::uint16_t {
|
||||||
|
BeginFrame = 1,
|
||||||
|
EndFrame,
|
||||||
|
Clear,
|
||||||
|
SetProjection,
|
||||||
|
SetModel,
|
||||||
|
SetView,
|
||||||
|
SetTextures,
|
||||||
|
SetState,
|
||||||
|
SetLight,
|
||||||
|
SetFog,
|
||||||
|
SetTextureTransform,
|
||||||
|
CreateTexture,
|
||||||
|
UpdateTexture,
|
||||||
|
DestroyTexture,
|
||||||
|
CreateBuffer,
|
||||||
|
UpdateBuffer,
|
||||||
|
DestroyBuffer,
|
||||||
|
DrawTriangles,
|
||||||
|
DrawArrays,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
Op op;
|
||||||
|
std::uint16_t reserved;
|
||||||
|
std::uint32_t size; // of the body and payload, padded to 4 bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
// Colors are 0xAARRGGBB. Resource ids are the adapter's, per engine; 0 means none.
|
||||||
|
|
||||||
|
struct BeginFrame {
|
||||||
|
std::uint32_t offscreen; // drawn into a render target, not the window: nothing to show
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EndFrame {
|
||||||
|
std::uint32_t unused;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Clear {
|
||||||
|
std::uint32_t flags; // 1: color, 2: depth (to 1)
|
||||||
|
std::uint32_t color;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The viewport's size, and a Direct3D projection matrix (row-major, row vectors).
|
||||||
|
struct SetProjection {
|
||||||
|
std::int32_t width;
|
||||||
|
std::int32_t height;
|
||||||
|
float m[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
// An affine transform (SetModel, SetView): a 3x3 matrix, row-major, then the translation.
|
||||||
|
struct SetTransform {
|
||||||
|
float m[12];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetTextures {
|
||||||
|
std::uint32_t texture0;
|
||||||
|
std::uint32_t texture1;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class State : std::uint32_t {
|
||||||
|
DepthTest,
|
||||||
|
DepthWrite,
|
||||||
|
Blending,
|
||||||
|
BlendMode, // 1: alpha, 2: additive, 3: premultiplied, 4: additive alpha
|
||||||
|
AlphaTest,
|
||||||
|
AlphaReference, // 0-255: pixels pass if their alpha is greater
|
||||||
|
Culling,
|
||||||
|
NormalInverting, // flips which winding faces front
|
||||||
|
Lighting,
|
||||||
|
Fog,
|
||||||
|
Ambient, // a color
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetState {
|
||||||
|
State state;
|
||||||
|
std::uint32_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetLight {
|
||||||
|
std::uint32_t index;
|
||||||
|
std::uint32_t enabled;
|
||||||
|
std::uint32_t color;
|
||||||
|
std::uint32_t directional; // 0: a point light at vector; 1: light coming from direction vector
|
||||||
|
float vector[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetFog {
|
||||||
|
float start;
|
||||||
|
float end;
|
||||||
|
std::uint32_t color;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Texture coordinates for a stage: u' = u * m[0] + v * m[1] + m[4], v' = u * m[2] + v * m[3] + m[5].
|
||||||
|
struct SetTextureTransform {
|
||||||
|
std::uint32_t stage;
|
||||||
|
std::uint32_t identity;
|
||||||
|
float m[6];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Payload: width * height pixels (0xAARRGGBB), rows packed.
|
||||||
|
struct CreateTexture {
|
||||||
|
std::uint32_t id;
|
||||||
|
std::uint32_t width;
|
||||||
|
std::uint32_t height;
|
||||||
|
std::uint32_t flags; // 2: wrap u, 4: wrap v (else clamp)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UpdateTexture {
|
||||||
|
std::uint32_t id;
|
||||||
|
std::uint32_t width;
|
||||||
|
std::uint32_t height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DestroyTexture {
|
||||||
|
std::uint32_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class BufferKind : std::uint32_t {
|
||||||
|
Index, // 16-bit indices
|
||||||
|
Vertex, // positions and normals (3 floats), texture coordinates (2 floats) or colors (0xAARRGGBB)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Payload: count elements of elementSize bytes, packed; empty for a buffer filled later (UpdateBuffer).
|
||||||
|
struct CreateBuffer {
|
||||||
|
std::uint32_t id;
|
||||||
|
BufferKind kind;
|
||||||
|
std::uint32_t elementSize;
|
||||||
|
std::uint32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UpdateBuffer {
|
||||||
|
std::uint32_t id;
|
||||||
|
std::uint32_t elementSize;
|
||||||
|
std::uint32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DestroyBuffer {
|
||||||
|
std::uint32_t id;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Triangles from buffers: indices and positions are required, the others optional.
|
||||||
|
struct DrawTriangles {
|
||||||
|
std::uint32_t indices;
|
||||||
|
std::uint32_t positions;
|
||||||
|
std::uint32_t normals;
|
||||||
|
std::uint32_t texCoords;
|
||||||
|
std::uint32_t colors;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Triangles from data in the payload, packed: indexCount 16-bit indices (padded to 4 bytes), then vertexCount
|
||||||
|
// positions, and normals, texture coordinates and colors if present.
|
||||||
|
struct DrawArrays {
|
||||||
|
std::uint32_t vertexCount;
|
||||||
|
std::uint32_t indexCount;
|
||||||
|
std::uint32_t hasNormals;
|
||||||
|
std::uint32_t hasTexCoords;
|
||||||
|
std::uint32_t hasColors;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(Header) == 8 && sizeof(SetProjection) == 72 && sizeof(SetLight) == 28
|
||||||
|
&& sizeof(DrawArrays) == 20);
|
||||||
|
|
||||||
|
inline constexpr std::size_t padded(std::size_t size) { return (size + 3) & ~std::size_t{3}; }
|
||||||
|
|
||||||
|
// Appends commands to a byte buffer.
|
||||||
|
class Writer {
|
||||||
|
public:
|
||||||
|
// Appends a command and returns its payload, payloadSize bytes for the caller to fill.
|
||||||
|
template <class Body>
|
||||||
|
std::byte* add(Op op, const Body& body, std::size_t payloadSize = 0)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(Body) % 4 == 0);
|
||||||
|
const std::size_t size = sizeof(Body) + padded(payloadSize);
|
||||||
|
const std::size_t start = bytes_.size();
|
||||||
|
bytes_.resize(start + sizeof(Header) + size);
|
||||||
|
const Header header{op, 0, static_cast<std::uint32_t>(size)};
|
||||||
|
std::memcpy(bytes_.data() + start, &header, sizeof(Header));
|
||||||
|
std::memcpy(bytes_.data() + start + sizeof(Header), &body, sizeof(Body));
|
||||||
|
return bytes_.data() + start + sizeof(Header) + sizeof(Body);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const std::byte> bytes() const { return bytes_; }
|
||||||
|
[[nodiscard]] std::size_t size() const { return bytes_.size(); }
|
||||||
|
void clear() { bytes_.clear(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::byte> bytes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// One command, as read back.
|
||||||
|
class Command {
|
||||||
|
public:
|
||||||
|
Command(Op op, std::span<const std::byte> data) : op_(op), data_(data) {}
|
||||||
|
|
||||||
|
[[nodiscard]] Op op() const { return op_; }
|
||||||
|
|
||||||
|
// The body, if the command is long enough to hold one.
|
||||||
|
template <class Body>
|
||||||
|
[[nodiscard]] std::optional<Body> body() const
|
||||||
|
{
|
||||||
|
if (data_.size() < sizeof(Body)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
Body body;
|
||||||
|
std::memcpy(&body, data_.data(), sizeof(Body));
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Body>
|
||||||
|
[[nodiscard]] std::span<const std::byte> payload() const
|
||||||
|
{
|
||||||
|
return data_.size() < sizeof(Body) ? std::span<const std::byte>{} : data_.subspan(sizeof(Body));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Op op_;
|
||||||
|
std::span<const std::byte> data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reads commands back from a byte buffer; stops at the end, or at a truncated command.
|
||||||
|
class Reader {
|
||||||
|
public:
|
||||||
|
explicit Reader(std::span<const std::byte> bytes) : bytes_(bytes) {}
|
||||||
|
|
||||||
|
std::optional<Command> next()
|
||||||
|
{
|
||||||
|
if (bytes_.size() < sizeof(Header)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
Header header;
|
||||||
|
std::memcpy(&header, bytes_.data(), sizeof(Header));
|
||||||
|
if (bytes_.size() - sizeof(Header) < header.size) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
Command command(header.op, bytes_.subspan(sizeof(Header), header.size));
|
||||||
|
bytes_ = bytes_.subspan(sizeof(Header) + header.size);
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::span<const std::byte> bytes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace t5de::protocol
|
||||||
+14
-1
@@ -20,8 +20,21 @@ FetchContent_Declare(DiligentCore
|
|||||||
SYSTEM)
|
SYSTEM)
|
||||||
FetchContent_MakeAvailable(DiligentCore)
|
FetchContent_MakeAvailable(DiligentCore)
|
||||||
|
|
||||||
add_executable(t5de_renderer WIN32 main.cpp)
|
# Shader sources, embedded in the program (Diligent compiles them at run time): each shaders/<name>.hlsl becomes
|
||||||
|
# <name>_hlsl.hpp, defining <camelCaseName>Hlsl.
|
||||||
|
function(t5de_embed_shader file name)
|
||||||
|
set(T5DE_SHADER_FILE ${file})
|
||||||
|
set(T5DE_SHADER_NAME ${name})
|
||||||
|
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/shaders/${file} T5DE_SHADER_SOURCE)
|
||||||
|
get_filename_component(stem ${file} NAME_WE)
|
||||||
|
configure_file(shaders/embed.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/generated/${stem}_hlsl.hpp @ONLY)
|
||||||
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS shaders/${file})
|
||||||
|
endfunction()
|
||||||
|
t5de_embed_shader(fixed_function.hlsl fixedFunctionHlsl)
|
||||||
|
|
||||||
|
add_executable(t5de_renderer WIN32 main.cpp scene.cpp)
|
||||||
set_target_properties(t5de_renderer PROPERTIES OUTPUT_NAME t5de-renderer)
|
set_target_properties(t5de_renderer PROPERTIES OUTPUT_NAME t5de-renderer)
|
||||||
|
target_include_directories(t5de_renderer PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
|
||||||
target_link_libraries(t5de_renderer PRIVATE t5de::protocol t5de::options Diligent-GraphicsEngineD3D11-static Diligent-Common)
|
target_link_libraries(t5de_renderer PRIVATE t5de::protocol t5de::options Diligent-GraphicsEngineD3D11-static Diligent-Common)
|
||||||
|
|
||||||
install(TARGETS t5de_renderer RUNTIME DESTINATION .)
|
install(TARGETS t5de_renderer RUNTIME DESTINATION .)
|
||||||
+132
-25
@@ -1,31 +1,63 @@
|
|||||||
// t5de-renderer.exe: the renderer process. Started by the adapter (t5deRenderEngine.dll) for each of the client's
|
// t5de-renderer.exe: the renderer process. Started by the adapter (t5deRenderEngine.dll) for each of the client's
|
||||||
// 3D views; draws into a child window it places in the client's window, frame by frame as the client draws.
|
// 3D views: receives the client's drawing as commands through a shared memory channel, and draws it with Diligent
|
||||||
//
|
// into a child window it places in the client's window (over the right half, for now, to compare with the client's
|
||||||
// For now a spike proving the architecture: a small box in the 3D view's corner, its color changing with each frame
|
// own renderer on the left).
|
||||||
// the client draws, drawn with Diligent (Direct3D 11) from this 64-bit process.
|
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <share.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <format>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "EngineFactoryD3D11.h"
|
#include "EngineFactoryD3D11.h"
|
||||||
#include "RefCntAutoPtr.hpp"
|
#include "RefCntAutoPtr.hpp"
|
||||||
|
#include "scene.hpp"
|
||||||
#include "t5de/protocol/channel.hpp"
|
#include "t5de/protocol/channel.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using namespace Diligent;
|
using namespace Diligent;
|
||||||
|
using t5de::protocol::Channel;
|
||||||
|
using t5de::protocol::View;
|
||||||
|
|
||||||
// The box: where in the client's 3D view, and how big.
|
// The renderer processes' log: %TEMP%\t5de-renderer-process.log, shared by all of them (lines start with the pid).
|
||||||
constexpr int boxX = 16;
|
void write_log(std::string_view line)
|
||||||
constexpr int boxY = 16;
|
{
|
||||||
constexpr int boxWidth = 240;
|
static std::FILE* file = [] {
|
||||||
constexpr int boxHeight = 80;
|
wchar_t temp[MAX_PATH + 1];
|
||||||
|
const DWORD length = GetTempPathW(MAX_PATH + 1, temp);
|
||||||
|
return length == 0 || length > MAX_PATH
|
||||||
|
? nullptr
|
||||||
|
: _wfsopen((std::filesystem::path(temp) / L"t5de-renderer-process.log").c_str(), L"a", _SH_DENYNO);
|
||||||
|
}();
|
||||||
|
if (file != nullptr) {
|
||||||
|
std::fprintf(file, "%lu: %.*s\n", GetCurrentProcessId(), static_cast<int>(line.size()), line.data());
|
||||||
|
std::fflush(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class... Args>
|
||||||
|
void log(std::format_string<Args...> format, Args&&... args)
|
||||||
|
{
|
||||||
|
write_log(std::format(format, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diligent's messages (shader compilation errors, invalid states, ...), into the log.
|
||||||
|
void DILIGENT_CALL_TYPE diligent_message(DEBUG_MESSAGE_SEVERITY severity, const Char* message, const Char* function,
|
||||||
|
const Char*, int)
|
||||||
|
{
|
||||||
|
static constexpr const char* severities[] = {"info", "warning", "error", "fatal error"};
|
||||||
|
log("Diligent {}: {}{}{}", severities[severity < 4 ? severity : 3], function != nullptr ? function : "",
|
||||||
|
function != nullptr ? ": " : "", message != nullptr ? message : "");
|
||||||
|
}
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
std::wstring channel;
|
std::wstring channel;
|
||||||
@@ -67,7 +99,18 @@ HWND create_child_window(HWND parent)
|
|||||||
.lpszClassName = L"T5DERenderer"};
|
.lpszClassName = L"T5DERenderer"};
|
||||||
RegisterClassExW(&windowClass);
|
RegisterClassExW(&windowClass);
|
||||||
return CreateWindowExW(0, windowClass.lpszClassName, L"", WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_CLIPSIBLINGS,
|
return CreateWindowExW(0, windowClass.lpszClassName, L"", WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_CLIPSIBLINGS,
|
||||||
boxX, boxY, boxWidth, boxHeight, parent, nullptr, windowClass.hInstance, nullptr);
|
0, 0, 1, 1, parent, nullptr, windowClass.hInstance, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where the child window goes in the client's 3D view.
|
||||||
|
RECT placement(HWND parent, View view)
|
||||||
|
{
|
||||||
|
RECT client{};
|
||||||
|
GetClientRect(parent, &client);
|
||||||
|
if (view == View::Split) {
|
||||||
|
client.left = client.right / 2;
|
||||||
|
}
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -80,37 +123,65 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
|||||||
}
|
}
|
||||||
HANDLE mapping = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, arguments.channel.c_str());
|
HANDLE mapping = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, arguments.channel.c_str());
|
||||||
auto* channel = mapping == nullptr ? nullptr
|
auto* channel = mapping == nullptr ? nullptr
|
||||||
: static_cast<t5de::protocol::Channel*>(MapViewOfFile(
|
: static_cast<Channel*>(MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0));
|
||||||
mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(t5de::protocol::Channel)));
|
HANDLE ready = OpenEventW(SYNCHRONIZE, FALSE, (arguments.channel + t5de::protocol::readyEventSuffix).c_str());
|
||||||
HANDLE frameEvent = OpenEventW(SYNCHRONIZE, FALSE, (arguments.channel + t5de::protocol::frameEventSuffix).c_str());
|
HANDLE consumed =
|
||||||
|
OpenEventW(EVENT_MODIFY_STATE, FALSE, (arguments.channel + t5de::protocol::consumedEventSuffix).c_str());
|
||||||
HANDLE parent = OpenProcess(SYNCHRONIZE, FALSE, arguments.parent);
|
HANDLE parent = OpenProcess(SYNCHRONIZE, FALSE, arguments.parent);
|
||||||
if (channel == nullptr || frameEvent == nullptr || parent == nullptr || channel->version != t5de::protocol::version) {
|
if (channel == nullptr || ready == nullptr || consumed == nullptr || parent == nullptr
|
||||||
|
|| channel->version != t5de::protocol::version) {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
const auto* data = reinterpret_cast<const std::byte*>(channel + 1);
|
||||||
|
|
||||||
HWND window = create_child_window(reinterpret_cast<HWND>(static_cast<std::uintptr_t>(channel->window)));
|
const HWND clientWindow = reinterpret_cast<HWND>(static_cast<std::uintptr_t>(channel->window));
|
||||||
|
const HWND window = create_child_window(clientWindow);
|
||||||
if (window == nullptr) {
|
if (window == nullptr) {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log("started for window {:#x}, view {}", channel->window, static_cast<int>(channel->view));
|
||||||
IEngineFactoryD3D11* factory = GetEngineFactoryD3D11();
|
IEngineFactoryD3D11* factory = GetEngineFactoryD3D11();
|
||||||
|
factory->SetMessageCallback(diligent_message);
|
||||||
RefCntAutoPtr<IRenderDevice> device;
|
RefCntAutoPtr<IRenderDevice> device;
|
||||||
RefCntAutoPtr<IDeviceContext> context;
|
RefCntAutoPtr<IDeviceContext> context;
|
||||||
EngineD3D11CreateInfo engineInfo;
|
EngineD3D11CreateInfo engineInfo;
|
||||||
factory->CreateDeviceAndContextsD3D11(engineInfo, &device, &context);
|
factory->CreateDeviceAndContextsD3D11(engineInfo, &device, &context);
|
||||||
if (!device) {
|
if (!device) {
|
||||||
|
log("can't create a Direct3D 11 device");
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
RefCntAutoPtr<ISwapChain> swapChain;
|
RefCntAutoPtr<ISwapChain> swapChain;
|
||||||
SwapChainDesc swapChainDesc;
|
SwapChainDesc swapChainDesc;
|
||||||
|
swapChainDesc.ColorBufferFormat = TEX_FORMAT_RGBA8_UNORM; // no sRGB conversion, like the client's renderer
|
||||||
|
swapChainDesc.DepthBufferFormat = TEX_FORMAT_D32_FLOAT;
|
||||||
factory->CreateSwapChainD3D11(device, context, swapChainDesc, FullScreenModeDesc{}, Win32NativeWindow{window},
|
factory->CreateSwapChainD3D11(device, context, swapChainDesc, FullScreenModeDesc{}, Win32NativeWindow{window},
|
||||||
&swapChain);
|
&swapChain);
|
||||||
if (!swapChain) {
|
if (!swapChain) {
|
||||||
|
log("can't create a swap chain");
|
||||||
return 6;
|
return 6;
|
||||||
}
|
}
|
||||||
|
t5de::Scene scene(device, context, swapChain);
|
||||||
|
|
||||||
|
// Takes the chunk waiting in the channel (after "ready"), and lets the adapter send the next: appends it to
|
||||||
|
// frame, and returns whether it ends a frame.
|
||||||
|
std::vector<std::byte> frame;
|
||||||
|
const auto take_chunk = [&] {
|
||||||
|
const std::uint32_t size = channel->chunkSize <= channel->capacity ? channel->chunkSize : 0;
|
||||||
|
const bool endsFrame = (channel->chunkFlags & t5de::protocol::chunkEndsFrame) != 0;
|
||||||
|
frame.insert(frame.end(), data, data + size);
|
||||||
|
SetEvent(consumed);
|
||||||
|
return endsFrame;
|
||||||
|
};
|
||||||
|
|
||||||
|
LARGE_INTEGER frequency;
|
||||||
|
QueryPerformanceFrequency(&frequency);
|
||||||
|
double drawTime = 0;
|
||||||
|
int drawn = 0;
|
||||||
|
int skipped = 0;
|
||||||
|
RECT placed{};
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const HANDLE handles[] = {frameEvent, parent};
|
const HANDLE handles[] = {ready, parent};
|
||||||
const DWORD result = MsgWaitForMultipleObjects(2, handles, FALSE, 1000, QS_ALLINPUT);
|
const DWORD result = MsgWaitForMultipleObjects(2, handles, FALSE, 1000, QS_ALLINPUT);
|
||||||
MSG message;
|
MSG message;
|
||||||
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
|
while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) {
|
||||||
@@ -119,19 +190,55 @@ int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
|
|||||||
if (result == WAIT_OBJECT_0 + 1 || channel->closing != 0) {
|
if (result == WAIT_OBJECT_0 + 1 || channel->closing != 0) {
|
||||||
break; // the client exited, or the engine is gone
|
break; // the client exited, or the engine is gone
|
||||||
}
|
}
|
||||||
if (result != WAIT_OBJECT_0) {
|
if (result != WAIT_OBJECT_0 || !take_chunk()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// T5DE gold, pulsing with the client's frames.
|
// If the client has sent more frames meanwhile (drawing fell behind), catch up: carry out the older ones'
|
||||||
const float pulse = 0.5f + 0.5f * std::sin(static_cast<float>(channel->frames) * 0.1f);
|
// changes without drawing them, and draw only the newest. (An incomplete frame stays for the next round.)
|
||||||
const float color[] = {1.0f, 0.82f * pulse + 0.1f, 0.0f, 1.0f};
|
std::size_t complete = frame.size();
|
||||||
ITextureView* backBuffer = swapChain->GetCurrentBackBufferRTV();
|
while (WaitForSingleObject(ready, 0) == WAIT_OBJECT_0) {
|
||||||
context->SetRenderTargets(1, &backBuffer, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
bool ended = take_chunk();
|
||||||
context->ClearRenderTarget(backBuffer, color, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
while (!ended && WaitForSingleObject(ready, 1000) == WAIT_OBJECT_0) {
|
||||||
swapChain->Present(0);
|
ended = take_chunk();
|
||||||
|
}
|
||||||
|
if (!ended) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scene.execute(std::span(frame).first(complete), 0, false);
|
||||||
|
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
||||||
|
complete = frame.size();
|
||||||
|
++skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Follow the client's 3D view's size.
|
||||||
|
const RECT place = placement(clientWindow, channel->view);
|
||||||
|
const int width = place.right - place.left;
|
||||||
|
const int height = place.bottom - place.top;
|
||||||
|
if (std::memcmp(&place, &placed, sizeof(RECT)) != 0 && width > 0 && height > 0) {
|
||||||
|
SetWindowPos(window, nullptr, place.left, place.top, width, height, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||||
|
swapChain->Resize(static_cast<Uint32>(width), static_cast<Uint32>(height));
|
||||||
|
placed = place;
|
||||||
|
log("drawing {}x{} at {} in the client's 3D view", width, height, place.left);
|
||||||
|
}
|
||||||
|
LARGE_INTEGER start, end;
|
||||||
|
QueryPerformanceCounter(&start);
|
||||||
|
if (scene.execute(std::span(frame).first(complete), place.left, width > 0 && height > 0)) {
|
||||||
|
swapChain->Present(0);
|
||||||
|
QueryPerformanceCounter(&end);
|
||||||
|
drawTime += static_cast<double>(end.QuadPart - start.QuadPart) / static_cast<double>(frequency.QuadPart);
|
||||||
|
if (++drawn == 300) {
|
||||||
|
log("last 300 frames: {:.1f} ms each to draw and present, {} skipped to catch up", drawTime * 1000 / drawn,
|
||||||
|
skipped);
|
||||||
|
drawTime = 0;
|
||||||
|
drawn = 0;
|
||||||
|
skipped = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame.erase(frame.begin(), frame.begin() + static_cast<std::ptrdiff_t>(complete));
|
||||||
|
}
|
||||||
|
|
||||||
|
log("exiting");
|
||||||
DestroyWindow(window);
|
DestroyWindow(window);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,612 @@
|
|||||||
|
#include "scene.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "fixed_function_hlsl.hpp"
|
||||||
|
|
||||||
|
namespace t5de {
|
||||||
|
|
||||||
|
using namespace Diligent;
|
||||||
|
using protocol::Op;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void set_identity(float (&m)[16])
|
||||||
|
{
|
||||||
|
std::memset(m, 0, sizeof(m));
|
||||||
|
m[0] = m[5] = m[10] = m[15] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An affine transform (3x3 row-major, then translation) as a 4x4 matrix for row vectors.
|
||||||
|
void set_transform(float (&m)[16], const float (&t)[12])
|
||||||
|
{
|
||||||
|
for (int row = 0; row < 3; ++row) {
|
||||||
|
m[row * 4 + 0] = t[row * 3 + 0];
|
||||||
|
m[row * 4 + 1] = t[row * 3 + 1];
|
||||||
|
m[row * 4 + 2] = t[row * 3 + 2];
|
||||||
|
m[row * 4 + 3] = 0;
|
||||||
|
}
|
||||||
|
m[12] = t[9];
|
||||||
|
m[13] = t[10];
|
||||||
|
m[14] = t[11];
|
||||||
|
m[15] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// u' = u * m[0] + v * m[1] + m[4], v' = u * m[2] + v * m[3] + m[5], as two rows dotted with (u, v, 1).
|
||||||
|
void set_texture_transform(float (&rows)[8], const float (&m)[6])
|
||||||
|
{
|
||||||
|
const float values[8] = {m[0], m[1], m[4], 0, m[2], m[3], m[5], 0};
|
||||||
|
std::memcpy(rows, values, sizeof(rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_color(float (&rgba)[4], std::uint32_t color)
|
||||||
|
{
|
||||||
|
rgba[0] = static_cast<float>((color >> 16) & 0xff) / 255.0f;
|
||||||
|
rgba[1] = static_cast<float>((color >> 8) & 0xff) / 255.0f;
|
||||||
|
rgba[2] = static_cast<float>(color & 0xff) / 255.0f;
|
||||||
|
rgba[3] = static_cast<float>(color >> 24) / 255.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::uint32_t hasNormals = 1;
|
||||||
|
constexpr std::uint32_t hasTexCoords = 2;
|
||||||
|
constexpr std::uint32_t hasColors = 4;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Scene::Scene(IRenderDevice* device, IDeviceContext* context, ISwapChain* swapChain)
|
||||||
|
: device_(device), context_(context), swapChain_(swapChain)
|
||||||
|
{
|
||||||
|
BufferDesc constants;
|
||||||
|
constants.Name = "Constants";
|
||||||
|
constants.Size = sizeof(Constants);
|
||||||
|
constants.Usage = USAGE_DYNAMIC;
|
||||||
|
constants.BindFlags = BIND_UNIFORM_BUFFER;
|
||||||
|
constants.CPUAccessFlags = CPU_ACCESS_WRITE;
|
||||||
|
device_->CreateBuffer(constants, nullptr, &constantBuffer_);
|
||||||
|
|
||||||
|
// IMVU's textures: bilinear, no mipmaps, wrapped or clamped per texture.
|
||||||
|
for (int wrap = 0; wrap < 4; ++wrap) {
|
||||||
|
SamplerDesc sampler;
|
||||||
|
sampler.MinFilter = FILTER_TYPE_LINEAR;
|
||||||
|
sampler.MagFilter = FILTER_TYPE_LINEAR;
|
||||||
|
sampler.MipFilter = FILTER_TYPE_POINT;
|
||||||
|
sampler.AddressU = (wrap & 1) != 0 ? TEXTURE_ADDRESS_WRAP : TEXTURE_ADDRESS_CLAMP;
|
||||||
|
sampler.AddressV = (wrap & 2) != 0 ? TEXTURE_ADDRESS_WRAP : TEXTURE_ADDRESS_CLAMP;
|
||||||
|
device_->CreateSampler(sampler, &samplers_[wrap]);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_identity(constants_.world);
|
||||||
|
set_identity(constants_.view);
|
||||||
|
set_identity(constants_.projection);
|
||||||
|
const float identity[6] = {1, 0, 0, 1, 0, 0};
|
||||||
|
set_texture_transform(constants_.texTransform0, identity);
|
||||||
|
set_texture_transform(constants_.texTransform1, identity);
|
||||||
|
constants_.flags[2] = 127; // the client's alpha reference
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Scene::execute(std::span<const std::byte> commands, int offsetX, bool draw)
|
||||||
|
{
|
||||||
|
offsetX_ = offsetX;
|
||||||
|
draw_ = draw;
|
||||||
|
bool drew = false;
|
||||||
|
protocol::Reader reader(commands);
|
||||||
|
while (const auto command = reader.next()) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::EndFrame:
|
||||||
|
drew = drew || onscreen_;
|
||||||
|
onscreen_ = false;
|
||||||
|
break;
|
||||||
|
case Op::Clear:
|
||||||
|
if (const auto clear = command->body<protocol::Clear>(); clear && onscreen_) {
|
||||||
|
if ((clear->flags & 1) != 0) {
|
||||||
|
float color[4];
|
||||||
|
set_color(color, clear->color);
|
||||||
|
context_->ClearRenderTarget(swapChain_->GetCurrentBackBufferRTV(), color,
|
||||||
|
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||||
|
}
|
||||||
|
if ((clear->flags & 2) != 0) {
|
||||||
|
context_->ClearDepthStencil(swapChain_->GetDepthBufferDSV(), CLEAR_DEPTH_FLAG, 1.0f, 0,
|
||||||
|
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::SetProjection:
|
||||||
|
if (const auto projection = command->body<protocol::SetProjection>()) {
|
||||||
|
setProjection(*projection);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::SetModel:
|
||||||
|
if (const auto model = command->body<protocol::SetTransform>()) {
|
||||||
|
set_transform(constants_.world, model->m);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::SetView:
|
||||||
|
if (const auto view = command->body<protocol::SetTransform>()) {
|
||||||
|
set_transform(constants_.view, view->m);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::SetTextures:
|
||||||
|
if (const auto textures = command->body<protocol::SetTextures>()) {
|
||||||
|
state_.texture0 = textures->texture0;
|
||||||
|
state_.texture1 = textures->texture1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::SetState:
|
||||||
|
if (const auto state = command->body<protocol::SetState>()) {
|
||||||
|
setState(*state);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::SetLight:
|
||||||
|
if (const auto light = command->body<protocol::SetLight>(); light && light->index < 8) {
|
||||||
|
set_color(constants_.lightColor[light->index], light->color);
|
||||||
|
constants_.lightColor[light->index][3] = light->enabled != 0 ? 1.0f : 0.0f;
|
||||||
|
std::memcpy(constants_.lightVector[light->index], light->vector, sizeof(light->vector));
|
||||||
|
constants_.lightVector[light->index][3] = light->directional != 0 ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::SetFog:
|
||||||
|
if (const auto fog = command->body<protocol::SetFog>()) {
|
||||||
|
constants_.fog[0] = fog->start;
|
||||||
|
constants_.fog[1] = fog->end;
|
||||||
|
set_color(constants_.fogColor, fog->color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::SetTextureTransform:
|
||||||
|
if (const auto transform = command->body<protocol::SetTextureTransform>(); transform && transform->stage < 2) {
|
||||||
|
set_texture_transform(transform->stage == 0 ? constants_.texTransform0 : constants_.texTransform1,
|
||||||
|
transform->m);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::CreateTexture:
|
||||||
|
createTexture(*command);
|
||||||
|
break;
|
||||||
|
case Op::UpdateTexture:
|
||||||
|
updateTexture(*command);
|
||||||
|
break;
|
||||||
|
case Op::DestroyTexture:
|
||||||
|
if (const auto destroy = command->body<protocol::DestroyTexture>()) {
|
||||||
|
textures_.erase(destroy->id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::CreateBuffer:
|
||||||
|
createBuffer(*command);
|
||||||
|
break;
|
||||||
|
case Op::UpdateBuffer:
|
||||||
|
updateBuffer(*command);
|
||||||
|
break;
|
||||||
|
case Op::DestroyBuffer:
|
||||||
|
if (const auto destroy = command->body<protocol::DestroyBuffer>()) {
|
||||||
|
buffers_.erase(destroy->id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::DrawTriangles:
|
||||||
|
if (const auto triangles = command->body<protocol::DrawTriangles>(); triangles && onscreen_) {
|
||||||
|
drawTriangles(*triangles);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Op::DrawArrays:
|
||||||
|
if (onscreen_) {
|
||||||
|
drawArrays(*command);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drew;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::setProjection(const protocol::SetProjection& projection)
|
||||||
|
{
|
||||||
|
std::memcpy(constants_.projection, projection.m, sizeof(constants_.projection));
|
||||||
|
if (!onscreen_) {
|
||||||
|
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.TopLeftY = 0;
|
||||||
|
viewport.Width = static_cast<float>(projection.width);
|
||||||
|
viewport.Height = static_cast<float>(projection.height);
|
||||||
|
viewport.MinDepth = 0;
|
||||||
|
viewport.MaxDepth = 1;
|
||||||
|
context_->SetViewports(1, &viewport, window.Width, window.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::setState(const protocol::SetState& state)
|
||||||
|
{
|
||||||
|
const bool on = state.value != 0;
|
||||||
|
switch (state.state) {
|
||||||
|
case protocol::State::DepthTest:
|
||||||
|
state_.depthTest = on;
|
||||||
|
break;
|
||||||
|
case protocol::State::DepthWrite:
|
||||||
|
state_.depthWrite = on;
|
||||||
|
break;
|
||||||
|
case protocol::State::Blending:
|
||||||
|
state_.blending = on;
|
||||||
|
break;
|
||||||
|
case protocol::State::BlendMode:
|
||||||
|
if (state.value >= 1 && state.value <= 4) {
|
||||||
|
state_.blendMode = state.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case protocol::State::AlphaTest:
|
||||||
|
constants_.flags[1] = on ? 1.0f : 0.0f;
|
||||||
|
break;
|
||||||
|
case protocol::State::AlphaReference:
|
||||||
|
constants_.flags[2] = static_cast<float>(state.value);
|
||||||
|
break;
|
||||||
|
case protocol::State::Culling:
|
||||||
|
state_.culling = on;
|
||||||
|
break;
|
||||||
|
case protocol::State::NormalInverting:
|
||||||
|
state_.normalInverting = on;
|
||||||
|
break;
|
||||||
|
case protocol::State::Lighting:
|
||||||
|
constants_.flags[0] = on ? 1.0f : 0.0f;
|
||||||
|
break;
|
||||||
|
case protocol::State::Fog:
|
||||||
|
constants_.fog[2] = on ? 1.0f : 0.0f;
|
||||||
|
break;
|
||||||
|
case protocol::State::Ambient:
|
||||||
|
set_color(constants_.ambient, state.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::createTexture(const protocol::Command& command)
|
||||||
|
{
|
||||||
|
const auto create = command.body<protocol::CreateTexture>();
|
||||||
|
const auto pixels = command.payload<protocol::CreateTexture>();
|
||||||
|
if (!create || create->width == 0 || create->height == 0
|
||||||
|
|| pixels.size() < std::size_t{create->width} * create->height * 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TextureDesc desc;
|
||||||
|
desc.Name = "Client texture";
|
||||||
|
desc.Type = RESOURCE_DIM_TEX_2D;
|
||||||
|
desc.Width = create->width;
|
||||||
|
desc.Height = create->height;
|
||||||
|
desc.MipLevels = 1;
|
||||||
|
desc.Format = TEX_FORMAT_BGRA8_UNORM; // 0xAARRGGBB in memory
|
||||||
|
desc.BindFlags = BIND_SHADER_RESOURCE;
|
||||||
|
desc.Usage = USAGE_DEFAULT;
|
||||||
|
TextureSubResData level;
|
||||||
|
level.pData = pixels.data();
|
||||||
|
level.Stride = std::uint64_t{create->width} * 4;
|
||||||
|
TextureData data;
|
||||||
|
data.pSubResources = &level;
|
||||||
|
data.NumSubresources = 1;
|
||||||
|
Texture texture{.width = create->width, .height = create->height, .flags = create->flags};
|
||||||
|
device_->CreateTexture(desc, &data, &texture.texture);
|
||||||
|
if (!texture.texture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
texture.texture->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE)
|
||||||
|
->SetSampler(samplers_[((create->flags & 2) != 0 ? 1 : 0) | ((create->flags & 4) != 0 ? 2 : 0)]);
|
||||||
|
textures_[create->id] = std::move(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::updateTexture(const protocol::Command& command)
|
||||||
|
{
|
||||||
|
const auto update = command.body<protocol::UpdateTexture>();
|
||||||
|
const auto pixels = command.payload<protocol::UpdateTexture>();
|
||||||
|
if (!update || pixels.size() < std::size_t{update->width} * update->height * 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto found = textures_.find(update->id);
|
||||||
|
if (found == textures_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Texture& texture = found->second;
|
||||||
|
if (texture.width != update->width || texture.height != update->height) {
|
||||||
|
// A different size: make it again, keeping its sampling.
|
||||||
|
protocol::Writer writer;
|
||||||
|
std::memcpy(writer.add(Op::CreateTexture,
|
||||||
|
protocol::CreateTexture{update->id, update->width, update->height, texture.flags},
|
||||||
|
pixels.size()),
|
||||||
|
pixels.data(), pixels.size());
|
||||||
|
protocol::Reader reader(writer.bytes());
|
||||||
|
createTexture(*reader.next());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Box box;
|
||||||
|
box.MaxX = update->width;
|
||||||
|
box.MaxY = update->height;
|
||||||
|
TextureSubResData level;
|
||||||
|
level.pData = pixels.data();
|
||||||
|
level.Stride = std::uint64_t{update->width} * 4;
|
||||||
|
context_->UpdateTexture(texture.texture, 0, 0, box, level, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
|
||||||
|
RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::createBuffer(const protocol::Command& command)
|
||||||
|
{
|
||||||
|
const auto create = command.body<protocol::CreateBuffer>();
|
||||||
|
if (!create || create->count == 0 || create->elementSize == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::size_t size = std::size_t{create->elementSize} * create->count;
|
||||||
|
const auto contents = command.payload<protocol::CreateBuffer>();
|
||||||
|
const bool filled = contents.size() >= size;
|
||||||
|
BufferDesc desc;
|
||||||
|
desc.Name = create->kind == protocol::BufferKind::Index ? "Client indices" : "Client vertices";
|
||||||
|
desc.Size = size;
|
||||||
|
desc.BindFlags = create->kind == protocol::BufferKind::Index ? BIND_INDEX_BUFFER : BIND_VERTEX_BUFFER;
|
||||||
|
desc.Usage = filled ? USAGE_IMMUTABLE : USAGE_DEFAULT; // dynamic buffers are filled by updates
|
||||||
|
BufferData data;
|
||||||
|
data.pData = contents.data();
|
||||||
|
data.DataSize = size;
|
||||||
|
Buffer buffer{.kind = create->kind, .elementSize = create->elementSize, .count = create->count};
|
||||||
|
device_->CreateBuffer(desc, filled ? &data : nullptr, &buffer.buffer);
|
||||||
|
if (buffer.buffer) {
|
||||||
|
buffers_[create->id] = std::move(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::updateBuffer(const protocol::Command& command)
|
||||||
|
{
|
||||||
|
const auto update = command.body<protocol::UpdateBuffer>();
|
||||||
|
const auto contents = command.payload<protocol::UpdateBuffer>();
|
||||||
|
if (!update) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto found = buffers_.find(update->id);
|
||||||
|
const std::size_t size = std::size_t{update->elementSize} * update->count;
|
||||||
|
if (found == buffers_.end() || contents.size() < size || found->second.elementSize != update->elementSize
|
||||||
|
|| found->second.count != update->count || found->second.buffer->GetDesc().Usage != USAGE_DEFAULT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context_->UpdateBuffer(found->second.buffer, 0, size, contents.data(), RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
ITextureView* Scene::textureView(std::uint32_t id)
|
||||||
|
{
|
||||||
|
const auto found = textures_.find(id);
|
||||||
|
return found == textures_.end() ? nullptr : found->second.texture->GetDefaultView(TEXTURE_VIEW_SHADER_RESOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::drawTriangles(const protocol::DrawTriangles& draw)
|
||||||
|
{
|
||||||
|
const auto buffer = [this](std::uint32_t id, protocol::BufferKind kind, std::uint32_t elementSize) -> Buffer* {
|
||||||
|
const auto found = buffers_.find(id);
|
||||||
|
if (id == 0 || found == buffers_.end() || found->second.kind != kind || found->second.elementSize != elementSize) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &found->second;
|
||||||
|
};
|
||||||
|
Buffer* indices = buffer(draw.indices, protocol::BufferKind::Index, 2);
|
||||||
|
Buffer* positions = buffer(draw.positions, protocol::BufferKind::Vertex, 12);
|
||||||
|
if (indices == nullptr || positions == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Optional streams count only if they cover every vertex.
|
||||||
|
const auto optional = [&](std::uint32_t id, std::uint32_t elementSize) -> IBuffer* {
|
||||||
|
Buffer* found = buffer(id, protocol::BufferKind::Vertex, elementSize);
|
||||||
|
return found != nullptr && found->count >= positions->count ? found->buffer.RawPtr() : nullptr;
|
||||||
|
};
|
||||||
|
this->draw(indices->buffer, indices->count, positions->buffer, optional(draw.normals, 12),
|
||||||
|
optional(draw.texCoords, 8), optional(draw.colors, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::drawArrays(const protocol::Command& command)
|
||||||
|
{
|
||||||
|
const auto arrays = command.body<protocol::DrawArrays>();
|
||||||
|
const auto payload = command.payload<protocol::DrawArrays>();
|
||||||
|
if (!arrays || arrays->vertexCount == 0 || arrays->indexCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::size_t indexBytes = protocol::padded(std::size_t{arrays->indexCount} * 2);
|
||||||
|
const std::size_t vertices = arrays->vertexCount;
|
||||||
|
if (payload.size() < indexBytes + vertices * (12 + (arrays->hasNormals ? 12 : 0) + (arrays->hasTexCoords ? 8 : 0) +
|
||||||
|
(arrays->hasColors ? 4 : 0))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto make = [this](const std::byte* data, std::size_t size, BIND_FLAGS bind) {
|
||||||
|
BufferDesc desc;
|
||||||
|
desc.Name = "Client arrays";
|
||||||
|
desc.Size = size;
|
||||||
|
desc.BindFlags = bind;
|
||||||
|
desc.Usage = USAGE_IMMUTABLE;
|
||||||
|
BufferData contents;
|
||||||
|
contents.pData = data;
|
||||||
|
contents.DataSize = size;
|
||||||
|
RefCntAutoPtr<IBuffer> buffer;
|
||||||
|
device_->CreateBuffer(desc, &contents, &buffer);
|
||||||
|
return buffer;
|
||||||
|
};
|
||||||
|
const std::byte* next = payload.data();
|
||||||
|
const auto indices = make(next, std::size_t{arrays->indexCount} * 2, BIND_INDEX_BUFFER);
|
||||||
|
next += indexBytes;
|
||||||
|
const auto positions = make(next, vertices * 12, BIND_VERTEX_BUFFER);
|
||||||
|
next += vertices * 12;
|
||||||
|
RefCntAutoPtr<IBuffer> normals, texCoords, colors;
|
||||||
|
if (arrays->hasNormals) {
|
||||||
|
normals = make(next, vertices * 12, BIND_VERTEX_BUFFER);
|
||||||
|
next += vertices * 12;
|
||||||
|
}
|
||||||
|
if (arrays->hasTexCoords) {
|
||||||
|
texCoords = make(next, vertices * 8, BIND_VERTEX_BUFFER);
|
||||||
|
next += vertices * 8;
|
||||||
|
}
|
||||||
|
if (arrays->hasColors) {
|
||||||
|
colors = make(next, vertices * 4, BIND_VERTEX_BUFFER);
|
||||||
|
}
|
||||||
|
draw(indices, arrays->indexCount, positions, normals, texCoords, colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::draw(IBuffer* indices, std::uint32_t indexCount, IBuffer* positions, IBuffer* normals, IBuffer* texCoords,
|
||||||
|
IBuffer* colors)
|
||||||
|
{
|
||||||
|
if (indices == nullptr || positions == nullptr || indexCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Like the client: no textures, the first, or both (the second only with a first).
|
||||||
|
ITextureView* texture0 = textureView(state_.texture0);
|
||||||
|
ITextureView* texture1 = texture0 != nullptr ? textureView(state_.texture1) : nullptr;
|
||||||
|
const std::uint32_t textures = texture0 == nullptr ? 0 : texture1 == nullptr ? 1 : 2;
|
||||||
|
const std::uint32_t streams =
|
||||||
|
(normals != nullptr ? hasNormals : 0) | (texCoords != nullptr ? hasTexCoords : 0) | (colors != nullptr ? hasColors : 0);
|
||||||
|
Pipeline* selected = pipeline(streams, textures);
|
||||||
|
if (selected == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* mapped = nullptr;
|
||||||
|
context_->MapBuffer(constantBuffer_, MAP_WRITE, MAP_FLAG_DISCARD, mapped);
|
||||||
|
if (mapped == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::memcpy(mapped, &constants_, sizeof(constants_));
|
||||||
|
context_->UnmapBuffer(constantBuffer_, MAP_WRITE);
|
||||||
|
|
||||||
|
if (selected->texture0 != nullptr) {
|
||||||
|
selected->texture0->Set(texture0);
|
||||||
|
}
|
||||||
|
if (selected->texture1 != nullptr) {
|
||||||
|
selected->texture1->Set(texture1);
|
||||||
|
}
|
||||||
|
context_->SetPipelineState(selected->state);
|
||||||
|
context_->CommitShaderResources(selected->bindings, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||||
|
|
||||||
|
IBuffer* vertexBuffers[4];
|
||||||
|
std::uint32_t count = 0;
|
||||||
|
for (IBuffer* stream : {positions, normals, texCoords, colors}) {
|
||||||
|
if (stream != nullptr) {
|
||||||
|
vertexBuffers[count++] = stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context_->SetVertexBuffers(0, count, vertexBuffers, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
|
||||||
|
SET_VERTEX_BUFFERS_FLAG_RESET);
|
||||||
|
context_->SetIndexBuffer(indices, 0, RESOURCE_STATE_TRANSITION_MODE_TRANSITION);
|
||||||
|
DrawIndexedAttribs attributes;
|
||||||
|
attributes.NumIndices = indexCount;
|
||||||
|
attributes.IndexType = VT_UINT16;
|
||||||
|
context_->DrawIndexed(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textures)
|
||||||
|
{
|
||||||
|
const std::uint32_t key = streams | (textures << 3);
|
||||||
|
if (const auto found = shaders_.find(key); found != shaders_.end()) {
|
||||||
|
return found->second;
|
||||||
|
}
|
||||||
|
const std::string source = std::string("#define HAS_NORMALS ") + ((streams & hasNormals) != 0 ? "1" : "0") +
|
||||||
|
"\n#define HAS_TEX_COORDS " + ((streams & hasTexCoords) != 0 ? "1" : "0") +
|
||||||
|
"\n#define HAS_COLORS " + ((streams & hasColors) != 0 ? "1" : "0") +
|
||||||
|
"\n#define TEXTURES " + std::to_string(textures) + "\n" + fixedFunctionHlsl;
|
||||||
|
Shaders created;
|
||||||
|
ShaderCreateInfo info;
|
||||||
|
info.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
|
||||||
|
info.Desc.UseCombinedTextureSamplers = true; // g_TextureN with g_TextureN_sampler, set from each texture's view
|
||||||
|
info.Source = source.c_str();
|
||||||
|
info.Desc.ShaderType = SHADER_TYPE_VERTEX;
|
||||||
|
info.Desc.Name = "Fixed function (vertex)";
|
||||||
|
info.EntryPoint = "vs_main";
|
||||||
|
device_->CreateShader(info, &created.vertex);
|
||||||
|
info.Desc.ShaderType = SHADER_TYPE_PIXEL;
|
||||||
|
info.Desc.Name = "Fixed function (pixel)";
|
||||||
|
info.EntryPoint = "ps_main";
|
||||||
|
device_->CreateShader(info, &created.pixel);
|
||||||
|
return shaders_[key] = std::move(created);
|
||||||
|
}
|
||||||
|
|
||||||
|
Scene::Pipeline* Scene::pipeline(std::uint32_t streams, std::uint32_t textures)
|
||||||
|
{
|
||||||
|
const std::uint32_t blend = state_.blending ? state_.blendMode : 0;
|
||||||
|
const std::uint32_t key = streams | (textures << 3) | (blend << 5) | (state_.depthTest ? 1u << 8 : 0) |
|
||||||
|
(state_.depthWrite ? 1u << 9 : 0) | (state_.culling ? 1u << 10 : 0) |
|
||||||
|
(state_.normalInverting ? 1u << 11 : 0);
|
||||||
|
if (const auto found = pipelines_.find(key); found != pipelines_.end()) {
|
||||||
|
return found->second.state ? &found->second : nullptr;
|
||||||
|
}
|
||||||
|
Pipeline& entry = pipelines_[key];
|
||||||
|
const Shaders& compiled = shaders(streams, textures);
|
||||||
|
if (!compiled.vertex || !compiled.pixel) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipelineStateCreateInfo info;
|
||||||
|
info.PSODesc.Name = "Fixed function";
|
||||||
|
info.PSODesc.PipelineType = PIPELINE_TYPE_GRAPHICS;
|
||||||
|
const SwapChainDesc& window = swapChain_->GetDesc();
|
||||||
|
GraphicsPipelineDesc& graphics = info.GraphicsPipeline;
|
||||||
|
graphics.NumRenderTargets = 1;
|
||||||
|
graphics.RTVFormats[0] = window.ColorBufferFormat;
|
||||||
|
graphics.DSVFormat = window.DepthBufferFormat;
|
||||||
|
graphics.PrimitiveTopology = PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
|
// The client culls clockwise triangles, or counter-clockwise ones when normals are inverted (mirrored objects).
|
||||||
|
graphics.RasterizerDesc.CullMode = state_.culling ? CULL_MODE_BACK : CULL_MODE_NONE;
|
||||||
|
graphics.RasterizerDesc.FrontCounterClockwise = !state_.normalInverting;
|
||||||
|
graphics.DepthStencilDesc.DepthEnable = state_.depthTest;
|
||||||
|
graphics.DepthStencilDesc.DepthWriteEnable = state_.depthWrite;
|
||||||
|
graphics.DepthStencilDesc.DepthFunc = COMPARISON_FUNC_LESS;
|
||||||
|
RenderTargetBlendDesc& blending = graphics.BlendDesc.RenderTargets[0];
|
||||||
|
blending.BlendEnable = blend != 0;
|
||||||
|
if (blend != 0) {
|
||||||
|
const BLEND_FACTOR source = blend == 1 || blend == 4 ? BLEND_FACTOR_SRC_ALPHA : BLEND_FACTOR_ONE;
|
||||||
|
const BLEND_FACTOR destination = blend == 2 || blend == 4 ? BLEND_FACTOR_ONE : BLEND_FACTOR_INV_SRC_ALPHA;
|
||||||
|
blending.SrcBlend = blending.SrcBlendAlpha = source;
|
||||||
|
blending.DestBlend = blending.DestBlendAlpha = destination;
|
||||||
|
blending.BlendOp = blending.BlendOpAlpha = BLEND_OPERATION_ADD;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutElement elements[4];
|
||||||
|
std::uint32_t count = 0;
|
||||||
|
elements[count] = LayoutElement{0, count, 3, VT_FLOAT32, False};
|
||||||
|
++count;
|
||||||
|
if ((streams & hasNormals) != 0) {
|
||||||
|
elements[count] = LayoutElement{1, count, 3, VT_FLOAT32, False};
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
if ((streams & hasTexCoords) != 0) {
|
||||||
|
elements[count] = LayoutElement{2, count, 2, VT_FLOAT32, False};
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
if ((streams & hasColors) != 0) {
|
||||||
|
elements[count] = LayoutElement{3, count, 4, VT_UINT8, True};
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
graphics.InputLayout.LayoutElements = elements;
|
||||||
|
graphics.InputLayout.NumElements = count;
|
||||||
|
|
||||||
|
info.pVS = compiled.vertex;
|
||||||
|
info.pPS = compiled.pixel;
|
||||||
|
const ShaderResourceVariableDesc variables[] = {
|
||||||
|
{SHADER_TYPE_PIXEL, "g_Texture0", SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC},
|
||||||
|
{SHADER_TYPE_PIXEL, "g_Texture1", SHADER_RESOURCE_VARIABLE_TYPE_DYNAMIC},
|
||||||
|
};
|
||||||
|
info.PSODesc.ResourceLayout.DefaultVariableType = SHADER_RESOURCE_VARIABLE_TYPE_STATIC;
|
||||||
|
info.PSODesc.ResourceLayout.Variables = variables;
|
||||||
|
info.PSODesc.ResourceLayout.NumVariables = textures;
|
||||||
|
device_->CreateGraphicsPipelineState(info, &entry.state);
|
||||||
|
if (!entry.state) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
for (const SHADER_TYPE stage : {SHADER_TYPE_VERTEX, SHADER_TYPE_PIXEL}) {
|
||||||
|
if (IShaderResourceVariable* constants = entry.state->GetStaticVariableByName(stage, "Constants")) {
|
||||||
|
constants->Set(constantBuffer_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.state->CreateShaderResourceBinding(&entry.bindings, true);
|
||||||
|
if (textures >= 1) {
|
||||||
|
entry.texture0 = entry.bindings->GetVariableByName(SHADER_TYPE_PIXEL, "g_Texture0");
|
||||||
|
}
|
||||||
|
if (textures >= 2) {
|
||||||
|
entry.texture1 = entry.bindings->GetVariableByName(SHADER_TYPE_PIXEL, "g_Texture1");
|
||||||
|
}
|
||||||
|
return &entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace t5de
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
// Carries out the command stream (include/t5de/protocol/commands.hpp) with Diligent, reproducing IMVU's Direct3D 9
|
||||||
|
// look (shaders/fixed_function.hlsl): the client's textures and buffers by id, its state as it sets it, a pipeline
|
||||||
|
// for each combination of states it draws with.
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "EngineFactoryD3D11.h"
|
||||||
|
#include "RefCntAutoPtr.hpp"
|
||||||
|
#include "t5de/protocol/commands.hpp"
|
||||||
|
|
||||||
|
namespace t5de {
|
||||||
|
|
||||||
|
class Scene {
|
||||||
|
public:
|
||||||
|
Scene(Diligent::IRenderDevice* device, Diligent::IDeviceContext* context, Diligent::ISwapChain* swapChain);
|
||||||
|
|
||||||
|
// Carries out one frame's commands. offsetX is where the window starts within the client's 3D view; without draw,
|
||||||
|
// only the frame's changes are made (textures, buffers, state). True if the frame was drawn into the window (to
|
||||||
|
// present).
|
||||||
|
bool execute(std::span<const std::byte> commands, int offsetX, bool draw);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Texture {
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::ITexture> texture;
|
||||||
|
std::uint32_t width = 0;
|
||||||
|
std::uint32_t height = 0;
|
||||||
|
std::uint32_t flags = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Buffer {
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::IBuffer> buffer;
|
||||||
|
protocol::BufferKind kind = protocol::BufferKind::Vertex;
|
||||||
|
std::uint32_t elementSize = 0;
|
||||||
|
std::uint32_t count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pipeline {
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::IPipelineState> state;
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::IShaderResourceBinding> bindings;
|
||||||
|
Diligent::IShaderResourceVariable* texture0 = nullptr;
|
||||||
|
Diligent::IShaderResourceVariable* texture1 = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Shaders {
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::IShader> vertex;
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::IShader> pixel;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mirrors the shader's constant buffer.
|
||||||
|
struct Constants {
|
||||||
|
float world[16];
|
||||||
|
float view[16];
|
||||||
|
float projection[16];
|
||||||
|
float texTransform0[8];
|
||||||
|
float texTransform1[8];
|
||||||
|
float ambient[4];
|
||||||
|
float lightColor[8][4];
|
||||||
|
float lightVector[8][4];
|
||||||
|
float fogColor[4];
|
||||||
|
float fog[4];
|
||||||
|
float flags[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
// What a draw needs from the state, as set by the client.
|
||||||
|
struct State {
|
||||||
|
bool depthTest = true;
|
||||||
|
bool depthWrite = true;
|
||||||
|
bool blending = false;
|
||||||
|
std::uint32_t blendMode = 1;
|
||||||
|
bool culling = true;
|
||||||
|
bool normalInverting = false;
|
||||||
|
std::uint32_t texture0 = 0;
|
||||||
|
std::uint32_t texture1 = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void createTexture(const protocol::Command& command);
|
||||||
|
void updateTexture(const protocol::Command& command);
|
||||||
|
void createBuffer(const protocol::Command& command);
|
||||||
|
void updateBuffer(const protocol::Command& command);
|
||||||
|
void setProjection(const protocol::SetProjection& projection);
|
||||||
|
void setState(const protocol::SetState& state);
|
||||||
|
void drawTriangles(const protocol::DrawTriangles& draw);
|
||||||
|
void drawArrays(const protocol::Command& command);
|
||||||
|
|
||||||
|
// Draws indexCount indices from index, with vertex streams (positions required, the others may be null).
|
||||||
|
void draw(Diligent::IBuffer* indices, std::uint32_t indexCount, Diligent::IBuffer* positions,
|
||||||
|
Diligent::IBuffer* normals, Diligent::IBuffer* texCoords, Diligent::IBuffer* colors);
|
||||||
|
Pipeline* pipeline(std::uint32_t streams, std::uint32_t textures);
|
||||||
|
const Shaders& shaders(std::uint32_t streams, std::uint32_t textures);
|
||||||
|
Diligent::ITextureView* textureView(std::uint32_t id);
|
||||||
|
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::IRenderDevice> device_;
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::IDeviceContext> context_;
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::ISwapChain> swapChain_;
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::IBuffer> constantBuffer_;
|
||||||
|
Diligent::RefCntAutoPtr<Diligent::ISampler> samplers_[4]; // by wrap u (1) and wrap v (2)
|
||||||
|
|
||||||
|
std::unordered_map<std::uint32_t, Texture> textures_;
|
||||||
|
std::unordered_map<std::uint32_t, Buffer> buffers_;
|
||||||
|
std::unordered_map<std::uint32_t, Shaders> shaders_;
|
||||||
|
std::unordered_map<std::uint32_t, Pipeline> pipelines_;
|
||||||
|
|
||||||
|
Constants constants_{};
|
||||||
|
State state_;
|
||||||
|
bool onscreen_ = false; // in a frame drawn into the window
|
||||||
|
bool draw_ = true;
|
||||||
|
int offsetX_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace t5de
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
// Generated by CMake from renderer/shaders/@T5DE_SHADER_FILE@: the shader source, compiled at run time by Diligent.
|
||||||
|
|
||||||
|
inline constexpr const char* @T5DE_SHADER_NAME@ = R"hlsl(@T5DE_SHADER_SOURCE@)hlsl";
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// IMVU's Direct3D 9 fixed-function look, as its renderer sets it up (docs/interface.md):
|
||||||
|
// - per-vertex (Gouraud) lighting, with the vertex color as diffuse and ambient material (white without one): the
|
||||||
|
// environment's ambient light, plus each light's color times max(0, N.L); no specular or emissive light;
|
||||||
|
// - textures: none (the lit color), one (times the lit color), or two (the second blended over the first by its
|
||||||
|
// alpha, then times the lit color), both on the vertex's texture coordinates with their own transforms;
|
||||||
|
// - an alpha test passing pixels whose alpha is greater than the reference, then linear fog by view depth.
|
||||||
|
//
|
||||||
|
// Compiled for each combination of the vertex streams present (HAS_NORMALS, HAS_TEX_COORDS, HAS_COLORS) and the
|
||||||
|
// number of textures (TEXTURES).
|
||||||
|
|
||||||
|
cbuffer Constants
|
||||||
|
{
|
||||||
|
row_major float4x4 g_World;
|
||||||
|
row_major float4x4 g_View;
|
||||||
|
row_major float4x4 g_Projection;
|
||||||
|
float4 g_TexTransform0[2]; // u' = dot((u, v, 1), [0].xyz), v' = dot((u, v, 1), [1].xyz)
|
||||||
|
float4 g_TexTransform1[2];
|
||||||
|
float4 g_Ambient; // rgb
|
||||||
|
float4 g_LightColor[8]; // rgb, a: enabled
|
||||||
|
float4 g_LightVector[8]; // xyz: position, or direction toward the light; w: 1 if directional
|
||||||
|
float4 g_FogColor; // rgb
|
||||||
|
float4 g_Fog; // x: start, y: end, z: on
|
||||||
|
float4 g_Flags; // x: lighting on, y: alpha test on, z: alpha reference (0-255)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VSInput
|
||||||
|
{
|
||||||
|
float3 Position : ATTRIB0;
|
||||||
|
#if HAS_NORMALS
|
||||||
|
float3 Normal : ATTRIB1;
|
||||||
|
#endif
|
||||||
|
#if HAS_TEX_COORDS
|
||||||
|
float2 TexCoord : ATTRIB2;
|
||||||
|
#endif
|
||||||
|
#if HAS_COLORS
|
||||||
|
float4 Color : ATTRIB3; // bytes B, G, R, A
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PSInput
|
||||||
|
{
|
||||||
|
float4 Position : SV_POSITION;
|
||||||
|
float4 Color : COLOR0;
|
||||||
|
float2 TexCoord0 : TEXCOORD0;
|
||||||
|
float2 TexCoord1 : TEXCOORD1;
|
||||||
|
float Depth : TEXCOORD2;
|
||||||
|
};
|
||||||
|
|
||||||
|
void vs_main(in VSInput input, out PSInput output)
|
||||||
|
{
|
||||||
|
float4 world = mul(float4(input.Position, 1.0), g_World);
|
||||||
|
float4 view = mul(world, g_View);
|
||||||
|
output.Position = mul(view, g_Projection);
|
||||||
|
output.Depth = abs(view.z);
|
||||||
|
|
||||||
|
float4 material = float4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
#if HAS_COLORS
|
||||||
|
material = input.Color.zyxw;
|
||||||
|
#endif
|
||||||
|
float4 color = material;
|
||||||
|
if (g_Flags.x > 0.5)
|
||||||
|
{
|
||||||
|
float3 normal = float3(0.0, 0.0, 0.0);
|
||||||
|
#if HAS_NORMALS
|
||||||
|
normal = normalize(mul(input.Normal, (float3x3)g_World));
|
||||||
|
#endif
|
||||||
|
float3 lit = g_Ambient.rgb * material.rgb;
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
if (g_LightColor[i].a > 0.5)
|
||||||
|
{
|
||||||
|
float3 toLight = g_LightVector[i].w > 0.5 ? g_LightVector[i].xyz : g_LightVector[i].xyz - world.xyz;
|
||||||
|
lit += g_LightColor[i].rgb * material.rgb * max(0.0, dot(normal, normalize(toLight)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color = float4(saturate(lit), material.a);
|
||||||
|
}
|
||||||
|
output.Color = color;
|
||||||
|
|
||||||
|
float3 uv = float3(0.0, 0.0, 1.0);
|
||||||
|
#if HAS_TEX_COORDS
|
||||||
|
uv.xy = input.TexCoord;
|
||||||
|
#endif
|
||||||
|
output.TexCoord0 = float2(dot(uv, g_TexTransform0[0].xyz), dot(uv, g_TexTransform0[1].xyz));
|
||||||
|
output.TexCoord1 = float2(dot(uv, g_TexTransform1[0].xyz), dot(uv, g_TexTransform1[1].xyz));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if TEXTURES >= 1
|
||||||
|
Texture2D g_Texture0;
|
||||||
|
SamplerState g_Texture0_sampler;
|
||||||
|
#endif
|
||||||
|
#if TEXTURES >= 2
|
||||||
|
Texture2D g_Texture1;
|
||||||
|
SamplerState g_Texture1_sampler;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float4 ps_main(in PSInput input) : SV_Target
|
||||||
|
{
|
||||||
|
float4 color = input.Color;
|
||||||
|
#if TEXTURES == 1
|
||||||
|
color *= g_Texture0.Sample(g_Texture0_sampler, input.TexCoord0);
|
||||||
|
#elif TEXTURES == 2
|
||||||
|
float4 first = g_Texture0.Sample(g_Texture0_sampler, input.TexCoord0);
|
||||||
|
float4 second = g_Texture1.Sample(g_Texture1_sampler, input.TexCoord1);
|
||||||
|
color *= lerp(first, second, second.a);
|
||||||
|
#endif
|
||||||
|
if (g_Flags.y > 0.5 && !(color.a * 255.0 > g_Flags.z))
|
||||||
|
{
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
if (g_Fog.z > 0.5)
|
||||||
|
{
|
||||||
|
float visible = saturate((g_Fog.y - input.Depth) / max(g_Fog.y - g_Fog.x, 1e-6));
|
||||||
|
color.rgb = lerp(g_FogColor.rgb, color.rgb, visible);
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
add_executable(shared_ptr_test shared_ptr_test.cpp)
|
add_executable(shared_ptr_test shared_ptr_test.cpp)
|
||||||
target_link_libraries(shared_ptr_test PRIVATE t5de::imvu t5de::options)
|
target_link_libraries(shared_ptr_test PRIVATE t5de::imvu t5de::options)
|
||||||
add_test(NAME shared_ptr COMMAND shared_ptr_test)
|
add_test(NAME shared_ptr COMMAND shared_ptr_test)
|
||||||
|
|
||||||
|
add_executable(protocol_test protocol_test.cpp)
|
||||||
|
target_link_libraries(protocol_test PRIVATE t5de::protocol t5de::options)
|
||||||
|
add_test(NAME protocol COMMAND protocol_test)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// The command stream: what the adapter writes, the renderer process reads back the same.
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "t5de/protocol/commands.hpp"
|
||||||
|
|
||||||
|
using namespace t5de::protocol;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int failures = 0;
|
||||||
|
|
||||||
|
void check(bool ok, const char* what)
|
||||||
|
{
|
||||||
|
if (!ok) {
|
||||||
|
std::printf("FAIL: %s\n", what);
|
||||||
|
++failures;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Writer writer;
|
||||||
|
writer.add(Op::Clear, Clear{3, 0xff131313});
|
||||||
|
const unsigned char pixels[] = {1, 2, 3, 4, 5, 6, 7, 8};
|
||||||
|
std::memcpy(writer.add(Op::CreateTexture, CreateTexture{7, 2, 1, 6}, sizeof(pixels)), pixels, sizeof(pixels));
|
||||||
|
std::byte* odd = writer.add(Op::UpdateBuffer, UpdateBuffer{9, 1, 3}, 3); // padded to 4
|
||||||
|
std::memset(odd, 0xab, 3);
|
||||||
|
writer.add(Op::EndFrame, EndFrame{});
|
||||||
|
check(writer.size() % 4 == 0, "commands stay 4-byte aligned");
|
||||||
|
|
||||||
|
Reader reader(writer.bytes());
|
||||||
|
auto clear = reader.next();
|
||||||
|
check(clear && clear->op() == Op::Clear && clear->body<Clear>()->color == 0xff131313, "clear");
|
||||||
|
auto texture = reader.next();
|
||||||
|
check(texture && texture->op() == Op::CreateTexture && texture->body<CreateTexture>()->id == 7, "texture");
|
||||||
|
check(texture && texture->payload<CreateTexture>().size() == sizeof(pixels)
|
||||||
|
&& std::memcmp(texture->payload<CreateTexture>().data(), pixels, sizeof(pixels)) == 0,
|
||||||
|
"texture pixels");
|
||||||
|
auto update = reader.next();
|
||||||
|
check(update && update->payload<UpdateBuffer>().size() == 4, "payload padded");
|
||||||
|
auto end = reader.next();
|
||||||
|
check(end && end->op() == Op::EndFrame, "end of frame");
|
||||||
|
check(!reader.next(), "nothing after");
|
||||||
|
|
||||||
|
// A truncated stream (a chunk cut short) yields only whole commands.
|
||||||
|
Reader truncated(writer.bytes().first(writer.size() - 2));
|
||||||
|
int count = 0;
|
||||||
|
while (truncated.next()) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
check(count == 3, "truncated: whole commands only");
|
||||||
|
|
||||||
|
if (failures == 0) {
|
||||||
|
std::printf("ok\n");
|
||||||
|
}
|
||||||
|
return failures == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user