<div class="gmail_quote">On Tue, Mar 24, 2009 at 8:34 AM, Boudewijn Rempt <span dir="ltr">&lt;<a href="mailto:boud@valdyas.org">boud@valdyas.org</a>&gt;</span> wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="im">
&gt; 1) The first thing to implement is Mipmapping. I&#39;ve come to a conclusion<br>
&gt; that mipmapping should be done at the level of Layer&#39;s projection (e.g.<br>
&gt; KisPaintLayer::Private {KisMipmappedPaintDevice projection; ...}). This<br>
&gt; KisMipmappedPaintDevice class should be derived from KisPaintDevice and<br>
&gt; support most of it&#39;s methods. The difference is that mipmapped class will<br>
&gt; have pyramid of scaled images.<br>
<br>
</div>Are you sure that KisPaintDevice is the right level, instead of<br>
KisDataMananager/KisTiledDataManager -- I&#39;d have thought that adding a<br>
KisMipMappedDataManager would be a more likely solution. I was also thinking<br>
of havinga  KisUntiledDataManager for those paint devices that are ephemeral<br>
and small, like brush footprints.<br>
<div class="im"></div></blockquote><div><br>Well, my motivation were cases A and B =)<br><br>caseA {<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div>
&gt; Why should it be put here? Let&#39;s look at the case when we have the only<br>
&gt; PaintLayer. In such a case, applying of any paintop we&#39;ll change 1-2 tiles.<br>
&gt; All the rest tiles will be untouched, and of course their scaled-down<br>
&gt; versions will stay untouched too. It turnes out that we have to scale only <br></div></blockquote><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="im">
&gt; 1-2 tiles instead of scaling the whole image.<br>
</div></blockquote><div>}; <br><br></div><div>caseB { <br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="im">
&gt; The things are a bit more difficult with Adjustment Layers because most of<br>
&gt; them need the whole image for processing. But there are a few filters that<br>
&gt; could benefit from mipmapping at the layer stage. E.g. Perchannel Filter.<br>
&gt; It could process scaled-down picture while previewing (previewing is the<br>
&gt; _worst_ thing in this filter at the moment). All the filters could have a<br>
&gt; method KisFilter::NeedsOriginalImage() that would say whether filter can<br>
&gt; work on downscaled image.<br>
</div></blockquote><div>};<br> <br>Maybe we are both right ;) The implementation should be spread between KisDataManager and KisPaintDevice. Now i&#39;ve got how to describe that:<br><br>We have to add a new layer of abstraction (not sure this term is right here) to these classes: that is &quot;current zoom level&quot;. The problem of current code is hiding in impossibility to work with image sizes different from original, so to say we _always_ have to work (modify, compose from layers, project them) with entire unscaled image. In some cases (see &quot;caseA&quot;) this make a huge overhead. As i&#39;ve understood from the code, the caseA in the current revision of the code works like that:<br>
<br>1) PaintOp changes a couple of pixels on some tile (scale 100%)<br>2) KisGroupLayer activates KisMergeVisitor, that in turn copies KisPaintLayer-&gt;d-&gt;m_paintDevice to ... to __root_layer-&gt;d_m_projection<br>3) Then KisQPainterCanvas::paintEvent comes, this function in turn:<br>
    a) calls KisImage::convertToQImage through classes KisPrescaledProjection and KisProjectionCache<br>       /**<br>         * 2boud: Why this KisProjectionCache is needed at all? I guess if caching and zooming is done in Tiles, this class (and KisPrescaledProjection too) will be happily removed<br>
         */<br>    b) KisImage::convertToQImage converts entire image stored in __root_layer-&gt;d_m_projection to QImage<br>    /**<br>      * Till this moment we have done a dozen of operations on *original unscaled image*. Remember point 1) - we&#39;ve changed a couple of pixels of the only tile.<br>
      */<br>    c) KisPrescaledProjection scales down image<br>    d) result returned to KisQPainterCanvas<br>4) KisQPainterCanvas draws these two pixels =)<br><br><br>Have i understood the architecture right?<br><br><br>
