diff --git a/adapter/engine.cpp b/adapter/engine.cpp index 3dc9293..5f8c524 100644 --- a/adapter/engine.cpp +++ b/adapter/engine.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "log.hpp" #include "resources.hpp" @@ -53,6 +54,9 @@ void Engine::clear(std::uint32_t flags, imvu::Color color) void Engine::setProjection(imvu::Vec2i viewport, const imvu::Mat4f& projection) { inner_->setProjection(viewport, projection); + if (projection.m[2][3] == 0.0f) { + ++stats_.flat; + } protocol::SetProjection command{viewport.x, viewport.y, {}}; std::memcpy(command.m, &projection.m[0][0], sizeof(command.m)); recorder_->add(Op::SetProjection, command); @@ -253,6 +257,14 @@ void Engine::endRendering() recorder_->add(Op::EndFrame, protocol::EndFrame{static_cast(now.QuadPart)}); recorder_->flush(true); ++frames_; + if (frames_ % 300 == 0 && statsLogged_ < 6) { + ++statsLogged_; + log::info("engine: last 300 frames: drawTriangles {} (and {} with buffers not ours), drawTrianglesFromMemory {} " + "(and {} without indices), point sprites {}, lines {}, 2D projections {}", + stats_.triangles, stats_.foreignTriangles, stats_.indexed, stats_.unindexed, stats_.sprites, stats_.lines, + stats_.flat); + stats_ = {}; + } } void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions, @@ -260,15 +272,21 @@ void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInterna imvu::PiInternalVertexBuffer* colors) { inner_->drawTriangles(inner(indices), inner(positions), inner(normals), inner(texCoords), inner(colors)); + ++(id(indices) != 0 && id(positions) != 0 ? stats_.triangles : stats_.foreignTriangles); recorder_->add(Op::DrawTriangles, protocol::DrawTriangles{id(indices), id(positions), id(normals), id(texCoords), id(colors)}); } -void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color) { inner_->drawLine(from, to, color); } +void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color) +{ + inner_->drawLine(from, to, color); + ++stats_.lines; +} void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites) { inner_->drawPointSprites(sprites); + ++stats_.sprites; if (sprites.first == nullptr || sprites.last <= sprites.first) { return; } @@ -287,9 +305,23 @@ void Engine::drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t in const imvu::VertexAttributeArray& colors) { inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors); - if (indices == nullptr || positions.data == nullptr || vertexCount == 0) { + ++(indices != nullptr ? stats_.indexed : stats_.unindexed); + if (positions.data == nullptr || vertexCount == 0) { return; } + // Without indices: the vertices in order, as a triangle list. + std::vector sequence; + if (indices == nullptr) { + if (vertexCount > 0xffff) { + return; + } + sequence.resize(vertexCount - vertexCount % 3); + for (std::size_t i = 0; i < sequence.size(); ++i) { + sequence[i] = static_cast(i); + } + indices = sequence.data(); + indexCount = static_cast(sequence.size()); + } const bool hasNormals = normals.data != nullptr && normals.count >= vertexCount; const bool hasTexCoords = texCoords.data != nullptr && texCoords.count >= vertexCount; const bool hasColors = colors.data != nullptr && colors.count >= vertexCount; diff --git a/adapter/engine.hpp b/adapter/engine.hpp index 8c56c10..e186695 100644 --- a/adapter/engine.hpp +++ b/adapter/engine.hpp @@ -69,6 +69,19 @@ private: std::uint64_t frames_ = 0; int foreignLogged_ = 0; // diagnostics: textures the client renderer made, render targets + // Diagnostics: what the client draws, summed over 300 frames and logged (the first few times). + struct Stats { + std::uint32_t triangles = 0; // drawTriangles with our buffers + std::uint32_t foreignTriangles = 0; // drawTriangles with buffers that aren't ours (not drawn) + std::uint32_t indexed = 0; // drawTrianglesFromMemory with indices + std::uint32_t unindexed = 0; // drawTrianglesFromMemory without + std::uint32_t sprites = 0; + std::uint32_t lines = 0; + std::uint32_t flat = 0; // setProjection with an orthographic projection (2D) + }; + Stats stats_; + int statsLogged_ = 0; + }; } // namespace t5de diff --git a/docs/architecture.md b/docs/architecture.md index 9159231..21d2a0d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -36,8 +36,21 @@ client exits. ## Status The renderer process draws the client's whole scene, over the right half of the 3D view, beside the client's own -Direct3D renderer on the left (the adapter still forwards everything to it): `T5DE_RENDERER_VIEW=full` or `off` -changes that. Checked in rooms: the halves match, particles included. +Direct3D renderer on the left (the adapter still forwards everything to it). Checked in rooms with screenshots of +both renderers side by side (the compare view): they match, particles and the 2D overlay (name tags) included. + +For comparing while the client runs (development): + +- Views: `T5DE_RENDERER_VIEW` (off, split, full or compare) when the client starts, or + `%TEMP%\t5de-renderer-view.txt` holding one of those at any time. Compare draws the left half's scene over the + right half, so both renderers show the same thing side by side. +- `%TEMP%\t5de-renderer-shaders\fixed_function.hlsl`, if present, replaces the built-in shaders and is reloaded + when it changes. +- The renderer process runs from a copy (`%TEMP%\t5de-renderer`), so `t5de-renderer.exe` can be replaced while the + client runs; engines started afterwards run the new one (the room's view keeps its engine until the client + restarts). +- The adapter's log (`%TEMP%\t5de-renderer.log`) sums up what the client draws every 300 frames (the first few + times), and logs textures and render targets the client renderer made itself. - The command stream (`include/t5de/protocol/commands.hpp`) carries state, transforms, lights, fog, textures, buffers (copied when created or updated) and draws, in chunks through the channel; the adapter waits for each to diff --git a/docs/interface.md b/docs/interface.md index 2e1bc28..0b33705 100644 --- a/docs/interface.md +++ b/docs/interface.md @@ -71,9 +71,16 @@ virtual methods, IMVU's code only uses an engine through helpers that call its v vertical field of view, Direct3D's 0 to 1 depth) and an orthographic one for 2D, orthonormal view rotations, opaque clear colors, ambient light with zero alpha, linear fog (1 to 50), scrolling texture transforms. Furniture vertices are interleaved: positions and normals are 12-byte elements 32 bytes apart, so strides matter. +- Projections give depth from -1 to 1, as OpenGL's do: IMVU's Direct3D renderer multiplies them by a matrix + mapping it to 0 to 1 (z' = z / 2 + w / 2) and shifting them half a pixel (-1 / width, +1 / height, for Direct3D + 9's pixel centers). Without the depth mapping, everything in the nearer half of the depth range is clipped: the + 2D overlay (name tags, the frame rate, the room's title) is drawn at depth -0.98. +- The 2D overlay is ordinary `drawTriangles` with an orthographic projection (a few per frame), textured with text + IMVU draws with GDI into bitmaps. - Particles (`drawPointSprites`): a Visual C++ 2010 `std::vector` of 16-byte points (position, color), drawn as - Direct3D 9 point sprites: squares viewport height / distance pixels across (Direct3D 9's defaults clamp them to - 1 to 64; IMVU sets only the size, 1, and the scale factors 0, 0, 1), textured over the whole square. + Direct3D 9 point sprites: squares viewport height / distance pixels across (IMVU sets the size, 1, and the scale + factors 0, 0, 1), textured over the whole square. Direct3D 9's documented 64-pixel default limit doesn't apply in + practice: clamped to it they look much smaller and fainter than the client's. - Fog: linear table fog, start 1 and end 50 in rooms. Fogging by view depth matches the client; by depth buffer value (as Direct3D 9's documentation suggests for projections like IMVU's) doesn't. - Still unknown: render targets, `CompoundRegion` (texture update diff --git a/renderer/scene.cpp b/renderer/scene.cpp index 8cec536..3bc4344 100644 --- a/renderer/scene.cpp +++ b/renderer/scene.cpp @@ -1,13 +1,9 @@ #include "scene.hpp" -#include -#include -#include #include #include #include "fixed_function_hlsl.hpp" -#include "log.hpp" namespace t5de { @@ -227,6 +223,13 @@ bool Scene::execute(std::span commands, int offsetX, bool draw) void Scene::setProjection(const protocol::SetProjection& projection) { std::memcpy(constants_.projection, projection.m, sizeof(constants_.projection)); + // The client's projections give depth from -1 to 1, as OpenGL does; IMVU's Direct3D renderer maps it to 0 to 1 + // (z' = z / 2 + w / 2) before setting them, and so do we. (It also shifts them half a pixel, which Direct3D 9 + // needs and Direct3D 11 doesn't.) + for (int row = 0; row < 4; ++row) { + float* m = constants_.projection + row * 4; + m[2] = 0.5f * m[2] + 0.5f * m[3]; + } constants_.viewport[0] = static_cast(projection.width); constants_.viewport[1] = static_cast(projection.height); if (!onscreen_) { @@ -545,38 +548,6 @@ void Scene::drawPointSprites(const protocol::Command& command) if (!buffer || prepare(0, true) == nullptr) { return; } - if (spritesLogged_ < 5) { - ++spritesLogged_; - const Texture* texture = textures_.contains(state_.texture0) ? &textures_[state_.texture0] : nullptr; - log::info("particles: {}, texture {} {}x{} flags {}, blending {} mode {}, depth test {} write {}, alpha test {} " - "reference {}, lighting {}, fog {}, ambient ({:.2f} {:.2f} {:.2f}), texture transform [{} {} {} {} {} {}]", - sprites->count, state_.texture0, texture ? texture->width : 0, texture ? texture->height : 0, - texture ? texture->flags : 0, state_.blending, state_.blendMode, state_.depthTest, state_.depthWrite, - constants_.flags[1], constants_.flags[2], constants_.flags[0], constants_.fog[2], constants_.ambient[0], - constants_.ambient[1], constants_.ambient[2], constants_.texTransform0[0], constants_.texTransform0[1], - constants_.texTransform0[2], constants_.texTransform0[4], constants_.texTransform0[5], - constants_.texTransform0[6]); - // The first sprite: where it is, seen from the camera, and how big it's drawn. - float point[4] = {0, 0, 0, 1}; - std::uint32_t color = 0; - std::memcpy(point, payload.data(), 12); - std::memcpy(&color, payload.data() + 12, 4); - const auto transform = [](const float (&p)[4], const float (&m)[16]) { - std::array out{}; - for (int column = 0; column < 4; ++column) { - out[column] = p[0] * m[column] + p[1] * m[4 + column] + p[2] * m[8 + column] + p[3] * m[12 + column]; - } - return out; - }; - const auto world = transform(point, constants_.world); - const float worldPoint[4] = {world[0], world[1], world[2], world[3]}; - const auto view = transform(worldPoint, constants_.view); - const float distance = std::sqrt(view[0] * view[0] + view[1] * view[1] + view[2] * view[2]); - log::info(" first sprite at ({:.2f} {:.2f} {:.2f}), color {:08x}, from the camera ({:.2f} {:.2f} {:.2f}): " - "distance {:.2f}, {:.1f} pixels across (viewport height {})", - point[0], point[1], point[2], color, view[0], view[1], view[2], distance, - std::clamp(constants_.viewport[1] / std::max(distance, 1e-4f), 1.0f, 2048.0f), constants_.viewport[1]); - } IBuffer* buffers[] = {buffer}; context_->SetVertexBuffers(0, 1, buffers, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION, SET_VERTEX_BUFFERS_FLAG_RESET); diff --git a/renderer/scene.hpp b/renderer/scene.hpp index e51fb74..d4be637 100644 --- a/renderer/scene.hpp +++ b/renderer/scene.hpp @@ -122,7 +122,6 @@ private: bool onscreen_ = false; // in a frame drawn into the window bool draw_ = true; std::uint64_t lastSentAt_ = 0; - int spritesLogged_ = 0; int offsetX_ = 0; };