winrt: Refactor backing store

Thanks to new features in ANGLE, the backing store implementation
complexity can be greatly reduced. By using ES3 framebuffer blit,
no shader code is required, and the shader loading code and blit shader
can be removed.

Change-Id: Iab3d915e279ad6468a75ef6257794f12acd8cb65
Reviewed-by: Oliver Wolff <oliver.wolff@digia.com>
This commit is contained in:
Andrew Knight 2014-08-05 23:21:54 +03:00
parent 1a9701fe81
commit c7abf81786
4 changed files with 81 additions and 325 deletions

View File

@ -1,14 +0,0 @@
uniform SamplerState Sampler : register(s0);
uniform Texture2D Texture : register(t0);
void blitvs(in float4 pos0 : TEXCOORD0, in float2 tex0 : TEXCOORD1,
out float4 gl_Position : SV_POSITION, out float2 coord : TEXCOORD0)
{
coord = tex0;
gl_Position = pos0 * float4(1.0, -1.0, 1.0, 1.0);
}
float4 blitps(in float4 gl_Position : SV_POSITION, in float2 coord : TEXCOORD0) : SV_TARGET0
{
return Texture.Sample(Sampler, coord);
}

View File

@ -45,343 +45,136 @@
#include "qwinrtwindow.h"
#include "qwinrteglcontext.h"
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLFramebufferObject>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
// Generated shader headers
#include "blitps.h"
#include "blitvs.h"
namespace { // Utility namespace for writing out an ANGLE-compatible binary blob
// Must match packaged ANGLE
enum : quint32 {
AngleMajorVersion = 1,
AngleMinorVersion = 3
};
struct ShaderString
{
ShaderString(const char *data = 0) : data(data) { }
const char *data;
};
// ANGLE stream compatibility - when size_t is 32-bit, QDataStream::writeBytes() also works
QDataStream &operator<<(QDataStream &stream, const ShaderString &shaderString)
{
if (!shaderString.data)
return stream << size_t(0);
size_t len = strlen(shaderString.data);
stream << len;
stream.writeRawData(shaderString.data, int(len));
return stream;
}
struct Attribute
{
Attribute(GLenum type = 0, const char *name = 0, quint32 index = 0)
: type(type), name(name), index(index) { }
GLenum type;
ShaderString name;
quint32 index;
};
struct Sampler
{
enum TextureType { Texture2D, TextureCube };
Sampler(bool active = false, GLint unit = 0, TextureType type = Texture2D)
: active(active), unit(unit), type(type) { }
bool active;
GLint unit;
TextureType type;
};
struct Uniform
{
Uniform() { }
Uniform(GLenum type, quint32 precision, const char *name, quint32 arraySize,
quint32 psRegisterIndex, quint32 vsRegisterIndex, quint32 registerCount)
: type(type), precision(precision), name(name), arraySize(arraySize)
, psRegisterIndex(psRegisterIndex), vsRegisterIndex(vsRegisterIndex), registerCount(registerCount) { }
GLenum type;
quint32 precision;
ShaderString name;
quint32 arraySize;
quint32 psRegisterIndex;
quint32 vsRegisterIndex;
quint32 registerCount;
};
struct UniformIndex
{
UniformIndex(const char *name = 0, quint32 element = 0, quint32 index = 0)
: name(name), element(element), index(index) { }
ShaderString name;
quint32 element;
quint32 index;
};
static const QByteArray createAngleBinary(
const QVector<Attribute> &attributes,
const QVector<Sampler> &textureSamplers,
const QVector<Sampler> &vertexSamplers,
const QVector<Uniform> &uniforms,
const QVector<UniformIndex> &uniformIndex,
const QByteArray &pixelShader,
const QByteArray &vertexShader,
const QByteArray &geometryShader = QByteArray(),
bool usesPointSize = false)
{
QByteArray binary;
QDataStream stream(&binary, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << quint32(GL_PROGRAM_BINARY_ANGLE)
<< qint32(AngleMajorVersion)
<< qint32(AngleMinorVersion);
// Vertex attributes
for (int i = 0; i < 16; ++i) {
if (i < attributes.size())
stream << quint32(attributes[i].type) << attributes[i].name << attributes[i].index;
else
stream << quint32(GL_NONE) << ShaderString() << qint32(-1);
}
// Texture units
for (int i = 0; i < 16; ++i) {
if (i < textureSamplers.size())
stream << textureSamplers[i].active << textureSamplers[i].unit << qint32(textureSamplers[i].type);
else
stream << false << qint32(0) << qint32(Sampler::Texture2D);
}
// Vertex texture units
for (int i = 0; i < 16; ++i) {
if (i < vertexSamplers.size())
stream << vertexSamplers[i].active << vertexSamplers[i].unit << qint32(vertexSamplers[i].type);
else
stream << false << qint32(0) << qint32(Sampler::Texture2D);
}
stream << vertexSamplers.size()
<< textureSamplers.size()
<< usesPointSize;
stream << size_t(uniforms.size());
foreach (const Uniform &uniform, uniforms) {
stream << uniform.type << uniform.precision << uniform.name << uniform.arraySize
<< uniform.psRegisterIndex << uniform.vsRegisterIndex << uniform.registerCount;
}
stream << size_t(uniformIndex.size());
foreach (const UniformIndex &index, uniformIndex)
stream << index.name << index.element << index.index;
stream << quint32(pixelShader.size())
<< quint32(vertexShader.size())
<< quint32(geometryShader.size());
stream.writeRawData(pixelShader.constData(), pixelShader.size());
stream.writeRawData(vertexShader.constData(), vertexShader.size());
if (!geometryShader.isEmpty())
stream.writeRawData(geometryShader.constData(), geometryShader.size());
return binary;
}
} // namespace
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
QT_BEGIN_NAMESPACE
static const GLfloat normCoords[] = { -1, 1, 1, 1, 1, -1, -1, -1 };
static const GLfloat quadCoords[] = { 0, 0, 1, 0, 1, 1, 0, 1 };
class QWinRTBackingStorePrivate
{
public:
bool initialized;
QSize size;
QScopedPointer<QOpenGLContext> context;
QScopedPointer<QOpenGLFramebufferObject> fbo;
QWinRTScreen *screen;
QImage paintDevice;
};
QWinRTBackingStore::QWinRTBackingStore(QWindow *window)
: QPlatformBackingStore(window)
, m_context(new QOpenGLContext)
, m_shaderProgram(0)
, m_fbo(0)
, m_texture(0)
, m_screen(static_cast<QWinRTScreen*>(window->screen()->handle()))
, m_initialized(false)
: QPlatformBackingStore(window), d_ptr(new QWinRTBackingStorePrivate)
{
Q_D(QWinRTBackingStore);
d->initialized = false;
d->screen = static_cast<QWinRTScreen*>(window->screen()->handle());
window->setSurfaceType(QSurface::OpenGLSurface); // Required for flipping, but could be done in the swap
}
bool QWinRTBackingStore::initialize()
{
if (m_initialized)
Q_D(QWinRTBackingStore);
if (d->initialized)
return true;
m_context->setFormat(window()->requestedFormat());
m_context->setScreen(window()->screen());
if (!m_context->create())
d->context.reset(new QOpenGLContext);
QSurfaceFormat format = window()->requestedFormat();
format.setVersion(3, 0); // Required for ES3 framebuffer blit
d->context->setFormat(format);
d->context->setScreen(window()->screen());
if (!d->context->create())
return false;
if (!m_context->makeCurrent(window()))
if (!d->context->makeCurrent(window()))
return false;
glGenFramebuffers(1, &m_fbo);
glGenRenderbuffers(1, &m_rbo);
glGenTextures(1, &m_texture);
m_shaderProgram = glCreateProgram();
#if 0 // Standard GLES passthrough shader program
static const char *vertexShaderSource =
"attribute vec4 pos0;\n"
"attribute vec2 tex0;\n"
"varying vec2 coord;\n"
"void main() {\n"
" coord = tex0;\n"
" gl_Position = pos0;\n"
"}\n";
static const char *fragmentShaderSource =
"uniform sampler2D texture;\n"
"varying highp vec2 coord;\n"
"void main() {\n"
" gl_FragColor = texture2D(texture, coord);\n"
"}\n";
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
glAttachShader(m_shaderProgram, vertexShader);
glAttachShader(m_shaderProgram, fragmentShader);
glLinkProgram(m_shaderProgram);
#else // Precompiled passthrough shader
QVector<Attribute> attributes = QVector<Attribute>() << Attribute(GL_FLOAT_VEC4, "pos0", 0)
<< Attribute(GL_FLOAT_VEC2, "tex0", 1);
QVector<Sampler> textureSamplers = QVector<Sampler>() << Sampler(true, 0, Sampler::Texture2D);
QVector<Sampler> vertexSamplers;
QVector<Uniform> uniforms = QVector<Uniform>() << Uniform(GL_SAMPLER_2D, 0, "texture", 0, 0, -1, 1);
QVector<UniformIndex> uniformsIndex = QVector<UniformIndex>() << UniformIndex("texture", 0, 0);
QByteArray pixelShader(reinterpret_cast<const char *>(q_blitps), sizeof(q_blitps));
QByteArray vertexShader(reinterpret_cast<const char *>(q_blitvs), sizeof(q_blitvs));
QByteArray binary = createAngleBinary(attributes, textureSamplers, vertexSamplers,
uniforms, uniformsIndex, pixelShader, vertexShader);
glProgramBinaryOES(m_shaderProgram, GL_PROGRAM_BINARY_ANGLE, binary.constData(), binary.size());
#endif
m_context->doneCurrent();
m_initialized = true;
d->context->doneCurrent();
d->initialized = true;
return true;
}
QWinRTBackingStore::~QWinRTBackingStore()
{
if (!m_initialized)
return;
glDeleteBuffers(1, &m_fbo);
glDeleteRenderbuffers(1, &m_rbo);
glDeleteTextures(1, &m_texture);
glDeleteProgram(m_shaderProgram);
}
QPaintDevice *QWinRTBackingStore::paintDevice()
{
return &m_paintDevice;
Q_D(QWinRTBackingStore);
return &d->paintDevice;
}
void QWinRTBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
{
Q_D(QWinRTBackingStore);
Q_UNUSED(offset)
if (m_size.isEmpty())
if (d->size.isEmpty())
return;
m_context->makeCurrent(window);
const bool ok = d->context->makeCurrent(window);
if (!ok)
qWarning("unable to flush");
// Blitting the entire image width trades zero image copy/relayout for a larger texture upload.
// Since we're blitting the whole width anyway, the boundingRect() is used in the assumption that
// we don't repeat upload. This is of course dependent on the distance between update regions.
// Ideally, we would use the GL_EXT_unpack_subimage extension, which should be possible to implement
// since D3D11_MAPPED_SUBRESOURCE supports RowPitch (see below).
// Note that single-line blits in a loop are *very* slow, so reducing calls to glTexSubImage2D
// is probably a good idea anyway.
glBindTexture(GL_TEXTURE_2D, m_texture);
QRect bounds = region.boundingRect();
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, bounds.y(), m_size.width(), bounds.height(),
GL_BGRA_EXT, GL_UNSIGNED_BYTE, m_paintDevice.constScanLine(bounds.y()));
// TODO: Implement GL_EXT_unpack_subimage in ANGLE for more minimal uploads
//glPixelStorei(GL_UNPACK_ROW_LENGTH, image->bytesPerLine());
//glTexSubImage2D(GL_TEXTURE_2D, 0, bounds.x(), bounds.y(), bounds.width(), bounds.height(),
// GL_BGRA_EXT, GL_UNSIGNED_BYTE, image->scanLine(bounds.y()) + bounds.x() * 4);
// Bind render buffer
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo);
// Bind position
glUseProgram(m_shaderProgram);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, normCoords);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, quadCoords);
// Render
const QSize blitSize = m_size * window->devicePixelRatio();
glViewport(0, 0, blitSize.width(), blitSize.height());
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
// Unbind
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glUseProgram(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
const QRect bounds = region.boundingRect();
glBindTexture(GL_TEXTURE_2D, d->fbo->texture());
// TODO: when ANGLE GLES3 support is finished, use the glPixelStorei functions to minimize upload
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, bounds.y(), d->size.width(), bounds.height(),
GL_RGBA, GL_UNSIGNED_BYTE, d->paintDevice.constScanLine(bounds.y()));
glBindTexture(GL_TEXTURE_2D, 0);
// fast blit - TODO: perform the blit inside swap buffers instead
glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, m_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, 0);
glBlitFramebufferANGLE(0, 0, blitSize.width(), blitSize.height(), // TODO: blit only the changed rectangle
0, 0, blitSize.width(), blitSize.height(),
glBindFramebuffer(GL_READ_FRAMEBUFFER, d->fbo->handle());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
const int y1 = bounds.height() + bounds.y();
const int y2 = d->size.height() - y1;
const int x1 = bounds.x();
const int x2 = x1 + bounds.width();
glBlitFramebuffer(x1, y2, x2, y1,
x1, y1, x2, y2,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
m_context->swapBuffers(window);
m_context->doneCurrent();
d->context->swapBuffers(window);
d->context->doneCurrent();
}
void QWinRTBackingStore::resize(const QSize &size, const QRegion &staticContents)
{
Q_D(QWinRTBackingStore);
Q_UNUSED(staticContents)
if (!initialize())
return;
if (m_size == size)
if (d->size == size)
return;
m_size = size;
if (m_size.isEmpty())
d->size = size;
if (d->size.isEmpty())
return;
m_paintDevice = QImage(m_size, QImage::Format_ARGB32_Premultiplied);
d->paintDevice = QImage(d->size, QImage::Format_RGBA8888_Premultiplied);
m_context->makeCurrent(window());
// Input texture
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, m_size.width(), m_size.height(),
0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
// Render buffer
glBindRenderbuffer(GL_RENDERBUFFER, m_rbo);
const QSize blitSize = m_size * window()->devicePixelRatio();
glRenderbufferStorage(GL_RENDERBUFFER, GL_BGRA8_EXT, blitSize.width(), blitSize.height());
glBindRenderbuffer(GL_RENDERBUFFER, 0);
m_context->doneCurrent();
const bool ok = d->context->makeCurrent(window());
if (!ok)
qWarning("unable to resize");
d->fbo.reset(new QOpenGLFramebufferObject(d->size));
d->context->doneCurrent();
}
QImage QWinRTBackingStore::toImage() const
{
Q_D(const QWinRTBackingStore);
return d->paintDevice;
}
void QWinRTBackingStore::beginPaint(const QRegion &region)
{
Q_UNUSED(region)
resize(window()->size(), QRegion());
resize(window()->size(), region);
}
void QWinRTBackingStore::endPaint()

View File

@ -48,8 +48,8 @@
QT_BEGIN_NAMESPACE
class QWinRTScreen;
class QOpenGLContext;
class QWinRTBackingStorePrivate;
class QWinRTBackingStore : public QPlatformBackingStore
{
public:
@ -60,19 +60,13 @@ public:
void endPaint();
void flush(QWindow *window, const QRegion &region, const QPoint &offset);
void resize(const QSize &size, const QRegion &staticContents);
QImage toImage() const Q_DECL_OVERRIDE { return m_paintDevice; }
QImage toImage() const Q_DECL_OVERRIDE;
private:
bool initialize();
bool m_initialized;
QSize m_size;
QScopedPointer<QOpenGLContext> m_context;
quint32 m_shaderProgram;
quint32 m_fbo;
quint32 m_rbo;
quint32 m_texture;
QWinRTScreen *m_screen;
QImage m_paintDevice;
QScopedPointer<QWinRTBackingStorePrivate> d_ptr;
Q_DECLARE_PRIVATE(QWinRTBackingStore)
};
QT_END_NAMESPACE

View File

@ -56,26 +56,9 @@ HEADERS = \
qwinrttheme.h \
qwinrtwindow.h
BLIT_INPUT = $$PWD/blit.hlsl
fxc_blitps.commands = fxc.exe /nologo /T ps_4_0_level_9_1 /E blitps /Vn q_blitps /Fh ${QMAKE_FILE_OUT} ${QMAKE_FILE_NAME}
fxc_blitps.output = $$OUT_PWD/blitps.h
fxc_blitps.input = BLIT_INPUT
fxc_blitps.dependency_type = TYPE_C
fxc_blitps.variable_out = HEADERS
fxc_blitps.CONFIG += target_predeps
fxc_blitvs.commands = fxc.exe /nologo /T vs_4_0_level_9_1 /E blitvs /Vn q_blitvs /Fh ${QMAKE_FILE_OUT} ${QMAKE_FILE_NAME}
fxc_blitvs.output = $$OUT_PWD/blitvs.h
fxc_blitvs.input = BLIT_INPUT
fxc_blitvs.dependency_type = TYPE_C
fxc_blitvs.variable_out = HEADERS
fxc_blitvs.CONFIG += target_predeps
QMAKE_EXTRA_COMPILERS += fxc_blitps fxc_blitvs
winphone:equals(WINSDK_VER, 8.0): {
SOURCES -= qwinrtplatformmessagedialoghelper.cpp
HEADERS -= qwinrtplatformmessagedialoghelper.h
}
OTHER_FILES += winrt.json \
blit.hlsl
OTHER_FILES += winrt.json