When we introduce &quot;zoom levels&quot;, we&#39;ll be able to request from paintDevice any zoomed-version of original image. Speaking about caseA, the Tiles subsystem will know that only one tile changed and will scale it down. Resulting projection will consist of prescaled old tiles and the new one. Writing to the paintDevice by all the paintOps should always be done to 100% level, but reading at any level. There is only exception - previewing (see caseB). Preview mechanism will feed filters with scaled-down versions of image, stored in internal m_previewPaintDevice (or just checkout scaled-down version from paintDev. to m_previewQImage, that is issue for dispute).<br>
<br>So, i see &quot;zoom levels&quot; like that:<br><br><span style="font-family: courier new,monospace;">  Tiles subsystem                    KisTiledPaintDevice<br>| ---------------- |                |--------------  |<br>
| *stores pyramids |&lt;----some------&gt;|   it&#39;s *only*  |<br>| *caches tiles    |&lt;-*!internal!*-&gt;|    inteface    |&lt;-...External <br>| *scales tiles    |&lt;----links-----&gt;|    to tiles    |&lt;-...interface<br>
| *etc...          |                |   subsystem    |<br>|                  |                | (with drawing, |<br>*------------------*                |   of course)   |<br>                                    *----------------*<br style="font-family: courier new,monospace;">
</span><br>External interface should be smth like:<br><br><font face="courier new,monospace">*-<br>| MIPMAPPED PART<br>|<br>|--convertToQImageNew(zoomlevel_struct zoomlevel)<br>|--drawSomething(zoomlevel_struct zoomlevel)<br>
|--...<br>|<br>|----------------<br>| LEGACY PART (works only at 100% zoom)<br>|<br>|--convertToQImage()-&gt;<br>|--drawSomething()-&gt;<br>|--...<br>|<br>*-</font><br><br>struct zoomlevel_struct {<br>    enum {<br>        ORIGINAL=0,<br>
        NOT_ORIGINAL<br>    } should_use_m_scale;<br>    double m_scale;<br>}<br><br><br>In such a case algorithm for caseA will be much simplier and more efficient:<br><br>1) PaintOp changes a couple of pixels on some tile (scale 100%)<br>

2) KisGroupLayer activates KisMergeVisitor, that in turn copies
KisPaintLayer-&gt;d-&gt;m_paintDevice to ... to
__root_layer-&gt;d_m_projection<br>    This step is like in previous case, except of _all the operations are made in current zoom-level_ (disputable?) and prescaled-down tiles cached<br>
3) Then KisQPainterCanvas::paintEvent comes, this function in turn:<br>
    a) calls KisImage::convertToQImage just copies __root_layer-&gt;d_m_projection (all the work is already done).<br>
4) KisQPainterCanvas draws these two pixels =)<br><br><br><br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="im">
</div>Yes, such a flag is needed. We might want to do a little refactoring with all<br>
our flags, though. Maybe create an enum and a test function, </blockquote><div> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">following Qt4 api<br>

conventions.</blockquote><div>Where should i read about it?<br><br> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="im">
&gt; More than that, layer-level mipmapping can be used while creating the<br>
&gt; projection of the root layer.<br>
&gt;<br>
&gt; Question. Could someone tell me, will the result be different in these two<br>
&gt; situautions:<br>
&gt; Assume we have two *PaintLayers*.<br>
&gt; 1) first we Merge them, then Scale down<br>
&gt; 2) first we Scale them down, then Merge<br>
&gt; Will the result be different? (or, more precisely, will the result be<br>
&gt; *much* different)<br>
<br>
</div><br>I think we need to experiment with this. My gut feeling says &quot;yes, there&#39;ll<br>
be a discernable difference&quot;.</blockquote><div> </div><div>Mine agrees with yours=) But i guess it&#39;s bearable for preview (e.g. filters)<br><br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
 Also note that the current zoom level of the<br>
canvas is rarely stable, people tend to zoom in and out a lot, which means<br>
that we don&#39;t win much by not compositing the levels that aren&#39;t in current<br>
use.</blockquote><div> </div><div>Ideally we shouldn&#39;t compose layers at all, we should compose tiles, but i can&#39;t imagine how atm :)<br> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">

