IMVU's projections give depth from -1 to 1; its Direct3D renderer maps it to 0 to 1, and now so does ours. Without that, the nearer half of the depth range was clipped, the whole 2D overlay (name tags, the frame rate, the room's title) with it. The adapter also draws drawTrianglesFromMemory without indices, and sums up what the client draws in its log. Co-Authored-By: Claude Opus 5.5 <[email protected]>
688 lines
27 KiB
C++
688 lines
27 KiB
C++
#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), shaderSource_(fixedFunctionHlsl)
|
|
{
|
|
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
|
|
}
|
|
|
|
void Scene::setShaderSource(std::string source)
|
|
{
|
|
shaderSource_ = std::move(source);
|
|
shaders_.clear();
|
|
pipelines_.clear();
|
|
}
|
|
|
|
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:
|
|
if (const auto end = command->body<protocol::EndFrame>()) {
|
|
lastSentAt_ = end->sentAt;
|
|
}
|
|
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;
|
|
case Op::DrawPointSprites:
|
|
if (onscreen_) {
|
|
drawPointSprites(*command);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return drew;
|
|
}
|
|
|
|
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<float>(projection.width);
|
|
constants_.viewport[1] = static_cast<float>(projection.height);
|
|
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;
|
|
}
|
|
const std::uint32_t streams =
|
|
(normals != nullptr ? hasNormals : 0) | (texCoords != nullptr ? hasTexCoords : 0) | (colors != nullptr ? hasColors : 0);
|
|
if (prepare(streams, false) == nullptr) {
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Scene::Pipeline* Scene::prepare(std::uint32_t streams, bool sprites)
|
|
{
|
|
// 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;
|
|
Pipeline* selected = pipeline(streams, textures, sprites);
|
|
if (selected == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
void* mapped = nullptr;
|
|
context_->MapBuffer(constantBuffer_, MAP_WRITE, MAP_FLAG_DISCARD, mapped);
|
|
if (mapped == nullptr) {
|
|
return nullptr;
|
|
}
|
|
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);
|
|
return selected;
|
|
}
|
|
|
|
void Scene::drawPointSprites(const protocol::Command& command)
|
|
{
|
|
const auto sprites = command.body<protocol::DrawPointSprites>();
|
|
const auto payload = command.payload<protocol::DrawPointSprites>();
|
|
if (!sprites || sprites->count == 0 || payload.size() < std::size_t{sprites->count} * 16) {
|
|
return;
|
|
}
|
|
BufferDesc desc;
|
|
desc.Name = "Client particles";
|
|
desc.Size = std::size_t{sprites->count} * 16;
|
|
desc.BindFlags = BIND_VERTEX_BUFFER;
|
|
desc.Usage = USAGE_IMMUTABLE;
|
|
BufferData contents;
|
|
contents.pData = payload.data();
|
|
contents.DataSize = desc.Size;
|
|
RefCntAutoPtr<IBuffer> buffer;
|
|
device_->CreateBuffer(desc, &contents, &buffer);
|
|
if (!buffer || prepare(0, true) == nullptr) {
|
|
return;
|
|
}
|
|
IBuffer* buffers[] = {buffer};
|
|
context_->SetVertexBuffers(0, 1, buffers, nullptr, RESOURCE_STATE_TRANSITION_MODE_TRANSITION,
|
|
SET_VERTEX_BUFFERS_FLAG_RESET);
|
|
DrawAttribs attributes;
|
|
attributes.NumVertices = 4;
|
|
attributes.NumInstances = sprites->count;
|
|
context_->Draw(attributes);
|
|
}
|
|
|
|
const Scene::Shaders& Scene::shaders(std::uint32_t streams, std::uint32_t textures, bool sprites)
|
|
{
|
|
const std::uint32_t key = streams | (textures << 3) | (sprites ? 1u << 5 : 0);
|
|
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" + shaderSource_;
|
|
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 = sprites ? "Fixed function particles (vertex)" : "Fixed function (vertex)";
|
|
info.EntryPoint = sprites ? "vs_sprite" : "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, bool sprites)
|
|
{
|
|
const std::uint32_t blend = state_.blending ? state_.blendMode : 0;
|
|
const bool culling = state_.culling && !sprites; // point sprites aren't culled
|
|
const std::uint32_t key = streams | (textures << 3) | (blend << 5) | (state_.depthTest ? 1u << 8 : 0) |
|
|
(state_.depthWrite ? 1u << 9 : 0) | (culling ? 1u << 10 : 0) |
|
|
(state_.normalInverting ? 1u << 11 : 0) | (sprites ? 1u << 12 : 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, sprites);
|
|
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 = sprites ? PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
|
// The client culls clockwise triangles, or counter-clockwise ones when normals are inverted (mirrored objects).
|
|
graphics.RasterizerDesc.CullMode = 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;
|
|
if (sprites) {
|
|
// One buffer of sprites, each a position and a color, read once per square (instance).
|
|
elements[0] = LayoutElement{0, 0, 3, VT_FLOAT32, False};
|
|
elements[1] = LayoutElement{3, 0, 4, VT_UINT8, True};
|
|
elements[1].RelativeOffset = 12;
|
|
for (LayoutElement& element : std::span(elements).first(2)) {
|
|
element.Stride = 16;
|
|
element.Frequency = INPUT_ELEMENT_FREQUENCY_PER_INSTANCE;
|
|
element.InstanceDataStepRate = 1;
|
|
}
|
|
count = 2;
|
|
} else {
|
|
elements[count] = LayoutElement{0, count, 3, VT_FLOAT32, False};
|
|
++count;
|
|
}
|
|
if (!sprites && (streams & hasNormals) != 0) {
|
|
elements[count] = LayoutElement{1, count, 3, VT_FLOAT32, False};
|
|
++count;
|
|
}
|
|
if (!sprites && (streams & hasTexCoords) != 0) {
|
|
elements[count] = LayoutElement{2, count, 2, VT_FLOAT32, False};
|
|
++count;
|
|
}
|
|
if (!sprites && (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
|