#include "scene.hpp" #include #include #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((color >> 16) & 0xff) / 255.0f; rgba[1] = static_cast((color >> 8) & 0xff) / 255.0f; rgba[2] = static_cast(color & 0xff) / 255.0f; rgba[3] = static_cast(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 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()) { 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(); 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()) { setProjection(*projection); } break; case Op::SetModel: if (const auto model = command->body()) { set_transform(constants_.world, model->m); } break; case Op::SetView: if (const auto view = command->body()) { set_transform(constants_.view, view->m); } break; case Op::SetTextures: if (const auto textures = command->body()) { state_.texture0 = textures->texture0; state_.texture1 = textures->texture1; } break; case Op::SetState: if (const auto state = command->body()) { setState(*state); } break; case Op::SetLight: if (const auto light = command->body(); 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()) { 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(); 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()) { 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()) { buffers_.erase(destroy->id); } break; case Op::DrawTriangles: if (const auto triangles = command->body(); 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(-offsetX_); viewport.TopLeftY = 0; viewport.Width = static_cast(projection.width); viewport.Height = static_cast(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(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(); const auto pixels = command.payload(); 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(); const auto pixels = command.payload(); 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(); 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(); 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(); const auto contents = command.payload(); 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(); const auto payload = command.payload(); 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 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 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