2018-09-26 13:25:58 +02:00
|
|
|
// Copyright 2018 The Chromium Embedded Framework Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be found
|
|
|
|
// in the LICENSE file.
|
|
|
|
//
|
|
|
|
// Portions Copyright (c) 2018 Daktronics with the following MIT License:
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
|
|
// in the Software without restriction, including without limitation the rights
|
|
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
// all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
#include "tests/cefclient/browser/osr_d3d11_win.h"
|
|
|
|
|
|
|
|
#include <iomanip> // For std::setw.
|
|
|
|
|
2019-10-02 13:43:38 +02:00
|
|
|
#if OS_WIN && ARCH_CPU_ARM_FAMILY
|
|
|
|
#define __prefetch(x) x
|
|
|
|
#endif
|
2018-09-26 13:25:58 +02:00
|
|
|
#include <d3dcompiler.h>
|
|
|
|
#include <directxmath.h>
|
|
|
|
|
|
|
|
#include "include/base/cef_logging.h"
|
|
|
|
#include "include/internal/cef_string.h"
|
|
|
|
#include "tests/shared/browser/util_win.h"
|
|
|
|
|
|
|
|
namespace client {
|
|
|
|
namespace d3d11 {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
// Wrap a raw COM pointer in a shared_ptr for auto Release().
|
|
|
|
template <class T>
|
|
|
|
std::shared_ptr<T> to_com_ptr(T* obj) {
|
|
|
|
return std::shared_ptr<T>(obj, [](T* p) {
|
2023-01-02 23:59:03 +01:00
|
|
|
if (p) {
|
2018-09-26 13:25:58 +02:00
|
|
|
p->Release();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2018-09-26 13:25:58 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
struct SimpleVertex {
|
|
|
|
DirectX::XMFLOAT3 pos;
|
|
|
|
DirectX::XMFLOAT2 tex;
|
|
|
|
};
|
|
|
|
|
|
|
|
Context::Context(ID3D11DeviceContext* ctx) : ctx_(to_com_ptr(ctx)) {}
|
|
|
|
|
|
|
|
void Context::flush() {
|
|
|
|
ctx_->Flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
SwapChain::SwapChain(IDXGISwapChain* swapchain,
|
|
|
|
ID3D11RenderTargetView* rtv,
|
|
|
|
ID3D11SamplerState* sampler,
|
|
|
|
ID3D11BlendState* blender)
|
|
|
|
: sampler_(to_com_ptr(sampler)),
|
|
|
|
blender_(to_com_ptr(blender)),
|
|
|
|
swapchain_(to_com_ptr(swapchain)),
|
|
|
|
rtv_(to_com_ptr(rtv)),
|
|
|
|
width_(0),
|
|
|
|
height_(0) {}
|
|
|
|
|
|
|
|
void SwapChain::bind(const std::shared_ptr<Context>& ctx) {
|
|
|
|
ctx_ = ctx;
|
|
|
|
ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
|
|
|
|
ID3D11RenderTargetView* rtv[1] = {rtv_.get()};
|
|
|
|
d3d11_ctx->OMSetRenderTargets(1, rtv, nullptr);
|
|
|
|
|
|
|
|
// Set default blending state.
|
|
|
|
if (blender_) {
|
|
|
|
float factor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
d3d11_ctx->OMSetBlendState(blender_.get(), factor, 0xffffffff);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set default sampler state.
|
|
|
|
if (sampler_) {
|
|
|
|
ID3D11SamplerState* samplers[1] = {sampler_.get()};
|
|
|
|
d3d11_ctx->PSSetSamplers(0, 1, samplers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SwapChain::unbind() {
|
|
|
|
ctx_.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SwapChain::clear(float red, float green, float blue, float alpha) {
|
|
|
|
ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
CHECK(d3d11_ctx);
|
|
|
|
|
|
|
|
FLOAT color[4] = {red, green, blue, alpha};
|
|
|
|
d3d11_ctx->ClearRenderTargetView(rtv_.get(), color);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SwapChain::present(int sync_interval) {
|
|
|
|
swapchain_->Present(sync_interval, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SwapChain::resize(int width, int height) {
|
|
|
|
if (width <= 0 || height <= 0 || width == width_ || height == height_) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
width_ = width;
|
|
|
|
height_ = height;
|
|
|
|
|
|
|
|
ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
CHECK(d3d11_ctx);
|
|
|
|
|
|
|
|
d3d11_ctx->OMSetRenderTargets(0, 0, 0);
|
|
|
|
rtv_.reset();
|
|
|
|
|
|
|
|
DXGI_SWAP_CHAIN_DESC desc;
|
|
|
|
swapchain_->GetDesc(&desc);
|
|
|
|
auto hr = swapchain_->ResizeBuffers(0, width, height, desc.BufferDesc.Format,
|
|
|
|
desc.Flags);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
LOG(ERROR) << "d3d11: Failed to resize swapchain (" << width << "x"
|
|
|
|
<< height << ")";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ID3D11Texture2D* buffer = nullptr;
|
|
|
|
hr = swapchain_->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&buffer);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
LOG(ERROR) << "d3d11: Failed to resize swapchain (" << width << "x"
|
|
|
|
<< height << ")";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ID3D11Device* dev = nullptr;
|
|
|
|
d3d11_ctx->GetDevice(&dev);
|
|
|
|
if (dev) {
|
|
|
|
D3D11_RENDER_TARGET_VIEW_DESC vdesc = {};
|
|
|
|
vdesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
|
|
|
|
vdesc.Texture2D.MipSlice = 0;
|
|
|
|
vdesc.Format = desc.BufferDesc.Format;
|
|
|
|
|
|
|
|
ID3D11RenderTargetView* view = nullptr;
|
|
|
|
hr = dev->CreateRenderTargetView(buffer, &vdesc, &view);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
rtv_ = to_com_ptr(view);
|
|
|
|
d3d11_ctx->OMSetRenderTargets(1, &view, nullptr);
|
|
|
|
}
|
|
|
|
dev->Release();
|
|
|
|
}
|
|
|
|
buffer->Release();
|
|
|
|
|
|
|
|
D3D11_VIEWPORT vp;
|
|
|
|
vp.Width = static_cast<float>(width);
|
|
|
|
vp.Height = static_cast<float>(height);
|
|
|
|
vp.MinDepth = D3D11_MIN_DEPTH;
|
|
|
|
vp.MaxDepth = D3D11_MAX_DEPTH;
|
|
|
|
vp.TopLeftX = 0;
|
|
|
|
vp.TopLeftY = 0;
|
|
|
|
d3d11_ctx->RSSetViewports(1, &vp);
|
|
|
|
}
|
|
|
|
|
|
|
|
Effect::Effect(ID3D11VertexShader* vsh,
|
|
|
|
ID3D11PixelShader* psh,
|
|
|
|
ID3D11InputLayout* layout)
|
|
|
|
: vsh_(to_com_ptr(vsh)),
|
|
|
|
psh_(to_com_ptr(psh)),
|
|
|
|
layout_(to_com_ptr(layout)) {}
|
|
|
|
|
|
|
|
void Effect::bind(const std::shared_ptr<Context>& ctx) {
|
|
|
|
ctx_ = ctx;
|
|
|
|
ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
|
|
|
|
d3d11_ctx->IASetInputLayout(layout_.get());
|
|
|
|
d3d11_ctx->VSSetShader(vsh_.get(), nullptr, 0);
|
|
|
|
d3d11_ctx->PSSetShader(psh_.get(), nullptr, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Effect::unbind() {}
|
|
|
|
|
|
|
|
Geometry::Geometry(D3D_PRIMITIVE_TOPOLOGY primitive,
|
|
|
|
uint32_t vertices,
|
|
|
|
uint32_t stride,
|
|
|
|
ID3D11Buffer* buffer)
|
|
|
|
: primitive_(primitive),
|
|
|
|
vertices_(vertices),
|
|
|
|
stride_(stride),
|
|
|
|
buffer_(to_com_ptr(buffer)) {}
|
|
|
|
|
|
|
|
void Geometry::bind(const std::shared_ptr<Context>& ctx) {
|
|
|
|
ctx_ = ctx;
|
|
|
|
ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
|
|
|
|
// TODO: Handle offset.
|
|
|
|
uint32_t offset = 0;
|
|
|
|
|
|
|
|
ID3D11Buffer* buffers[1] = {buffer_.get()};
|
|
|
|
d3d11_ctx->IASetVertexBuffers(0, 1, buffers, &stride_, &offset);
|
|
|
|
d3d11_ctx->IASetPrimitiveTopology(primitive_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Geometry::unbind() {}
|
|
|
|
|
|
|
|
void Geometry::draw() {
|
|
|
|
ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
CHECK(d3d11_ctx);
|
|
|
|
|
|
|
|
// TODO: Handle offset.
|
|
|
|
d3d11_ctx->Draw(vertices_, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
Texture2D::Texture2D(ID3D11Texture2D* tex, ID3D11ShaderResourceView* srv)
|
|
|
|
: texture_(to_com_ptr(tex)), srv_(to_com_ptr(srv)) {
|
|
|
|
share_handle_ = nullptr;
|
|
|
|
|
|
|
|
IDXGIResource* res = nullptr;
|
|
|
|
if (SUCCEEDED(texture_->QueryInterface(__uuidof(IDXGIResource),
|
|
|
|
reinterpret_cast<void**>(&res)))) {
|
|
|
|
res->GetSharedHandle(&share_handle_);
|
|
|
|
res->Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Are we using a keyed mutex?
|
|
|
|
IDXGIKeyedMutex* mutex = nullptr;
|
|
|
|
if (SUCCEEDED(texture_->QueryInterface(__uuidof(IDXGIKeyedMutex),
|
|
|
|
(void**)&mutex))) {
|
|
|
|
keyed_mutex_ = to_com_ptr(mutex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Texture2D::width() const {
|
|
|
|
D3D11_TEXTURE2D_DESC desc;
|
|
|
|
texture_->GetDesc(&desc);
|
|
|
|
return desc.Width;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Texture2D::height() const {
|
|
|
|
D3D11_TEXTURE2D_DESC desc;
|
|
|
|
texture_->GetDesc(&desc);
|
|
|
|
return desc.Height;
|
|
|
|
}
|
|
|
|
|
|
|
|
DXGI_FORMAT Texture2D::format() const {
|
|
|
|
D3D11_TEXTURE2D_DESC desc;
|
|
|
|
texture_->GetDesc(&desc);
|
|
|
|
return desc.Format;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Texture2D::has_mutex() const {
|
|
|
|
return (keyed_mutex_.get() != nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Texture2D::lock_key(uint64_t key, uint32_t timeout_ms) {
|
|
|
|
if (keyed_mutex_) {
|
|
|
|
const auto hr = keyed_mutex_->AcquireSync(key, timeout_ms);
|
2020-05-12 21:41:20 +02:00
|
|
|
return (hr == S_OK);
|
2018-09-26 13:25:58 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Texture2D::unlock_key(uint64_t key) {
|
|
|
|
if (keyed_mutex_) {
|
|
|
|
keyed_mutex_->ReleaseSync(key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Texture2D::bind(const std::shared_ptr<Context>& ctx) {
|
|
|
|
ctx_ = ctx;
|
|
|
|
ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
if (srv_) {
|
|
|
|
ID3D11ShaderResourceView* views[1] = {srv_.get()};
|
|
|
|
d3d11_ctx->PSSetShaderResources(0, 1, views);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Texture2D::unbind() {}
|
|
|
|
|
|
|
|
void* Texture2D::share_handle() const {
|
|
|
|
return share_handle_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Texture2D::copy_from(const std::shared_ptr<Texture2D>& other) {
|
|
|
|
ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
CHECK(d3d11_ctx);
|
|
|
|
if (other) {
|
|
|
|
d3d11_ctx->CopyResource(texture_.get(), other->texture_.get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Device::Device(ID3D11Device* pdev, ID3D11DeviceContext* pctx)
|
|
|
|
: device_(to_com_ptr(pdev)), ctx_(std::make_shared<Context>(pctx)) {
|
|
|
|
lib_compiler_ = LoadLibrary(L"d3dcompiler_47.dll");
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
std::shared_ptr<Device> Device::create() {
|
|
|
|
UINT flags = 0;
|
|
|
|
#ifdef _DEBUG
|
|
|
|
flags |= D3D11_CREATE_DEVICE_DEBUG;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
D3D_FEATURE_LEVEL feature_levels[] = {
|
|
|
|
D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
|
|
|
|
D3D_FEATURE_LEVEL_10_0,
|
|
|
|
// D3D_FEATURE_LEVEL_9_3,
|
|
|
|
};
|
|
|
|
UINT num_feature_levels = sizeof(feature_levels) / sizeof(feature_levels[0]);
|
|
|
|
|
|
|
|
ID3D11Device* pdev = nullptr;
|
|
|
|
ID3D11DeviceContext* pctx = nullptr;
|
|
|
|
|
|
|
|
D3D_FEATURE_LEVEL selected_level;
|
|
|
|
HRESULT hr = D3D11CreateDevice(
|
|
|
|
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags, feature_levels,
|
|
|
|
num_feature_levels, D3D11_SDK_VERSION, &pdev, &selected_level, &pctx);
|
|
|
|
|
|
|
|
if (hr == E_INVALIDARG) {
|
|
|
|
// DirectX 11.0 platforms will not recognize D3D_FEATURE_LEVEL_11_1
|
|
|
|
// so we need to retry without it.
|
|
|
|
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags,
|
|
|
|
&feature_levels[1], num_feature_levels - 1,
|
|
|
|
D3D11_SDK_VERSION, &pdev, &selected_level, &pctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
const auto dev = std::make_shared<Device>(pdev, pctx);
|
|
|
|
|
|
|
|
LOG(INFO) << "d3d11: Selected adapter " << dev->adapter_name()
|
|
|
|
<< " and feature level 0x" << std::setw(4) << std::hex
|
|
|
|
<< selected_level;
|
|
|
|
|
|
|
|
return dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string Device::adapter_name() const {
|
|
|
|
IDXGIDevice* dxgi_dev = nullptr;
|
|
|
|
auto hr = device_->QueryInterface(__uuidof(dxgi_dev), (void**)&dxgi_dev);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
IDXGIAdapter* dxgi_adapt = nullptr;
|
|
|
|
hr = dxgi_dev->GetAdapter(&dxgi_adapt);
|
|
|
|
dxgi_dev->Release();
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
DXGI_ADAPTER_DESC desc;
|
|
|
|
hr = dxgi_adapt->GetDesc(&desc);
|
|
|
|
dxgi_adapt->Release();
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
return CefString(desc.Description);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "n/a";
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Context> Device::immedidate_context() {
|
|
|
|
return ctx_;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<SwapChain> Device::create_swapchain(HWND window,
|
|
|
|
int width,
|
|
|
|
int height) {
|
|
|
|
HRESULT hr;
|
|
|
|
IDXGIFactory1* dxgi_factory = nullptr;
|
|
|
|
|
|
|
|
// Default size to the window size unless specified.
|
|
|
|
RECT rc_bounds;
|
|
|
|
GetClientRect(window, &rc_bounds);
|
|
|
|
if (width <= 0) {
|
|
|
|
width = rc_bounds.right - rc_bounds.left;
|
|
|
|
}
|
|
|
|
if (height <= 0) {
|
|
|
|
height = rc_bounds.bottom - rc_bounds.top;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
IDXGIDevice* dxgi_dev = nullptr;
|
|
|
|
hr = device_->QueryInterface(__uuidof(IDXGIDevice),
|
|
|
|
reinterpret_cast<void**>(&dxgi_dev));
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
IDXGIAdapter* adapter = nullptr;
|
|
|
|
hr = dxgi_dev->GetAdapter(&adapter);
|
|
|
|
dxgi_dev->Release();
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
hr = adapter->GetParent(__uuidof(IDXGIFactory1),
|
|
|
|
reinterpret_cast<void**>(&dxgi_factory));
|
|
|
|
adapter->Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dxgi_factory) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
IDXGISwapChain* swapchain = nullptr;
|
|
|
|
|
|
|
|
// Create swap chain.
|
|
|
|
IDXGIFactory2* dxgi_factory2 = nullptr;
|
|
|
|
hr = dxgi_factory->QueryInterface(__uuidof(IDXGIFactory2),
|
|
|
|
reinterpret_cast<void**>(&dxgi_factory2));
|
|
|
|
if (dxgi_factory2) {
|
|
|
|
DXGI_SWAP_CHAIN_DESC1 sd;
|
|
|
|
ZeroMemory(&sd, sizeof(sd));
|
|
|
|
sd.Width = width;
|
|
|
|
sd.Height = height;
|
|
|
|
sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
sd.SampleDesc.Count = 1;
|
|
|
|
sd.SampleDesc.Quality = 0;
|
|
|
|
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
|
|
sd.BufferCount = 1;
|
|
|
|
|
|
|
|
IDXGISwapChain1* swapchain1 = nullptr;
|
|
|
|
hr = dxgi_factory2->CreateSwapChainForHwnd(device_.get(), window, &sd,
|
|
|
|
nullptr, nullptr, &swapchain1);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
hr = swapchain1->QueryInterface(__uuidof(IDXGISwapChain),
|
|
|
|
reinterpret_cast<void**>(&swapchain));
|
|
|
|
swapchain1->Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
dxgi_factory2->Release();
|
|
|
|
} else {
|
|
|
|
// DirectX 11.0 systems.
|
|
|
|
DXGI_SWAP_CHAIN_DESC sd;
|
|
|
|
ZeroMemory(&sd, sizeof(sd));
|
|
|
|
sd.BufferCount = 1;
|
|
|
|
sd.BufferDesc.Width = width;
|
|
|
|
sd.BufferDesc.Height = height;
|
|
|
|
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
|
|
sd.BufferDesc.RefreshRate.Numerator = 60;
|
|
|
|
sd.BufferDesc.RefreshRate.Denominator = 1;
|
|
|
|
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
|
|
sd.OutputWindow = window;
|
|
|
|
sd.SampleDesc.Count = 1;
|
|
|
|
sd.SampleDesc.Quality = 0;
|
|
|
|
sd.Windowed = TRUE;
|
|
|
|
|
|
|
|
hr = dxgi_factory->CreateSwapChain(device_.get(), &sd, &swapchain);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't handle full-screen swapchains so we block the ALT+ENTER shortcut.
|
|
|
|
dxgi_factory->MakeWindowAssociation(window, DXGI_MWA_NO_ALT_ENTER);
|
|
|
|
dxgi_factory->Release();
|
|
|
|
|
|
|
|
if (!swapchain) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
ID3D11Texture2D* back_buffer = nullptr;
|
|
|
|
hr = swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D),
|
|
|
|
reinterpret_cast<void**>(&back_buffer));
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
swapchain->Release();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
ID3D11RenderTargetView* rtv = nullptr;
|
|
|
|
hr = device_->CreateRenderTargetView(back_buffer, nullptr, &rtv);
|
|
|
|
back_buffer->Release();
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
swapchain->Release();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto ctx = (ID3D11DeviceContext*)(*ctx_);
|
|
|
|
|
|
|
|
ctx->OMSetRenderTargets(1, &rtv, nullptr);
|
|
|
|
|
|
|
|
// Setup the viewport.
|
|
|
|
D3D11_VIEWPORT vp;
|
|
|
|
vp.Width = (FLOAT)width;
|
|
|
|
vp.Height = (FLOAT)height;
|
|
|
|
vp.MinDepth = 0.0f;
|
|
|
|
vp.MaxDepth = 1.0f;
|
|
|
|
vp.TopLeftX = 0;
|
|
|
|
vp.TopLeftY = 0;
|
|
|
|
ctx->RSSetViewports(1, &vp);
|
|
|
|
|
|
|
|
// Create a default sampler to use.
|
|
|
|
ID3D11SamplerState* sampler = nullptr;
|
|
|
|
{
|
|
|
|
D3D11_SAMPLER_DESC desc = {};
|
|
|
|
desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
|
|
desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
|
|
desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
|
|
desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
|
|
|
|
desc.MinLOD = 0.0f;
|
|
|
|
desc.MaxLOD = D3D11_FLOAT32_MAX;
|
|
|
|
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
|
|
|
device_->CreateSamplerState(&desc, &sampler);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a default blend state to use (pre-multiplied alpha).
|
|
|
|
ID3D11BlendState* blender = nullptr;
|
|
|
|
{
|
|
|
|
D3D11_BLEND_DESC desc;
|
|
|
|
desc.AlphaToCoverageEnable = FALSE;
|
|
|
|
desc.IndependentBlendEnable = FALSE;
|
|
|
|
const auto count = sizeof(desc.RenderTarget) / sizeof(desc.RenderTarget[0]);
|
|
|
|
for (size_t n = 0; n < count; ++n) {
|
|
|
|
desc.RenderTarget[n].BlendEnable = TRUE;
|
|
|
|
desc.RenderTarget[n].SrcBlend = D3D11_BLEND_ONE;
|
|
|
|
desc.RenderTarget[n].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
|
|
|
desc.RenderTarget[n].SrcBlendAlpha = D3D11_BLEND_ONE;
|
|
|
|
desc.RenderTarget[n].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
|
|
|
|
desc.RenderTarget[n].BlendOp = D3D11_BLEND_OP_ADD;
|
|
|
|
desc.RenderTarget[n].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
|
|
|
desc.RenderTarget[n].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
|
|
|
|
}
|
|
|
|
device_->CreateBlendState(&desc, &blender);
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_shared<SwapChain>(swapchain, rtv, sampler, blender);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Geometry> Device::create_quad(float x,
|
|
|
|
float y,
|
|
|
|
float width,
|
|
|
|
float height,
|
|
|
|
bool flip) {
|
|
|
|
x = (x * 2.0f) - 1.0f;
|
|
|
|
y = 1.0f - (y * 2.0f);
|
|
|
|
width = width * 2.0f;
|
|
|
|
height = height * 2.0f;
|
|
|
|
float z = 1.0f;
|
|
|
|
|
|
|
|
SimpleVertex vertices[] = {
|
|
|
|
|
|
|
|
{DirectX::XMFLOAT3(x, y, z), DirectX::XMFLOAT2(0.0f, 0.0f)},
|
|
|
|
{DirectX::XMFLOAT3(x + width, y, z), DirectX::XMFLOAT2(1.0f, 0.0f)},
|
|
|
|
{DirectX::XMFLOAT3(x, y - height, z), DirectX::XMFLOAT2(0.0f, 1.0f)},
|
|
|
|
{DirectX::XMFLOAT3(x + width, y - height, z),
|
|
|
|
DirectX::XMFLOAT2(1.0f, 1.0f)}};
|
|
|
|
|
|
|
|
if (flip) {
|
|
|
|
DirectX::XMFLOAT2 tmp(vertices[2].tex);
|
|
|
|
vertices[2].tex = vertices[0].tex;
|
|
|
|
vertices[0].tex = tmp;
|
|
|
|
|
|
|
|
tmp = vertices[3].tex;
|
|
|
|
vertices[3].tex = vertices[1].tex;
|
|
|
|
vertices[1].tex = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
D3D11_BUFFER_DESC desc = {};
|
|
|
|
desc.Usage = D3D11_USAGE_DEFAULT;
|
|
|
|
desc.ByteWidth = sizeof(SimpleVertex) * 4;
|
|
|
|
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
|
|
|
desc.CPUAccessFlags = 0;
|
|
|
|
|
|
|
|
D3D11_SUBRESOURCE_DATA srd = {};
|
|
|
|
srd.pSysMem = vertices;
|
|
|
|
|
|
|
|
ID3D11Buffer* buffer = nullptr;
|
|
|
|
const auto hr = device_->CreateBuffer(&desc, &srd, &buffer);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
return std::make_shared<Geometry>(
|
|
|
|
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, 4,
|
|
|
|
static_cast<uint32_t>(sizeof(SimpleVertex)), buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Texture2D> Device::open_shared_texture(void* handle) {
|
|
|
|
ID3D11Texture2D* tex = nullptr;
|
|
|
|
auto hr = device_->OpenSharedResource(handle, __uuidof(ID3D11Texture2D),
|
|
|
|
(void**)(&tex));
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
D3D11_TEXTURE2D_DESC td;
|
|
|
|
tex->GetDesc(&td);
|
|
|
|
|
|
|
|
ID3D11ShaderResourceView* srv = nullptr;
|
|
|
|
|
|
|
|
if (td.BindFlags & D3D11_BIND_SHADER_RESOURCE) {
|
|
|
|
D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
|
|
|
|
srv_desc.Format = td.Format;
|
|
|
|
srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
|
|
|
srv_desc.Texture2D.MostDetailedMip = 0;
|
|
|
|
srv_desc.Texture2D.MipLevels = 1;
|
|
|
|
|
|
|
|
hr = device_->CreateShaderResourceView(tex, &srv_desc, &srv);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
tex->Release();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_shared<Texture2D>(tex, srv);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Texture2D> Device::create_texture(int width,
|
|
|
|
int height,
|
|
|
|
DXGI_FORMAT format,
|
|
|
|
const void* data,
|
|
|
|
size_t row_stride) {
|
|
|
|
D3D11_TEXTURE2D_DESC td;
|
|
|
|
td.ArraySize = 1;
|
|
|
|
td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
|
|
|
td.CPUAccessFlags = 0;
|
|
|
|
td.Format = format;
|
|
|
|
td.Width = width;
|
|
|
|
td.Height = height;
|
|
|
|
td.MipLevels = 1;
|
|
|
|
td.MiscFlags = 0;
|
|
|
|
td.SampleDesc.Count = 1;
|
|
|
|
td.SampleDesc.Quality = 0;
|
|
|
|
td.Usage = D3D11_USAGE_DEFAULT;
|
|
|
|
|
|
|
|
D3D11_SUBRESOURCE_DATA srd;
|
|
|
|
srd.pSysMem = data;
|
|
|
|
srd.SysMemPitch = static_cast<uint32_t>(row_stride);
|
|
|
|
srd.SysMemSlicePitch = 0;
|
|
|
|
|
|
|
|
ID3D11Texture2D* tex = nullptr;
|
|
|
|
auto hr = device_->CreateTexture2D(&td, data ? &srd : nullptr, &tex);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
|
|
|
|
srv_desc.Format = td.Format;
|
|
|
|
srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
|
|
|
srv_desc.Texture2D.MostDetailedMip = 0;
|
|
|
|
srv_desc.Texture2D.MipLevels = 1;
|
|
|
|
|
|
|
|
ID3D11ShaderResourceView* srv = nullptr;
|
|
|
|
hr = device_->CreateShaderResourceView(tex, &srv_desc, &srv);
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
tex->Release();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_shared<Texture2D>(tex, srv);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<ID3DBlob> Device::compile_shader(const std::string& source_code,
|
|
|
|
const std::string& entry_point,
|
|
|
|
const std::string& model) {
|
|
|
|
if (!lib_compiler_) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef HRESULT(WINAPI * PFN_D3DCOMPILE)(
|
|
|
|
LPCVOID, SIZE_T, LPCSTR, const D3D_SHADER_MACRO*, ID3DInclude*, LPCSTR,
|
|
|
|
LPCSTR, UINT, UINT, ID3DBlob**, ID3DBlob**);
|
|
|
|
|
|
|
|
const auto fnc_compile = reinterpret_cast<PFN_D3DCOMPILE>(
|
|
|
|
GetProcAddress(lib_compiler_, "D3DCompile"));
|
|
|
|
if (!fnc_compile) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS;
|
|
|
|
|
|
|
|
#if defined(NDEBUG)
|
|
|
|
// flags |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
|
|
|
|
// flags |= D3DCOMPILE_AVOID_FLOW_CONTROL;
|
|
|
|
#else
|
|
|
|
flags |= D3DCOMPILE_DEBUG;
|
|
|
|
flags |= D3DCOMPILE_SKIP_OPTIMIZATION;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
ID3DBlob* blob = nullptr;
|
|
|
|
ID3DBlob* blob_err = nullptr;
|
|
|
|
|
|
|
|
const auto psrc = source_code.c_str();
|
|
|
|
const auto len = source_code.size() + 1;
|
|
|
|
|
|
|
|
const auto hr =
|
|
|
|
fnc_compile(psrc, len, nullptr, nullptr, nullptr, entry_point.c_str(),
|
|
|
|
model.c_str(), flags, 0, &blob, &blob_err);
|
|
|
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
if (blob_err) {
|
|
|
|
// TODO: Log the error.
|
|
|
|
blob_err->Release();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (blob_err) {
|
|
|
|
blob_err->Release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::shared_ptr<ID3DBlob>(blob, [](ID3DBlob* p) {
|
2023-01-02 23:59:03 +01:00
|
|
|
if (p) {
|
2018-09-26 13:25:58 +02:00
|
|
|
p->Release();
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2018-09-26 13:25:58 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Effect> Device::create_default_effect() {
|
|
|
|
const auto vsh =
|
|
|
|
R"--(struct VS_INPUT
|
|
|
|
{
|
|
|
|
float4 pos : POSITION;
|
|
|
|
float2 tex : TEXCOORD0;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct VS_OUTPUT
|
|
|
|
{
|
|
|
|
float4 pos : SV_POSITION;
|
|
|
|
float2 tex : TEXCOORD0;
|
|
|
|
};
|
|
|
|
|
|
|
|
VS_OUTPUT main(VS_INPUT input)
|
|
|
|
{
|
|
|
|
VS_OUTPUT output;
|
|
|
|
output.pos = input.pos;
|
|
|
|
output.tex = input.tex;
|
|
|
|
return output;
|
|
|
|
})--";
|
|
|
|
|
|
|
|
const auto psh =
|
|
|
|
R"--(Texture2D tex0 : register(t0);
|
|
|
|
SamplerState samp0 : register(s0);
|
|
|
|
|
|
|
|
struct VS_OUTPUT
|
|
|
|
{
|
|
|
|
float4 pos : SV_POSITION;
|
|
|
|
float2 tex : TEXCOORD0;
|
|
|
|
};
|
|
|
|
|
|
|
|
float4 main(VS_OUTPUT input) : SV_Target
|
|
|
|
{
|
|
|
|
return tex0.Sample(samp0, input.tex);
|
|
|
|
})--";
|
|
|
|
|
|
|
|
return create_effect(vsh, "main", "vs_4_0", psh, "main", "ps_4_0");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Effect> Device::create_effect(const std::string& vertex_code,
|
|
|
|
const std::string& vertex_entry,
|
|
|
|
const std::string& vertex_model,
|
|
|
|
const std::string& pixel_code,
|
|
|
|
const std::string& pixel_entry,
|
|
|
|
const std::string& pixel_model) {
|
|
|
|
const auto vs_blob = compile_shader(vertex_code, vertex_entry, vertex_model);
|
|
|
|
|
|
|
|
ID3D11VertexShader* vshdr = nullptr;
|
|
|
|
ID3D11InputLayout* layout = nullptr;
|
|
|
|
|
|
|
|
if (vs_blob) {
|
|
|
|
device_->CreateVertexShader(vs_blob->GetBufferPointer(),
|
|
|
|
vs_blob->GetBufferSize(), nullptr, &vshdr);
|
|
|
|
|
|
|
|
D3D11_INPUT_ELEMENT_DESC layout_desc[] = {
|
|
|
|
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
|
|
|
|
D3D11_INPUT_PER_VERTEX_DATA, 0},
|
|
|
|
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12,
|
|
|
|
D3D11_INPUT_PER_VERTEX_DATA, 0},
|
|
|
|
};
|
|
|
|
|
|
|
|
UINT elements = ARRAYSIZE(layout_desc);
|
|
|
|
|
|
|
|
// Create the input layout.
|
|
|
|
device_->CreateInputLayout(layout_desc, elements,
|
|
|
|
vs_blob->GetBufferPointer(),
|
|
|
|
vs_blob->GetBufferSize(), &layout);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto ps_blob = compile_shader(pixel_code, pixel_entry, pixel_model);
|
|
|
|
ID3D11PixelShader* pshdr = nullptr;
|
|
|
|
if (ps_blob) {
|
|
|
|
device_->CreatePixelShader(ps_blob->GetBufferPointer(),
|
|
|
|
ps_blob->GetBufferSize(), nullptr, &pshdr);
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_shared<Effect>(vshdr, pshdr, layout);
|
|
|
|
}
|
|
|
|
|
|
|
|
Layer::Layer(const std::shared_ptr<Device>& device, bool flip)
|
|
|
|
: device_(device), flip_(flip) {
|
|
|
|
bounds_.x = bounds_.y = bounds_.width = bounds_.height = 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
Layer::~Layer() {}
|
|
|
|
|
|
|
|
void Layer::attach(const std::shared_ptr<Composition>& parent) {
|
|
|
|
composition_ = parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Composition> Layer::composition() const {
|
|
|
|
return composition_.lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
Rect Layer::bounds() const {
|
|
|
|
return bounds_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Layer::move(float x, float y, float width, float height) {
|
|
|
|
bounds_.x = x;
|
|
|
|
bounds_.y = y;
|
|
|
|
bounds_.width = width;
|
|
|
|
bounds_.height = height;
|
|
|
|
|
|
|
|
// It's not efficient to create the quad everytime we move, but for now we're
|
|
|
|
// just trying to get something on-screen.
|
|
|
|
geometry_.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Layer::tick(double) {
|
|
|
|
// Nothing to update in the base class.
|
|
|
|
}
|
|
|
|
|
|
|
|
void Layer::render_texture(const std::shared_ptr<Context>& ctx,
|
|
|
|
const std::shared_ptr<Texture2D>& texture) {
|
|
|
|
if (!geometry_) {
|
|
|
|
geometry_ = device_->create_quad(bounds_.x, bounds_.y, bounds_.width,
|
|
|
|
bounds_.height, flip_);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (geometry_ && texture) {
|
|
|
|
// We need a shader.
|
|
|
|
if (!effect_) {
|
|
|
|
effect_ = device_->create_default_effect();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bind our states/resource to the pipeline.
|
|
|
|
ScopedBinder<Geometry> quad_binder(ctx, geometry_);
|
|
|
|
ScopedBinder<Effect> fx_binder(ctx, effect_);
|
|
|
|
ScopedBinder<Texture2D> tex_binder(ctx, texture);
|
|
|
|
|
|
|
|
// Draw the quad.
|
|
|
|
geometry_->draw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Composition::Composition(const std::shared_ptr<Device>& device,
|
|
|
|
int width,
|
|
|
|
int height)
|
|
|
|
: width_(width), height_(height), vsync_(true), device_(device) {
|
|
|
|
fps_ = 0.0;
|
|
|
|
time_ = 0.0;
|
|
|
|
frame_ = 0;
|
|
|
|
fps_start_ = GetTimeNow();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Composition::is_vsync() const {
|
|
|
|
return vsync_;
|
|
|
|
}
|
|
|
|
|
|
|
|
double Composition::time() const {
|
|
|
|
return time_;
|
|
|
|
}
|
|
|
|
|
|
|
|
double Composition::fps() const {
|
|
|
|
return fps_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Composition::add_layer(const std::shared_ptr<Layer>& layer) {
|
|
|
|
if (layer) {
|
|
|
|
layers_.push_back(layer);
|
|
|
|
|
|
|
|
// Attach ourselves as the parent.
|
|
|
|
layer->attach(shared_from_this());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Composition::remove_layer(const std::shared_ptr<Layer>& layer) {
|
|
|
|
size_t match = 0;
|
|
|
|
if (layer) {
|
|
|
|
for (auto i = layers_.begin(); i != layers_.end();) {
|
|
|
|
if ((*i).get() == layer.get()) {
|
|
|
|
i = layers_.erase(i);
|
|
|
|
match++;
|
|
|
|
} else {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (match > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Composition::resize(bool vsync, int width, int height) {
|
|
|
|
vsync_ = vsync;
|
|
|
|
width_ = width;
|
|
|
|
height_ = height;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Composition::tick(double t) {
|
|
|
|
time_ = t;
|
|
|
|
for (const auto& layer : layers_) {
|
|
|
|
layer->tick(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Composition::render(const std::shared_ptr<Context>& ctx) {
|
|
|
|
// Use painter's algorithm and render our layers in order (not doing any dept
|
|
|
|
// or 3D here).
|
|
|
|
for (const auto& layer : layers_) {
|
|
|
|
layer->render(ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
frame_++;
|
|
|
|
const auto now = GetTimeNow();
|
|
|
|
if ((now - fps_start_) > 1000000) {
|
|
|
|
fps_ = frame_ / double((now - fps_start_) / 1000000.0);
|
|
|
|
frame_ = 0;
|
|
|
|
fps_start_ = now;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FrameBuffer::FrameBuffer(const std::shared_ptr<Device>& device)
|
|
|
|
: device_(device) {}
|
|
|
|
|
|
|
|
void FrameBuffer::on_paint(void* shared_handle) {
|
|
|
|
// Did the shared texture change?
|
|
|
|
if (shared_buffer_ && shared_handle != shared_buffer_->share_handle()) {
|
|
|
|
shared_buffer_.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the shared texture.
|
|
|
|
if (!shared_buffer_) {
|
|
|
|
shared_buffer_ = device_->open_shared_texture((void*)shared_handle);
|
|
|
|
if (!shared_buffer_) {
|
|
|
|
LOG(ERROR) << "d3d11: Could not open shared texture!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace d3d11
|
2019-10-02 13:43:38 +02:00
|
|
|
} // namespace client
|