#include "engine.hpp" #include #include #include #include #include #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 inner, std::shared_ptr recorder) : inner_(std::move(inner)), recorder_(std::move(recorder)) { } Engine::~Engine() { log::info("engine: destroyed after {} frames", frames_); } void Engine::setState(State state, std::uint32_t value) { recorder_->add(Op::SetState, protocol::SetState{state, value}); } void Engine::resizeFrameBuffer(imvu::Vec2i size) { inner_->resizeFrameBuffer(size); } void Engine::enable2dFrameBuffer() { inner_->enable2dFrameBuffer(); } void Engine::clear(std::uint32_t flags, imvu::Color color) { if (innerDraws()) { inner_->clear(flags, color); } recorder_->add(Op::Clear, protocol::Clear{flags, color}); } void Engine::setProjection(imvu::Vec2i viewport, const imvu::Mat4f& projection) { inner_->setProjection(viewport, projection); if (projection.m[2][3] == 0.0f) { ++stats_.flat; } protocol::SetProjection command{viewport.x, viewport.y, {}}; std::memcpy(command.m, &projection.m[0][0], sizeof(command.m)); recorder_->add(Op::SetProjection, command); } void Engine::setModelSpace(const imvu::Transform& model) { inner_->setModelSpace(model); recorder_->add(Op::SetModel, transform_command(model)); } void Engine::setViewSpace(const imvu::Transform& view) { inner_->setViewSpace(view); recorder_->add(Op::SetView, transform_command(view)); } void Engine::setTextures(imvu::PiRenderTexture* texture0, imvu::PiRenderTexture* texture1) { inner_->setTextures(inner(texture0), inner(texture1)); for (imvu::PiRenderTexture* texture : {texture0, texture1}) { if (texture != nullptr && id(texture) == 0 && foreignLogged_ < 20) { ++foreignLogged_; log::info("engine: frame {}: a texture the client renderer made ({})", frames_, static_cast(texture)); } } recorder_->add(Op::SetTextures, protocol::SetTextures{id(texture0), id(texture1)}); } void Engine::setDepthTestingOn(bool on) { inner_->setDepthTestingOn(on); setState(State::DepthTest, on); } void Engine::setDepthWritingOn(bool on) { inner_->setDepthWritingOn(on); setState(State::DepthWrite, on); } void Engine::setBlendMode(imvu::RenderBlendMode mode) { inner_->setBlendMode(mode); setState(State::BlendMode, static_cast(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::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 Engine::newPixmapRenderTarget(imvu::Vec2i size) { log::info("engine: frame {}: a {}x{} pixmap render target", frames_, size.x, size.y); return inner_->newPixmapRenderTarget(size); } imvu::SharedPtr Engine::newTextureRenderTarget(imvu::Vec2i size) { log::info("engine: frame {}: a {}x{} texture render target", frames_, size.x, size.y); return inner_->newTextureRenderTarget(size); } void Engine::beginRendering(imvu::PiRenderTarget* target) { inner_->beginRendering(target); // Decided per frame: frames into render targets, and frames while the renderer process doesn't cover the view, are // drawn by the client's renderer. const bool takenOver = target == nullptr && recorder_->covering(); if (takenOver != takenOver_ && target == nullptr) { log::info("engine: frame {}: {}", frames_, takenOver ? "drawn by the renderer process" : "drawn by the client's renderer"); } takenOver_ = takenOver; if (target != nullptr && foreignLogged_ < 20) { ++foreignLogged_; log::info("engine: frame {}: drawing into a render target ({})", frames_, static_cast(target)); } recorder_->add(Op::BeginFrame, protocol::BeginFrame{target != nullptr}); } void Engine::endRendering() { inner_->endRendering(); LARGE_INTEGER now; QueryPerformanceCounter(&now); recorder_->add(Op::EndFrame, protocol::EndFrame{static_cast(now.QuadPart)}); recorder_->flush(true); ++frames_; if (frames_ % 300 == 0 && statsLogged_ < 6) { ++statsLogged_; log::info("engine: last 300 frames: drawTriangles {} (and {} with buffers not ours), drawTrianglesFromMemory {} " "(and {} without indices), point sprites {}, lines {}, 2D projections {}", stats_.triangles, stats_.foreignTriangles, stats_.indexed, stats_.unindexed, stats_.sprites, stats_.lines, stats_.flat); stats_ = {}; } } void Engine::drawTriangles(imvu::PiInternalIndexBuffer* indices, imvu::PiInternalVertexBuffer* positions, imvu::PiInternalVertexBuffer* normals, imvu::PiInternalVertexBuffer* texCoords, imvu::PiInternalVertexBuffer* colors) { if (innerDraws()) { inner_->drawTriangles(inner(indices), inner(positions), inner(normals), inner(texCoords), inner(colors)); } ++(id(indices) != 0 && id(positions) != 0 ? stats_.triangles : stats_.foreignTriangles); recorder_->add(Op::DrawTriangles, protocol::DrawTriangles{id(indices), id(positions), id(normals), id(texCoords), id(colors)}); } void Engine::drawLine(imvu::Vec3f from, imvu::Vec3f to, imvu::Color color) { if (innerDraws()) { inner_->drawLine(from, to, color); } ++stats_.lines; } void Engine::drawPointSprites(const imvu::PointSpriteVector& sprites) { if (innerDraws()) { inner_->drawPointSprites(sprites); } ++stats_.sprites; if (sprites.first == nullptr || sprites.last <= sprites.first) { return; } const auto count = static_cast(sprites.last - sprites.first); if (std::byte* payload = recorder_->add(Op::DrawPointSprites, protocol::DrawPointSprites{count}, std::size_t{count} * sizeof(imvu::PointSprite))) { std::memcpy(payload, sprites.first, std::size_t{count} * sizeof(imvu::PointSprite)); } } void Engine::drawTrianglesFromMemory(std::uint32_t vertexCount, std::uint32_t indexCount, const std::uint16_t* indices, const imvu::VertexAttributeArray& positions, const imvu::VertexAttributeArray& normals, const imvu::VertexAttributeArray& texCoords, const imvu::VertexAttributeArray& colors) { if (innerDraws()) { inner_->drawTrianglesFromMemory(vertexCount, indexCount, indices, positions, normals, texCoords, colors); } ++(indices != nullptr ? stats_.indexed : stats_.unindexed); if (positions.data == nullptr || vertexCount == 0) { return; } // Without indices: the vertices in order, as a triangle list. std::vector 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; 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