The gimp, btw, has currently already something like this, I believe. I haven&#39;t<br>
had time to check their source code yet, though.<br>
<div class="im"><br>
&gt; If the difference is too small, then we could use pyramid in<br>
&gt; KisMergeVisitor to create a projection.<br>
&gt;<br>
&gt; (the last issue can&#39;t be applied in case we have at least one Adjustment<br>
&gt; Filter in the stack, in this case we should consult with<br>
&gt; KisFilter::NeedsOriginalImage() first. BUT this approach works well with<br>
&gt; Filter Masks, as in such a case filters brake only one pyramid that can be<br>
&gt; quickly recalculated)<br>
&gt;<br>
&gt; 2) Locking and threading. I think locking should be made something like<br>
&gt; linux kernel&#39;s deferred irq subsystem. Every MipmappedPaintDevice should<br>
&gt; have its own working thread. Most of the time it sleeps on it&#39;s<br>
&gt; working_queue&#39;s semaphore. When work appears it goes to work_queue, the<br>
&gt; semaphore gets up and thread goes on calculating piramids for the tiles<br>
&gt; listed in workqueue.<br>
<br>
</div>Sounds good.<br>
<div class="im"><br>
&gt; In addition every tile should have it&#39;s own read/write semaphore.<br>
<br>
</div>Also think of COW and concurrent reads and so on.</blockquote><div>Something like Read-copy-update (RCU) in linux? I guess it&#39;s too weird for that.<br><br> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="im">
&gt; 2b) Swapping. Again, one KisTiledDataManager - one swapper thread. This<br>
&gt; thread could work without workqueue. Just go though existing tiles and find<br>
&gt; unneeded. The concurrent access to tiles is controlled by tile&#39;s<br>
&gt; RW-semaphore.<br>
<br>
</div><br>Right now I thin, we have one swapper thread -- if it&#39;s a thread and not part<br>
of the main loop -- because we have one big pool of tiles instead of separate<br>
pools for every paint device. This did give us quite a bit of performance<br>
boost back in the days when it was created this way.<br>
<div class="im"></div></blockquote><div><br>What was the cause of this boost? <br>I&#39;ll think over idea of united storage. But on new processors the more threads the better ;)<br><br> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="im">
&gt; 3) In memory compression. I&#39;m not sure i&#39;ll have time for good on-the-fly<br>
&gt; compression engine, but it can be made in swapper thread.<br>
<br>
</div>We have a good compression engine for on-the-fly work already -- Ariya Hidayat<br>
made it and did some experiments that showed that keeping lesser-used stuff in<br>
compressed form in memory before swapping (and uncompressing on undo) would<br>
indeed save us a lot of memory and also quite a bit of performance, since the<br>
compressed tile would take fewer memory reads to get into the processor cache.<br><div class="im"></div></blockquote><div><br>I&#39;ve not thought about compressing of mementoed tiled. Sounds great.<br> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="im">
&gt; Could someone comment on this plan and tell me what should i do next?<br>
<br>
</div>It looks cool. Chew a bit on it and then enter a proposal with Google.<br>
<div class="im"></div></blockquote><div><br> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="im">
&gt; PS:<br>
&gt; What does KisProjection do? Where is it used? I&#39;ve not seen it&#39;s<br>
&gt; declarations in layers&#39; classes.<br>
<br>
</div>It used to do multi-threading of recompositing and was owned by KisImage. I&#39;m<br>
currently reworking it to work with the new stateless recompositing scheme,<br>
but I&#39;ve run into a Threadweaver bug I haven&#39;t had time to fix yet.<br>
<font color="#888888"></font></blockquote></div><br>i see<br><br>PS:<br>Taking into account the parallel thread about full-time working. I have four exams at the university in the beginning of June, but i hope to manage to pass two of them in advance in May. So at least two of them are left for June and it&#39;ll take at least 9 days. Can it prevent me from GSOC?<br>
<br>PPS:<br>/* in the beginning */<br>heh.. let&#39;s sit down and write a short reply...<br>=)<br><br clear="all"><br>-- <br>Dmitry Kazakov<br>