【问题标题】:Using arbitrary QML items as cached image source使用任意 QML 项目作为缓存图像源
【发布时间】:2016-05-03 16:44:36
【问题描述】:

编辑:原始问题仍包含在下面,但我决定重新命名为在各种情况下对开发人员更有用的表单,其中一些在我下面的回答中描述,作为解决原来的问题提供了更广泛的应用领域。


我有一组用于应用程序的灰度图标,并且要求用户可以更改图标颜色。

因此,显而易见的解决方案是使用来自QtGraphicalEffects 的股票Colorize 元素。

效果本身有一个cached 属性 - 它缓存该特定效果的结果,因此它不会被连续计算。但是,这只适用于效果的特定实例,这意味着如果有多个图标实例,并且每个实例都有着色效果,则此缓存不会在不同实例之间共享。

显然,一个缓存就足够了,考虑到所有图标的大小和颜色都相同,可以重复使用来自 VRAM 的数据,从而节省 VRAM 和 GPU 时间。

所以最大的问题是如何重用单个效果的单个缓存并多次显示它而没有任何开销。

另外,上一个问题是关于我目前使用着色图标所学的课程。但是,我可能缺少另一种方法。

当然,效率是关键,但也需要简单,我的意思是我可以想到几种低级方法来非常有效地做到这一点,但它们都需要更复杂的低级实现,而在 QML 中是不可能的。

