The adapter records everything the client draws (state, transforms, lights, fog, textures and buffers) as a command stream, sent in chunks through the shared memory channel, while IMVU's Direct3D renderer still draws the whole view. The renderer process draws it with Diligent, with shaders reproducing IMVU's Direct3D 9 fixed-function setup, over the right half of the 3D view to compare. It skips to the newest frame when it falls behind, and logs its errors and frame times. Co-Authored-By: Claude Opus 5.5 <[email protected]>
613 lines
24 KiB
C++
613 lines
24 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)
|
|
{
|
|
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:
|
|
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;
|
|
}
|
|
}
|
|
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<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;
|
|
}
|
|
// 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
|