Particles (point sprites) are drawn as instanced camera-facing squares sized like Direct3D 9's. Fog is by depth buffer value, as Direct3D 9 does with IMVU's projections. At most one frame is queued for display, frames carry the adapter's timestamp so the renderer process logs the delay to presenting, and the log's lines from several processes no longer mix. Co-Authored-By: Claude Opus 5.5 <[email protected]>
687 lines
28 KiB
C++
687 lines
28 KiB
C++
#include "scene.hpp"
|
|
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#include "fixed_function_hlsl.hpp"
|
|
#include "log.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:
|
|
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));
|
|
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;
|
|
}
|
|
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]);
|
|
}
|
|
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" + 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 = 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
|