【问题讨论】:

    标签: image qt caching qml components


    【解决方案1】:

    结果出乎意料地简单。

    在这种情况下,特定于 OP - 即为图标着色,最有效的方法是简单地使用带有 琐碎 片段着色器的自定义 ShaderEffect - 将 gl_FragColor 设置为所需颜色,作为 vec4 和源图像的 alpha 值传递。真的不需要缓存任何东西,因为着色器真的很简单,而且速度很快。

    只有一件事需要考虑 - QML 场景图可能会在纹理图集中分配原始图像,然后默认实现会将纹理从图集中复制到另一个纹理。我们不希望这样,因为它违背了目的 - VRAM 使用会增加,因为这将针对每个“实例”进行,并且新分配的纹理也有可能大于它们需要的大小应该是,因为在某些平台上,纹理的大小是有限制的,在这种情况下,我们谈论的是图标,所以它们不会那么大。

    解决方案是将supportsAtlasTextures 显式设置为true。这意味着您还必须在图集中传递纹理的偏移量并计算偏移量 - 仍然很少有开销。这将确保效率,来自图集的纹理不会在内存中重复,此外,渲染引擎实际上将允许使用来自同一图集的不同纹理的不同着色器效果在一次调用中批处理在一起。


    类似的方法可用于缓存几乎所有内容,并使用该缓存来显示“图像” - 使用 ShaderEffectSource 来“捕获”所需的图像,然后使用 ShaderEffect 和更多琐碎的片段着色器 - 只需从源采样器输出数据。立即想到几个非常有用的用例:

    • 它可用于“实例化”图像,这是计算密集型着色器的结果,请记住 ShaderEffectSources 和 ShaderEffects 可以以任意顺序链接
    • 它可用于实例化程序生成的图像,再次使用着色器,可用作平铺纹理,甚至可以非常高效地制作动画
    • 它可以与 QML Canvas 一起使用,以将复杂的画布绘图用作多个“图像”的缓存和源
    • 它可以用作图像,由复杂的 QML Items 组成 - 这些实际上在 RAM 上相当重,想象一个场景,你有 1000 个对象,每个对象都是在 20 个不同的 QML 项目中——矩形、文本、图像、上帝禁止动画,这是内存中的 20000 个对象——根据我的测试,这就像 500 MB 的 RAM 使用量,但如果它们相同,则可以使用单个对象来提供缓存,并且所有其他对象只能使用单个着色器效果来显示该缓存。它对 CPU 时间也有影响 - 比如说你的设计必然会改变值 - 一个非常常见的场景,如果你在内存中有 20000 个对象,那就是 20000 个评估的绑定 - 即使对于微不足道的表达式,这在移动设备上也可能需要几秒钟,在这段时间内冻结屏幕。缓存将冻结时间减少 1000 倍,几乎不存在。

    • 它还可以用于缓存和实例化动画,显着减少所需的 CPU 时间,还可以处理视频

    【讨论】:

      【解决方案2】:

      我为优化 QtGraphicalEffects 所做的是使用 Item.grabToImage() :)

      此函数返回具有url() 函数的QQuickItemGrabResult 项目。此函数返回一个QUrl,可以将其设置为Image 对象的源。

      所以你需要做的是创建一个Image 并在其上应用Colorize。当它准备好使用grabToImage() 并在成功抓取后将QUrl 保存在安全的地方并销毁源对象。


      我想您需要在应用程序运行时不时更改图标的颜色。如果是这样,请记住,只需更改 Image 对象的来源,以便没有人使用抓取的图像 url 就会使其从内存中释放。不是即时的,而是在需要时。

      由于某些不兼容,我的应用程序只有在我在 main.cpp 文件中使用 QGuiApplication::setAttribute(Qt::AA_UseOpenGLES); 时才能正确管理内存。


      还有一个重要的事实:

      图像在内部被缓存和共享,因此如果多个图像项具有相同的来源,则只会加载图像的一份副本。 (source)


      这是工作示例。源对象是Rectangle,但可以轻松更改为Image

      import QtQuick 2.3
      import QtQuick.Window 2.2
      import QtGraphicalEffects 1.0
      
      Window {
          visible: true    
          width: 500
          height: 500
      
          property string urlOfMyIcon: ""
      
      
          Grid {
              columns: 1
              spacing: 10
              Image {
                  width: 100
                  height: 100
                  source: urlOfMyIcon
              }
              Image {
                  width: 100
                  height: 100
                  source: urlOfMyIcon
              }
              Image {
                  width: 100
                  height: 100
                  source: urlOfMyIcon
              }
          }
      
          Component.onCompleted: {
              component.createObject(this)
          }
      
          Component {
              id: component
              Item {
                  id: yourImageWithLoadedIconContainer
                  Rectangle {
                      id: yourImageWithLoadedIcon
                      width: 80
                      height: 80
                      color: "white"
                      visible: false
      
                      // needed because I used Rectangle instead of Image
                      Component.onCompleted: {
                          colorizeEffect.grabToImage(function(result) {
                              urlOfMyIcon = result.url;
                              yourImageWithLoadedIconContainer.destroy()
                          }, Qt.size(width, height));
                      }
      
                      // use this when using Image instead of Rectangle
      //                onStatusChanged: {
      //                    if (status === Image.Ready)
      //                        colorizeEffect.grabToImage(function(result) {
      //                            urlOfMyIcon = result.url;
      //                            yourImageWithLoadedIconContainer.destroy()
      //                        }, yourImageWithLoadedIcon.sourceSize);
      //                }
                  }
                  Colorize {
                      id: colorizeEffect
                      anchors.fill: yourImageWithLoadedIcon
                      source: yourImageWithLoadedIcon
                      hue: 0.8
                      saturation: 0.5
                      lightness: -0.2
                      visible: false
                  }
              }
          }
      }
      

      【讨论】:

      • 在我的例子中,图标颜色和大小都可以在运行时改变,所以我必须看看是否可以使用绑定来自动化这个过程。不过我不得不承认,这个解决方案并不理想,因为它不会留在 VRAM 中 - grabToImage()从 VRAM 复制到 RAM,只是在用作图像源时将其复制回 VRAM。最好省略那些实际上不必要且已知传输速度相对较慢的内容。
      • @ddriver 很好,你是对的。如果您需要另一种方法,我只能推荐我在 Docs about Item.grabToImage() 中找到的内容:注意:此函数会将项目渲染到屏幕外表面并将该表面从 GPU 的内存复制到 CPU 的内存中,这可能非常昂贵。对于“实时”预览,请使用图层或 ShaderEffectSource。
      • 昨晚睡觉后,我实际上想出了一个可能的解决方案,使用 ShaderEffectSource,但尚未测试它是否有效。有空我会测试一下,如果成功就发帖。
      • @ddriver 太好了。期待它:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-15
      • 1970-01-01
      • 1970-01-01
      • 2011-01-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多