[platforms/drm] Introduce Gl post-process output rotations
Summary: In case the hardware is not able to rotate the output for the configured rotation value do this rotation in a post-process step. For that rendering the current view into a separate framebuffer bound to a texture that then gets sampled to the default framebuffer in an additional rendering pass through a simple shader rotating it. This allows us to leave the Effects system and internal model-view-projection matrix untouched. The rotation in the post-processing step is isolated. BUG: 389665 FIXED-IN: 5.18 Test Plan: With KScreen all rotations work. Reviewers: #kwin Subscribers: davidedmundson, PureTryOut, z3ntu, zzag, univerz, kwin Tags: #kwin Maniphest Tasks: T6106 Differential Revision: https://phabricator.kde.org/D25907
This commit is contained in:
parent
429fe05856
commit
8180662233
3 changed files with 183 additions and 8 deletions
|
@ -1037,12 +1037,15 @@ bool DrmOutput::doAtomicCommit(AtomicCommitMode mode)
|
||||||
bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable)
|
bool DrmOutput::atomicReqModesetPopulate(drmModeAtomicReq *req, bool enable)
|
||||||
{
|
{
|
||||||
if (enable) {
|
if (enable) {
|
||||||
|
const QSize mSize = modeSize();
|
||||||
|
const QSize sourceSize = hardwareTransforms() ? pixelSize() : mSize;
|
||||||
|
|
||||||
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0);
|
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcX), 0);
|
||||||
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0);
|
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcY), 0);
|
||||||
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), m_mode.hdisplay << 16);
|
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcW), sourceSize.width() << 16);
|
||||||
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), m_mode.vdisplay << 16);
|
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::SrcH), sourceSize.height() << 16);
|
||||||
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), m_mode.hdisplay);
|
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcW), mSize.width());
|
||||||
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), m_mode.vdisplay);
|
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcH), mSize.height());
|
||||||
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id());
|
m_primaryPlane->setValue(int(DrmPlane::PropertyIndex::CrtcId), m_crtc->id());
|
||||||
} else {
|
} else {
|
||||||
if (m_backend->deleteBufferAfterPageFlip()) {
|
if (m_backend->deleteBufferAfterPageFlip()) {
|
||||||
|
|
|
@ -54,14 +54,26 @@ EglGbmBackend::~EglGbmBackend()
|
||||||
|
|
||||||
void EglGbmBackend::cleanupSurfaces()
|
void EglGbmBackend::cleanupSurfaces()
|
||||||
{
|
{
|
||||||
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
|
for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) {
|
||||||
cleanupOutput(*it);
|
cleanupOutput(*it);
|
||||||
}
|
}
|
||||||
m_outputs.clear();
|
m_outputs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EglGbmBackend::cleanupOutput(const Output &output)
|
void EglGbmBackend::cleanupFramebuffer(Output &output)
|
||||||
{
|
{
|
||||||
|
if (!output.render.framebuffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
glDeleteTextures(1, &output.render.texture);
|
||||||
|
output.render.texture = 0;
|
||||||
|
glDeleteFramebuffers(1, &output.render.framebuffer);
|
||||||
|
output.render.framebuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EglGbmBackend::cleanupOutput(Output &output)
|
||||||
|
{
|
||||||
|
cleanupFramebuffer(output);
|
||||||
output.output->releaseGbm();
|
output.output->releaseGbm();
|
||||||
|
|
||||||
if (output.eglSurface != EGL_NO_SURFACE) {
|
if (output.eglSurface != EGL_NO_SURFACE) {
|
||||||
|
@ -182,7 +194,8 @@ EGLSurface EglGbmBackend::createEglSurface(std::shared_ptr<GbmSurface> gbmSurfac
|
||||||
bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput)
|
bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput)
|
||||||
{
|
{
|
||||||
output.output = drmOutput;
|
output.output = drmOutput;
|
||||||
const QSize size = drmOutput->pixelSize();
|
const QSize size = drmOutput->hardwareTransforms() ? drmOutput->pixelSize() :
|
||||||
|
drmOutput->modeSize();
|
||||||
|
|
||||||
auto gbmSurface = createGbmSurface(size);
|
auto gbmSurface = createGbmSurface(size);
|
||||||
if (!gbmSurface) {
|
if (!gbmSurface) {
|
||||||
|
@ -202,6 +215,8 @@ bool EglGbmBackend::resetOutput(Output &output, DrmOutput *drmOutput)
|
||||||
}
|
}
|
||||||
output.eglSurface = eglSurface;
|
output.eglSurface = eglSurface;
|
||||||
output.gbmSurface = gbmSurface;
|
output.gbmSurface = gbmSurface;
|
||||||
|
|
||||||
|
resetFramebuffer(output);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +255,146 @@ void EglGbmBackend::removeOutput(DrmOutput *drmOutput)
|
||||||
m_outputs.erase(it);
|
m_outputs.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *vertexShader = R"SHADER(
|
||||||
|
#version 130
|
||||||
|
uniform mat4 modelViewProjectionMatrix;
|
||||||
|
uniform mat4 rotationMatrix;
|
||||||
|
|
||||||
|
in vec2 vertex;
|
||||||
|
in vec2 texCoord;
|
||||||
|
|
||||||
|
out vec2 TexCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = rotationMatrix * vec4(vertex.x, vertex.y, 0.0, 1.0);
|
||||||
|
TexCoord = texCoord;
|
||||||
|
}
|
||||||
|
)SHADER";
|
||||||
|
|
||||||
|
const char *fragmentShader = R"SHADER(
|
||||||
|
#version 130
|
||||||
|
uniform sampler2D sampler;
|
||||||
|
|
||||||
|
in vec2 TexCoord;
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = texture(sampler, TexCoord);
|
||||||
|
}
|
||||||
|
)SHADER";
|
||||||
|
|
||||||
|
const float vertices[] = {
|
||||||
|
-1.0f, 1.0f,
|
||||||
|
-1.0f, -1.0f,
|
||||||
|
1.0f, -1.0f,
|
||||||
|
|
||||||
|
-1.0f, 1.0f,
|
||||||
|
1.0f, -1.0f,
|
||||||
|
1.0f, 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
const float texCoords[] = {
|
||||||
|
0.0f, 1.0f,
|
||||||
|
0.0f, 0.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
|
||||||
|
0.0f, 1.0f,
|
||||||
|
1.0f, 0.0f,
|
||||||
|
1.0f, 1.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
bool EglGbmBackend::resetFramebuffer(Output &output)
|
||||||
|
{
|
||||||
|
cleanupFramebuffer(output);
|
||||||
|
|
||||||
|
if (output.output->hardwareTransforms()) {
|
||||||
|
// No need for an extra render target.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeContextCurrent(output);
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &output.render.framebuffer);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, output.render.framebuffer);
|
||||||
|
GLRenderTarget::setKWinFramebuffer(output.render.framebuffer);
|
||||||
|
|
||||||
|
glGenTextures(1, &output.render.texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, output.render.texture);
|
||||||
|
|
||||||
|
const QSize texSize = output.output->pixelSize();
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texSize.width(), texSize.height(),
|
||||||
|
0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||||
|
output.render.texture, 0);
|
||||||
|
|
||||||
|
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
qCWarning(KWIN_DRM) << "Error: framebuffer not complete";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
GLRenderTarget::setKWinFramebuffer(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EglGbmBackend::initRenderTarget(Output &output)
|
||||||
|
{
|
||||||
|
if (output.render.shader) {
|
||||||
|
// Already initialized.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::shared_ptr<GLShader> shader(ShaderManager::instance()->loadShaderFromCode(vertexShader,
|
||||||
|
fragmentShader));
|
||||||
|
if (!shader) {
|
||||||
|
qCWarning(KWIN_DRM) << "Error creating render shader.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.render.shader = shader;
|
||||||
|
|
||||||
|
std::shared_ptr<GLVertexBuffer> vbo(new GLVertexBuffer(KWin::GLVertexBuffer::Static));
|
||||||
|
vbo->setData(6, 2, vertices, texCoords);
|
||||||
|
output.render.vbo = vbo;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EglGbmBackend::renderFramebufferToSurface(Output &output)
|
||||||
|
{
|
||||||
|
if (!output.render.framebuffer) {
|
||||||
|
// No additional render target.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!initRenderTarget(output)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
GLRenderTarget::setKWinFramebuffer(0);
|
||||||
|
|
||||||
|
const auto size = output.output->modeSize();
|
||||||
|
glViewport(0, 0, size.width(), size.height());
|
||||||
|
|
||||||
|
ShaderBinder binder(output.render.shader.get());
|
||||||
|
QMatrix4x4 rotationMatrix;
|
||||||
|
rotationMatrix.rotate(output.output->rotation(), 0, 0, 1);
|
||||||
|
output.render.shader->setUniform("rotationMatrix", rotationMatrix);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, output.render.texture);
|
||||||
|
output.render.vbo->render(GL_TRIANGLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EglGbmBackend::prepareRenderFramebuffer(const Output &output) const
|
||||||
|
{
|
||||||
|
// When render.framebuffer is 0 we may just reset to the screen framebuffer.
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, output.render.framebuffer);
|
||||||
|
GLRenderTarget::setKWinFramebuffer(output.render.framebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
bool EglGbmBackend::makeContextCurrent(const Output &output) const
|
bool EglGbmBackend::makeContextCurrent(const Output &output) const
|
||||||
{
|
{
|
||||||
const EGLSurface surface = output.eglSurface;
|
const EGLSurface surface = output.eglSurface;
|
||||||
|
@ -371,6 +526,7 @@ QRegion EglGbmBackend::prepareRenderingForScreen(int screenId)
|
||||||
const Output &output = m_outputs.at(screenId);
|
const Output &output = m_outputs.at(screenId);
|
||||||
|
|
||||||
makeContextCurrent(output);
|
makeContextCurrent(output);
|
||||||
|
prepareRenderFramebuffer(output);
|
||||||
setViewport(output);
|
setViewport(output);
|
||||||
|
|
||||||
if (supportsBufferAge()) {
|
if (supportsBufferAge()) {
|
||||||
|
@ -400,6 +556,7 @@ void EglGbmBackend::endRenderingFrameForScreen(int screenId,
|
||||||
const QRegion &damagedRegion)
|
const QRegion &damagedRegion)
|
||||||
{
|
{
|
||||||
Output &output = m_outputs[screenId];
|
Output &output = m_outputs[screenId];
|
||||||
|
renderFramebufferToSurface(output);
|
||||||
|
|
||||||
if (damagedRegion.intersected(output.output->geometry()).isEmpty() && screenId == 0) {
|
if (damagedRegion.intersected(output.output->geometry()).isEmpty() && screenId == 0) {
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,13 @@ private:
|
||||||
* @brief The damage history for the past 10 frames.
|
* @brief The damage history for the past 10 frames.
|
||||||
*/
|
*/
|
||||||
QList<QRegion> damageHistory;
|
QList<QRegion> damageHistory;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
GLuint framebuffer = 0;
|
||||||
|
GLuint texture = 0;
|
||||||
|
std::shared_ptr<GLVertexBuffer> vbo;
|
||||||
|
std::shared_ptr<GLShader> shader;
|
||||||
|
} render;
|
||||||
};
|
};
|
||||||
|
|
||||||
void createOutput(DrmOutput *drmOutput);
|
void createOutput(DrmOutput *drmOutput);
|
||||||
|
@ -80,10 +87,18 @@ private:
|
||||||
|
|
||||||
bool makeContextCurrent(const Output &output) const;
|
bool makeContextCurrent(const Output &output) const;
|
||||||
void setViewport(const Output &output) const;
|
void setViewport(const Output &output) const;
|
||||||
|
|
||||||
|
bool resetFramebuffer(Output &output);
|
||||||
|
bool initRenderTarget(Output &output);
|
||||||
|
|
||||||
|
void prepareRenderFramebuffer(const Output &output) const;
|
||||||
|
void renderFramebufferToSurface(Output &output);
|
||||||
|
|
||||||
void presentOnOutput(Output &output);
|
void presentOnOutput(Output &output);
|
||||||
|
|
||||||
void removeOutput(DrmOutput *drmOutput);
|
void removeOutput(DrmOutput *drmOutput);
|
||||||
void cleanupOutput(const Output &output);
|
void cleanupOutput(Output &output);
|
||||||
|
void cleanupFramebuffer(Output &output);
|
||||||
|
|
||||||
DrmBackend *m_backend;
|
DrmBackend *m_backend;
|
||||||
QVector<Output> m_outputs;
|
QVector<Output> m_outputs;
|
||||||
|
|
Loading…
Reference in a new issue