diff --git a/plugins/scenes/vulkan/quadsplitter.cpp b/plugins/scenes/vulkan/quadsplitter.cpp index 2c1d13a4c..59e40ac65 100644 --- a/plugins/scenes/vulkan/quadsplitter.cpp +++ b/plugins/scenes/vulkan/quadsplitter.cpp @@ -1,95 +1,95 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright © 2017-2018 Fredrik Höglund This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "quadsplitter.h" namespace KWin { QuadSplitter::QuadSplitter(const WindowQuadList &quads) { size_t shadowCount = 0; size_t decorationCount = 0; size_t contentCount = 0; // Count the quads for (const WindowQuad &quad : qAsConst(quads)) { switch (quad.type()) { case WindowQuadContents: contentCount++; continue; case WindowQuadDecoration: decorationCount++; continue; case WindowQuadShadow: shadowCount++; continue; default: continue; } } m_maxQuadCount = std::max(shadowCount, std::max(decorationCount, contentCount)); // Allocate space size_t count = shadowCount + decorationCount + contentCount; - m_data = static_cast(std::malloc(count * sizeof(WindowQuad))); + m_data = static_cast(std::malloc(count * sizeof(Quad))); uintptr_t shadowOffset = 0; uintptr_t decorationOffset = shadowOffset + shadowCount; uintptr_t contentOffset = decorationOffset + decorationCount; m_shadow = { m_data + shadowOffset, shadowCount }; m_decoration = { m_data + decorationOffset, decorationCount }; m_content = { m_data + contentOffset, contentCount }; // Split the quads for (const WindowQuad &quad : qAsConst(quads)) { switch (quad.type()) { case WindowQuadContents: m_data[contentOffset++] = quad; continue; case WindowQuadDecoration: m_data[decorationOffset++] = quad; continue; case WindowQuadShadow: m_data[shadowOffset++] = quad; continue; default: continue; } } } QuadSplitter::~QuadSplitter() { std::free(m_data); } } // namespace KWin diff --git a/plugins/scenes/vulkan/quadsplitter.h b/plugins/scenes/vulkan/quadsplitter.h index cd1d93792..f95b28eaa 100644 --- a/plugins/scenes/vulkan/quadsplitter.h +++ b/plugins/scenes/vulkan/quadsplitter.h @@ -1,106 +1,138 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright © 2017-2018 Fredrik Höglund This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef QUADSPLITTER_H #define QUADSPLITTER_H #include "kwineffects.h" namespace KWin { /** * Splits a WindowQuadList into separate fixed size arrays for the shadow, decoration and content quads, respectively. * * Storage for the arrays is allocated as a single contiguous memory allocation, * owned by the QuadSplitter. */ class QuadSplitter { public: /** - * Fixed size WindowQuad array + * Vertex is a version of a WindowVertex that can be copied directly into a vertex buffer. + * + * It differs from WindowVertex in that the coordinates are stored as 32-bit single-precision + * floating point values, and it does not have the original position. + */ + struct Vertex : public GLVertex2D + { + Vertex(const WindowVertex &other) { + position = { float(other.x()), float(other.y())}; + texcoord = { float(other.u()), float(other.v())}; + } + + float x() const { return position.x(); } + float y() const { return position.y(); } + float u() const { return texcoord.x(); } + float v() const { return texcoord.y(); } + }; + + + /** + * Quad is a version of a WindowQuad that can be copied directly into a vertex buffer. + * + * It differs from WindowQuad in that it contains 32-bit single-precision floating point + * vertices, and is missing the type and quad ID. + */ + class Quad + { + public: + Quad(const WindowQuad &other) + : m_vertices{other[0], other[1], other[2], other[3]} + { + } + + const Vertex &operator [](size_t index) const { return m_vertices[index]; } + + private: + Vertex m_vertices[4]; + }; + + + /** + * Fixed size Quad array */ class Array { public: Array() = default; - Array(WindowQuad *data, size_t count) : m_data(data), m_count(count) {} + Array(Quad *data, size_t count) : m_data(data), m_count(count) {} size_t count() const { return m_count; } bool isEmpty() const { return m_count == 0; } - // STL compatibility + // For STL compatibility size_t size() const { return m_count; } bool empty() const { return m_count == 0; } - const WindowQuad &first() const { return m_data[0]; } - const WindowQuad &last() const { return m_data[m_count - 1]; } - const WindowQuad *cbegin() const { return m_data; } - const WindowQuad *cend() const { return m_data + m_count; } - const WindowQuad *begin() const { return m_data; } - const WindowQuad *end() const { return m_data + m_count; } + const Quad &first() const { return m_data[0]; } + const Quad &last() const { return m_data[m_count - 1]; } + const Quad *cbegin() const { return m_data; } + const Quad *cend() const { return m_data + m_count; } + const Quad *begin() const { return m_data; } + const Quad *end() const { return m_data + m_count; } - WindowQuad &at(size_t index) { return m_data[index]; } - const WindowQuad &at(size_t index) const { return m_data[index]; } + Quad &at(size_t index) { return m_data[index]; } + const Quad &at(size_t index) const { return m_data[index]; } - WindowQuad &operator [](size_t index) { return m_data[index]; } - const WindowQuad &operator [](size_t index) const { return m_data[index]; } + Quad &operator [](size_t index) { return m_data[index]; } + const Quad &operator [](size_t index) const { return m_data[index]; } void writeVertices(GLVertex2D *dst) const { - for (const WindowQuad &quad : *this) { - for (int i = 0; i < 4; i++) { - const WindowVertex &vertex = quad[i]; - - *dst++ = GLVertex2D { - .position = { float(vertex.x()), float(vertex.y()) }, - .texcoord = { float(vertex.u()), float(vertex.v()) } - }; - } - } + memcpy(dst, m_data, m_count * sizeof(Quad)); } private: - WindowQuad *m_data; + Quad *m_data; size_t m_count; }; QuadSplitter(const WindowQuadList &quads); ~QuadSplitter(); size_t maxQuadCount() const { return m_maxQuadCount; } const Array &shadow() const { return m_shadow; } const Array &decoration() const { return m_decoration; } const Array &content() const { return m_content; } private: - WindowQuad *m_data; + Quad *m_data; size_t m_maxQuadCount; Array m_shadow; Array m_decoration; Array m_content; }; } // namespace KWin #endif diff --git a/plugins/scenes/vulkan/window.cpp b/plugins/scenes/vulkan/window.cpp index 377cf1abf..91dbf64bb 100644 --- a/plugins/scenes/vulkan/window.cpp +++ b/plugins/scenes/vulkan/window.cpp @@ -1,443 +1,443 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright © 2017-2018 Fredrik Höglund This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "window.h" #include "windowpixmap.h" #include "descriptorset.h" #include "abstract_client.h" #include "client.h" #include "deleted.h" #include "decorationrenderer.h" #include "decorations/decoratedclient.h" #include "shadow.h" #include "quadsplitter.h" #include namespace KWin { VulkanWindow::VulkanWindow(Toplevel *toplevel, VulkanScene *scene) : Scene::Window(toplevel), m_scene(scene) { } VulkanWindow::~VulkanWindow() { } QMatrix4x4 VulkanWindow::windowMatrix(int mask, const WindowPaintData &data) const { QMatrix4x4 matrix; matrix.translate(x(), y()); if (!(mask & Scene::PAINT_WINDOW_TRANSFORMED)) return matrix; matrix.translate(data.translation()); data.scale().applyTo(&matrix); if (data.rotationAngle() == 0.0) return matrix; // Apply the rotation // We cannot use data.rotation.applyTo(&matrix) as QGraphicsRotation uses projectedRotate to map back to 2D matrix.translate(data.rotationOrigin()); const QVector3D axis = data.rotationAxis(); matrix.rotate(data.rotationAngle(), axis.x(), axis.y(), axis.z()); matrix.translate(-data.rotationOrigin()); return matrix; } QMatrix4x4 VulkanWindow::modelViewProjectionMatrix(int mask, const WindowPaintData &data) const { const QMatrix4x4 pMatrix = data.projectionMatrix(); const QMatrix4x4 mvMatrix = data.modelViewMatrix(); // An effect may want to override the default projection matrix in some cases, // such as when it is rendering a window on a render target that doesn't have // the same dimensions as the default framebuffer. // // Note that the screen transformation is not applied here. if (!pMatrix.isIdentity()) return pMatrix * mvMatrix; // If an effect has specified a model-view matrix, we multiply that matrix // with the default projection matrix. If the effect hasn't specified a // model-view matrix, mvMatrix will be the identity matrix. if (mask & Scene::PAINT_SCREEN_TRANSFORMED) return scene()->screenProjectionMatrix() * mvMatrix; return scene()->projectionMatrix() * mvMatrix; } // This method cannot be const because windowPixmap() is not const VulkanWindow::Texture VulkanWindow::getContentTexture() { // Note that windowPixmap() returns a pointer to the window pixmap, // casting it to a pointer to T if (auto pixmap = windowPixmap()) { pixmap->aboutToRender(); return Texture(pixmap->image(), pixmap->imageView(), pixmap->memory(), pixmap->imageLayout()); } return Texture(); } // This method cannot be const because previousWindowPixmap() is not const VulkanWindow::Texture VulkanWindow::getPreviousContentTexture() { // Note that previousWindowPixmap() returns a pointer to the previous window pixmap, // casting it to a pointer to T if (auto pixmap = previousWindowPixmap()) return Texture(pixmap->image(), pixmap->imageView(), pixmap->memory(), pixmap->imageLayout()); return Texture(); } VulkanWindow::Texture VulkanWindow::getDecorationTexture() const { if (AbstractClient *client = dynamic_cast(toplevel)) { if (client->noBorder()) { return Texture(); } if (!client->isDecorated()) { return Texture(); } if (VulkanDecorationRenderer *renderer = static_cast(client->decoratedClient()->renderer())) { renderer->render(); return Texture(renderer->image(), renderer->imageView(), renderer->memory(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } } else if (toplevel->isDeleted()) { Deleted *deleted = static_cast(toplevel); if (!deleted->wasClient() || deleted->noBorder()) { return Texture(); } if (const VulkanDecorationRenderer *renderer = static_cast(deleted->decorationRenderer())) { return Texture(renderer->image(), renderer->imageView(), renderer->memory(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } } return Texture(); } VulkanWindow::Texture VulkanWindow::getShadowTexture() const { if (m_shadow) { auto shadow = static_cast(m_shadow); return Texture(shadow->image(), shadow->imageView(), shadow->memory(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } return Texture(); } void VulkanWindow::performPaint(int mask, QRegion clipRegion, WindowPaintData data) { if (data.quads.isEmpty()) return; // The x and y members of the scissor offset must be >= 0. // Note that it is legal for x + width and y + height to exceed the dimensions of the framebuffer. const QPoint clipOffset = clipRegion.boundingRect().topLeft(); if (clipOffset.x() < 0 || clipOffset.y() < 0) clipRegion &= QRect(0, 0, INT_MAX, INT_MAX); if (clipRegion.isEmpty()) return; auto pipelineManager = scene()->pipelineManager(); auto uploadManager = scene()->uploadManager(); auto cmd = scene()->mainCommandBuffer(); const Texture contentTexture = getContentTexture(); const Texture previousContentTexture = getPreviousContentTexture(); const Texture decorationTexture = getDecorationTexture(); const Texture shadowTexture = getShadowTexture(); // Select the pipelines for the contents and decorations respectively // ------------------------------------------------------------------ VulkanPipelineManager::Material contentMaterial = VulkanPipelineManager::Texture; VulkanPipelineManager::Material decorationMaterial = VulkanPipelineManager::Texture; VulkanPipelineManager::Traits contentTraits = VulkanPipelineManager::NoTraits; VulkanPipelineManager::Traits decorationTraits = VulkanPipelineManager::PreMultipliedAlphaBlend; if (!isOpaque() || data.opacity() != 1.0) contentTraits |= VulkanPipelineManager::PreMultipliedAlphaBlend; if (data.opacity() != 1.0 || data.brightness() != 1.0) { contentTraits |= VulkanPipelineManager::Modulate; decorationTraits |= VulkanPipelineManager::Modulate; } if (data.saturation() != 1.0) { contentTraits |= VulkanPipelineManager::Desaturate; decorationTraits |= VulkanPipelineManager::Desaturate; } if (data.crossFadeProgress() != 1.0 && previousContentTexture.imageView()) { contentMaterial = VulkanPipelineManager::TwoTextures; contentTraits |= VulkanPipelineManager::CrossFade; } const CullModeFlags cullMode = scene()->cullMode(); if (cullMode & CullModeFlag::Front) { contentTraits |= VulkanPipelineManager::CullFront; decorationTraits |= VulkanPipelineManager::CullFront; } if (cullMode & CullModeFlag::Back) { contentTraits |= VulkanPipelineManager::CullBack; decorationTraits |= VulkanPipelineManager::CullBack; } VkPipeline contentPipeline; VkPipelineLayout contentPipelineLayout; std::tie(contentPipeline, contentPipelineLayout) = pipelineManager->pipeline(contentMaterial, contentTraits, VulkanPipelineManager::DescriptorSet, VulkanPipelineManager::TriangleList, VulkanPipelineManager::SwapchainRenderPass); VkPipeline decorationPipeline; VkPipelineLayout decorationPipelineLayout; std::tie(decorationPipeline, decorationPipelineLayout) = pipelineManager->pipeline(decorationMaterial, decorationTraits, VulkanPipelineManager::DescriptorSet, VulkanPipelineManager::TriangleList, VulkanPipelineManager::SwapchainRenderPass); // Upload the uniform buffer data const QMatrix4x4 mvpMatrix = modelViewProjectionMatrix(mask, data) * windowMatrix(mask, data); const VulkanBufferRange ubo = uploadManager->emplaceUniform(mvpMatrix, data.opacity(), data.brightness(), data.saturation(), data.crossFadeProgress()); // Select the sampler const VkSampler sampler = (mask & (Effect::PAINT_WINDOW_TRANSFORMED | Effect::PAINT_SCREEN_TRANSFORMED)) ? scene()->linearSampler() : scene()->nearestSampler(); // Split the quads into separate lists for each type QuadSplitter quads(data.quads); // Allocate space in the upload buffer for the vertices size_t quadCount = quads.decoration().count() + quads.shadow().count(); if (!(contentTraits & VulkanPipelineManager::CrossFade)) quadCount += quads.content().count(); const VulkanBufferRange vbo = uploadManager->allocate(quadCount * 4 * sizeof(GLVertex2D)); // Bind the index and vertex buffers cmd->bindIndexBuffer(scene()->indexBufferForQuadCount(quads.maxQuadCount()), 0, VK_INDEX_TYPE_UINT16); cmd->bindVertexBuffers(0, { vbo.handle() }, { vbo.offset() }); GLVertex2D *vertices = static_cast(vbo.data()); size_t vertexOffset = 0; if (!vertices) return; VulkanClippedDrawHelper clip(cmd, clipRegion); // Draw the shadow // --------------- if (!quads.shadow().isEmpty() && shadowTexture) { quads.shadow().writeVertices(&vertices[vertexOffset]); const auto &view = shadowTexture.imageView(); auto &set = m_shadowDescriptorSet; // Update the descriptor set if necessary if (!set || set->sampler() != sampler || set->imageView() != view || set->uniformBuffer() != ubo.buffer()) { // Orphan the descriptor set if it is busy if (!set || set.use_count() > 1) set = std::make_shared(scene()->textureDescriptorPool()); set->update(sampler, view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, ubo.buffer()); } cmd->bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, decorationPipeline); cmd->bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, decorationPipelineLayout, 0, { set->handle() }, { (uint32_t) ubo.offset() }); clip.drawIndexed(quads.shadow().count() * 6, 1, 0, vertexOffset, 0); vertexOffset += quads.shadow().count() * 4; scene()->addBusyReference(set); scene()->addBusyReference(shadowTexture.image()); scene()->addBusyReference(shadowTexture.imageView()); scene()->addBusyReference(shadowTexture.memory()); } // Draw the decoration // ------------------- if (!quads.decoration().isEmpty() && decorationTexture) { quads.decoration().writeVertices(&vertices[vertexOffset]); const auto &view = decorationTexture.imageView(); auto &set = m_decorationDescriptorSet; // Update the descriptor set if necessary if (!set || set->sampler() != sampler || set->imageView() != view || set->uniformBuffer() != ubo.buffer()) { // Orphan the descriptor set if it is busy if (!set || set.use_count() > 1) set = std::make_shared(scene()->textureDescriptorPool()); set->update(sampler, view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, ubo.buffer()); } cmd->bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, decorationPipeline); cmd->bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, decorationPipelineLayout, 0, { set->handle() }, { (uint32_t) ubo.offset() }); clip.drawIndexed(quads.decoration().count() * 6, 1, 0, vertexOffset, 0); vertexOffset += quads.decoration().count() * 4; scene()->addBusyReference(set); scene()->addBusyReference(decorationTexture.image()); scene()->addBusyReference(decorationTexture.imageView()); scene()->addBusyReference(decorationTexture.memory()); } // Draw the contents // ----------------- if (!quads.content().isEmpty() && contentTexture) { if (!(contentTraits & VulkanPipelineManager::CrossFade)) { quads.content().writeVertices(&vertices[vertexOffset]); const auto &view = contentTexture.imageView(); const auto imageLayout = contentTexture.imageLayout(); auto &set = m_contentDescriptorSet; // Update the descriptor set if necessary if (!set || set->sampler() != sampler || set->imageView() != view || set->imageLayout() != imageLayout || set->uniformBuffer() != ubo.buffer()) { // Orphan the descriptor set if it is busy if (!set || set.use_count() > 1) set = std::make_shared(scene()->textureDescriptorPool()); set->update(sampler, view, imageLayout, ubo.buffer()); } cmd->bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, contentPipeline); cmd->bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, contentPipelineLayout, 0, { set->handle() }, { (uint32_t) ubo.offset() }); clip.drawIndexed(quads.content().count() * 6, 1, 0, vertexOffset, 0); vertexOffset += quads.content().count() * 4; scene()->addBusyReference(set); scene()->addBusyReference(contentTexture.image()); scene()->addBusyReference(contentTexture.imageView()); scene()->addBusyReference(contentTexture.memory()); // Drop the reference to the cross-fade descriptor set if we are not cross-fading m_crossFadeDescriptorSet = nullptr; } else { // Allocate and upload vertices auto vbo = uploadManager->allocate(quads.content().count() * 4 * sizeof(GLCrossFadeVertex2D)); GLCrossFadeVertex2D *vertex = static_cast(vbo.data()); VulkanWindowPixmap *previous = previousWindowPixmap(); const QRect &oldGeometry = previous->contentsRect(); - for (const WindowQuad &quad : quads.content()) { + for (const QuadSplitter::Quad &quad : quads.content()) { for (int i = 0; i < 4; ++i) { const double xFactor = double(quad[i].u() - toplevel->clientPos().x()) / double(toplevel->clientSize().width()); const double yFactor = double(quad[i].v() - toplevel->clientPos().y()) / double(toplevel->clientSize().height()); *vertex++ = { .position = { float(quad[i].x()), float(quad[i].y()) }, .texcoord1 = { float(xFactor * oldGeometry.width() + oldGeometry.x()), float(yFactor * oldGeometry.height() + oldGeometry.y()) }, .texcoord2 = { float(quad[i].u()), float(quad[i].v()) } }; } } const auto &currView = contentTexture.imageView(); const auto &prevView = previousContentTexture.imageView(); const auto imageLayout = contentTexture.imageLayout(); auto &set = m_crossFadeDescriptorSet; // Update the descriptor set if necessary if (!set || set->sampler() != sampler || set->imageView1() != prevView || set->imageView2() != currView || set->imageLayout() != imageLayout || set->uniformBuffer() != ubo.buffer()) { // Orphan the descriptor set if it is busy if (!set || set.use_count() > 1) set = std::make_shared(scene()->crossFadeDescriptorPool()); set->update(sampler, prevView, currView, imageLayout, ubo.buffer()); } cmd->bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, contentPipeline); cmd->bindVertexBuffers(0, { vbo.handle() }, { vbo.offset() }); cmd->bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, contentPipelineLayout, 0, { set->handle() }, { (uint32_t) ubo.offset() }); clip.drawIndexed(quads.content().count() * 6, 1, 0, 0, 0); scene()->addBusyReference(set); scene()->addBusyReference(contentTexture.image()); scene()->addBusyReference(contentTexture.imageView()); scene()->addBusyReference(contentTexture.memory()); scene()->addBusyReference(previousContentTexture.image()); scene()->addBusyReference(previousContentTexture.imageView()); scene()->addBusyReference(previousContentTexture.memory()); } } } WindowPixmap *VulkanWindow::createWindowPixmap() { return new VulkanWindowPixmap(this, m_scene); } } // namespace KWin