Commit 64509cc5 authored by Laszlo Agocs's avatar Laszlo Agocs
Browse files

d3d: Add compressed and sRGB texture support

...and related plumbing here and there.
parent cba06005
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "../shared/examplefw.h"
#include "../shared/cube.h"
struct {
QRhiBuffer *vbuf = nullptr;
bool vbufReady = false;
QRhiBuffer *ubuf = nullptr;
QRhiTexture *tex = nullptr;
QRhiSampler *sampler = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
QRhiGraphicsPipeline *ps = nullptr;
float rotation = 0;
QByteArrayList compressedData;
} d;
static const quint32 DDS_MAGIC = 0x20534444; // 'DDS '
static const quint32 DDS_FOURCC = 4;
#define FOURCC(c0, c1, c2, c3) ((c0) | ((c1) << 8) | ((c2) << 16) | ((c3 << 24)))
struct DDS_PIXELFORMAT {
quint32 size;
quint32 flags;
quint32 fourCC;
quint32 rgbBitCount;
quint32 rBitMask;
quint32 gBitMask;
quint32 bBitMask;
quint32 aBitMask;
};
struct DDS_HEADER {
quint32 size;
quint32 flags;
quint32 height;
quint32 width;
quint32 pitch;
quint32 depth;
quint32 mipMapCount;
quint32 reserved1[11];
DDS_PIXELFORMAT pixelFormat;
quint32 caps;
quint32 caps2;
quint32 caps3;
quint32 caps4;
quint32 reserved2;
};
static quint32 bc1size(const QSize &size)
{
static const quint32 blockSize = 8; // 8 bytes for BC1
const quint32 bytesPerLine = qMax<quint32>(1, (size.width() + 3) / 4) * blockSize;
const quint32 ySize = qMax<quint32>(1, (size.height() + 3) / 4);
return bytesPerLine * ySize;
}
static QByteArrayList loadBC1(const QString &filename, QSize *size, int *mipLevelCount)
{
QFile f(filename);
if (!f.open(QIODevice::ReadOnly)) {
qWarning("Failed to open %s", qPrintable(filename));
return QByteArrayList();
}
quint32 magic = 0;
f.read(reinterpret_cast<char *>(&magic), sizeof(magic));
if (magic != DDS_MAGIC) {
qWarning("%s is not a DDS file", qPrintable(filename));
return QByteArrayList();
}
DDS_HEADER header;
f.read(reinterpret_cast<char *>(&header), sizeof(header));
if (header.size != sizeof(DDS_HEADER)) {
qWarning("Invalid DDS header size");
return QByteArrayList();
}
if (header.pixelFormat.size != sizeof(DDS_PIXELFORMAT)) {
qWarning("Invalid DDS pixel format size");
return QByteArrayList();
}
if (!(header.pixelFormat.flags & DDS_FOURCC)) {
qWarning("Invalid DDS pixel format");
return QByteArrayList();
}
if (header.pixelFormat.fourCC != FOURCC('D', 'X', 'T', '1')) {
qWarning("Only DXT1 (BC1) is supported");
return QByteArrayList();
}
QByteArrayList data;
QSize sz(header.width, header.height);
for (quint32 level = 0; level < header.mipMapCount; ++level) {
data.append(f.read(bc1size(sz)));
sz.setWidth(qMax(1, sz.width() / 2));
sz.setHeight(qMax(1, sz.height() / 2));
}
*size = QSize(header.width, header.height);
*mipLevelCount = header.mipMapCount;
return data;
}
void Window::customInit()
{
if (!m_r->canTextureFormatBeSupported(QRhiTexture::BC1))
qFatal("This backend does not support BC1");
d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube));
d.vbuf->build();
d.vbufReady = false;
d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68);
d.ubuf->build();
QSize size;
int mipCount = 0;
d.compressedData = loadBC1(QLatin1String(":/qt.dds"), &size, &mipCount);
d.tex = m_r->newTexture(QRhiTexture::BC1, size, 0);
d.tex->build();
d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
d.sampler->build();
d.srb = m_r->newShaderResourceBindings();
d.srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler)
});
d.srb->build();
d.ps = m_r->newGraphicsPipeline();
d.ps->setDepthTest(true);
d.ps->setDepthWrite(true);
d.ps->setDepthOp(QRhiGraphicsPipeline::Less);
d.ps->setCullMode(QRhiGraphicsPipeline::Back);
d.ps->setFrontFace(QRhiGraphicsPipeline::CCW);
const QBakedShader vs = getShader(QLatin1String(":/texture.vert.qsb"));
if (!vs.isValid())
qFatal("Failed to load shader pack (vertex)");
const QBakedShader fs = getShader(QLatin1String(":/texture.frag.qsb"));
if (!fs.isValid())
qFatal("Failed to load shader pack (fragment)");
d.ps->setShaderStages({
{ QRhiGraphicsShaderStage::Vertex, vs },
{ QRhiGraphicsShaderStage::Fragment, fs }
});
QRhiVertexInputLayout inputLayout;
inputLayout.bindings = {
{ 3 * sizeof(float) },
{ 2 * sizeof(float) }
};
inputLayout.attributes = {
{ 0, 0, QRhiVertexInputLayout::Attribute::Float3, 0 },
{ 1, 1, QRhiVertexInputLayout::Attribute::Float2, 0 }
};
d.ps->setVertexInputLayout(inputLayout);
d.ps->setShaderResourceBindings(d.srb);
d.ps->setRenderPassDescriptor(m_rp);
d.ps->build();
}
void Window::customRelease()
{
if (d.ps) {
d.ps->releaseAndDestroy();
d.ps = nullptr;
}
if (d.srb) {
d.srb->releaseAndDestroy();
d.srb = nullptr;
}
if (d.ubuf) {
d.ubuf->releaseAndDestroy();
d.ubuf = nullptr;
}
if (d.vbuf) {
d.vbuf->releaseAndDestroy();
d.vbuf = nullptr;
}
if (d.sampler) {
d.sampler->releaseAndDestroy();
d.sampler = nullptr;
}
if (d.tex) {
d.tex->releaseAndDestroy();
d.tex = nullptr;
}
}
void Window::customRender()
{
QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch();
if (!d.vbufReady) {
d.vbufReady = true;
u->uploadStaticBuffer(d.vbuf, cube);
qint32 flip = 0;
u->updateDynamicBuffer(d.ubuf, 64, 4, &flip);
}
if (!d.compressedData.isEmpty()) {
QRhiTextureUploadDescription::Layer::MipLevel image(d.compressedData[0]);
QRhiTextureUploadDescription desc;
desc.layers.append({ { image } });
u->uploadTexture(d.tex, desc);
d.compressedData.clear();
}
d.rotation += 1.0f;
QMatrix4x4 mvp = m_proj;
mvp.scale(0.5f);
mvp.rotate(d.rotation, 0, 1, 0);
u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData());
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
const QSize outputSizeInPixels = m_sc->effectivePixelSize();
m_r->beginPass(m_sc->currentFrameRenderTarget(), cb, { 0.4f, 0.7f, 0.0f, 1.0f }, { 1.0f, 0 }, u);
m_r->setGraphicsPipeline(cb, d.ps);
m_r->setViewport(cb, { 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
m_r->setVertexInput(cb, 0, { { d.vbuf, 0 }, { d.vbuf, 36 * 3 * sizeof(float) } });
m_r->draw(cb, 36);
m_r->endPass(cb);
}
TEMPLATE = app
QT += shadertools rhi
SOURCES = \
compressedtexture_bc1.cpp
RESOURCES = compressedtexture_bc1.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/rhi/compressedtexture_bc1
INSTALLS += target
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>qt.dds</file>
<file alias="texture.vert.qsb">../shared/texture.vert.qsb</file>
<file alias="texture.frag.qsb">../shared/texture.frag.qsb</file>
</qresource>
</RCC>
......@@ -2,6 +2,7 @@ TEMPLATE = subdirs
SUBDIRS += \
hellominimalcrossgfxtriangle \
compressedtexture_bc1 \
plainqwindow_gles2
qtConfig(vulkan) {
......
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
// Adapted from hellominimalcrossgfxtriangle with the frame rendering stripped out.
// Include this file and implement Window::customInit, release and render.
// Debug/validation layer is enabled for D3D and Vulkan.
#include <QGuiApplication>
#include <QCommandLineParser>
#include <QWindow>
#include <QPlatformSurfaceEvent>
#include <QElapsedTimer>
#include <QBakedShader>
#include <QFile>
#ifndef QT_NO_OPENGL
#include <QRhiGles2InitParams>
#include <QOpenGLContext>
#include <QOffscreenSurface>
#endif
#if QT_CONFIG(vulkan)
#include <QLoggingCategory>
#include <QRhiVulkanInitParams>
#endif
#ifdef Q_OS_WIN
#include <QRhiD3D11InitParams>
#endif
#ifdef Q_OS_DARWIN
#include <QRhiMetalInitParams>
#endif
QBakedShader getShader(const QString &name)
{
QFile f(name);
if (f.open(QIODevice::ReadOnly))
return QBakedShader::fromSerialized(f.readAll());
return QBakedShader();
}
enum GraphicsApi
{
OpenGL,
Vulkan,
D3D11,
Metal
};
GraphicsApi graphicsApi;
QString graphicsApiName()
{
switch (graphicsApi) {
case OpenGL:
return QLatin1String("OpenGL 2.x");
case Vulkan:
return QLatin1String("Vulkan");
case D3D11:
return QLatin1String("Direct3D 11");
case Metal:
return QLatin1String("Metal");
default:
break;
}
return QString();
}
class Window : public QWindow
{
public:
Window();
~Window();
protected:
void init();
void releaseResources();
void resizeSwapChain();
void releaseSwapChain();
void render();
void customInit();
void customRelease();
void customRender();
void exposeEvent(QExposeEvent *) override;
bool event(QEvent *) override;
bool m_running = false;
bool m_notExposed = false;
bool m_newlyExposed = false;
QRhi *m_r = nullptr;
bool m_hasSwapChain = false;
QRhiSwapChain *m_sc = nullptr;
QRhiRenderBuffer *m_ds = nullptr;
QRhiRenderPassDescriptor *m_rp = nullptr;
QMatrix4x4 m_proj;
QElapsedTimer m_timer;
qint64 m_elapsedMs;
int m_elapsedCount;
#ifndef QT_NO_OPENGL
QOpenGLContext *m_context = nullptr;
QOffscreenSurface *m_fallbackSurface = nullptr;
#endif
};
Window::Window()
{
// Tell the platform plugin what we want.
switch (graphicsApi) {
case OpenGL:
setSurfaceType(OpenGLSurface);
break;
case Vulkan:
setSurfaceType(VulkanSurface);
break;
case D3D11:
setSurfaceType(OpenGLSurface); // not a typo
break;
case Metal:
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
setSurfaceType(MetalSurface);
#endif
break;
default:
break;
}
}
Window::~Window()
{
releaseResources();
}
void Window::exposeEvent(QExposeEvent *)
{
// initialize and start rendering when the window becomes usable for graphics purposes
if (isExposed() && !m_running) {
m_running = true;
init();
resizeSwapChain();
render();
}
// stop pushing frames when not exposed (on some platforms this is essential, optional on others)
if (!isExposed() && m_running)
m_notExposed = true;
// continue when exposed again
if (isExposed() && m_running && m_notExposed) {
m_notExposed = false;
m_newlyExposed = true;
render();
}
}
bool Window::event(QEvent *e)
{
switch (e->type()) {
case QEvent::UpdateRequest:
render();
break;
case QEvent::PlatformSurface:
// this is the proper time to tear down the swapchain (while the native window and surface are still around)
if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
releaseSwapChain();
break;
default:
break;
}
return QWindow::event(e);
}
void Window::init()
{
#ifndef QT_NO_OPENGL
if (graphicsApi == OpenGL) {
m_context = new QOpenGLContext;
if (!m_context->create())
qFatal("Failed to get OpenGL context");
m_fallbackSurface = new QOffscreenSurface;
m_fallbackSurface->setFormat(m_context->format());
m_fallbackSurface->create();
QRhiGles2InitParams params;
params.context = m_context;
params.window = this;
params.fallbackSurface = m_fallbackSurface;
m_r = QRhi::create(QRhi::OpenGLES2, &params);
}
#endif
#if QT_CONFIG(vulkan)
if (graphicsApi == Vulkan) {
QRhiVulkanInitParams params;
params.inst = vulkanInstance();
params.window = this;
m_r = QRhi::create(QRhi::Vulkan, &params);
}
#endif
#ifdef Q_OS_WIN
if (graphicsApi == D3D11) {
QRhiD3D11InitParams params;
params.enableDebugLayer = true;
m_r = QRhi::create(QRhi::D3D11, &params);
}
#endif
#ifdef Q_OS_DARWIN
if (graphicsApi == Metal) {
QRhiMetalInitParams params;
m_r = QRhi::create(QRhi::Metal, &params);
}
#endif
if (!m_r)
qFatal("Failed to create RHI backend");
// now onto the backend-independent init
m_sc = m_r->newSwapChain();
// allow depth-stencil, although we do not actually enable depth test/write for the triangle
m_ds = m_r->newRenderBuffer(QRhiRenderBuffer::DepthStencil,