Showing posts with label directx11. Show all posts
Showing posts with label directx11. Show all posts

Sunday, 25 October 2015

Intersections Part2 : Id Maps

In the previous post, I spoke about the ability to perform hit detection using analytical functions.

This works extremely well when we can restrict our use case to it, but now we have some other cases where this is not as ideal:

  • Perform detection on arbitrary shape/3d model.
  • User input is not a pointer anymore, but can also be arbitrary (threshold camera texture, Kinect Body Index)
  • Both previous cases combined together

While we can often perform detection for 3d model by using triangle raycast (I'll keep that one for next post), it can be pretty expensive (specially if we perform a 10 touch hit detection, we need to raycast 10 times).

So instead, one easy technique is to use ID map.

Concept is extremely simple, instead of performing hit with a function, we will render our scene into a UInt texture, where each pixel will be object ID.

Of course it means you have to render your scene another time, but in that case you can also easily use the following:
  • Render to a downsized texture (512*512 is often sufficient)
  • Render either bounding volumes, or simplified versions of our 3d models.
Great thing with this technique, our depth buffer already makes sure that we have closest object ID stored (so we get that for "free").

So now we have our ID map, picking objectID from pointer is trivial:

Code Snippet
  1. Texture2D<uint> ObjectIDTexture;
  2.  
  3. RWStructuredBuffer<uint> RWObjectBuffer : BACKBUFFER;
  4.  
  5. float2 MousePosition;
  6. int width = 512;
  7. int height = 424;
  8. [numthreads(1,1,1)]
  9. void CS(uint3 tid : SV_DispatchThreadID)
  10. {
  11.     uint w,h;
  12.     ObjectIDTexture.GetDimensions(w,h);
  13.     
  14.     float2 p = MousePosition;
  15.     p = p * 0.5f  + 0.5f;
  16.     p.y = 1.0f-p.y;
  17.     p.x *= (float)w;
  18.     p.y *= (float)h;
  19.     
  20.     uint obj = ObjectIDTexture.Load(int3(p,0));
  21.     RWObjectBuffer[0] = obj;
  22.  
  23. }

Not much more is involved, we grab the pixel id, store in a buffer that we can retrieve in staging.

In case we need multiple pointer, we only need to grab N pixels instead, so process stays pretty simple (and we don't need to render scene for each pointer).


Now as mentioned before, we might need to perform detection against arbitrary texture.

As a starter, for simplicity, I will restrict the use case to single user texture.

So first we render user into a R8_Uint texture , where 0 means no active user and anything else = active.

We render our object map next in the same resolution.

We create a buffer (same size as object count, uint), that will store how many user pixel hit an object pixel.

Dispatch to perform this count.

Use another Append buffer, that select elements over a minimum account of pixel (this is generally important to avoid noise with camera/kinect textures).

Accumulating pixel hit count is done this way:

Code Snippet
  1. Texture2D<uint> ObjectIDTexture;
  2. Texture2D<float> InputTexture;
  3.  
  4. RWStructuredBuffer<uint> RWObjectBuffer : BACKBUFFER;
  5.  
  6. float Minvalue;
  7. int maxObjectID;
  8.  
  9. [numthreads(8,8,1)]
  10. void CS(uint3 tid : SV_DispatchThreadID)
  11. {
  12.     uint obj = ObjectIDTexture[tid.xy];
  13.     float value = InputTexture[tid.xy];
  14.     
  15.     if (value > Minvalue && obj < maxObjectID)
  16.     {
  17.         uint oldValue;
  18.         InterlockedAdd(RWObjectBuffer[obj],1,oldValue);
  19.     }
  20. }

Make sure you use InterlockedAdd, as you need atomic operation in that case.


Next we can filter elements:

Code Snippet
  1. StructuredBuffer<uint> HitCountBuffer;
  2. AppendStructuredBuffer<uint> AppendObjectIDBuffer : BACKBUFFER;
  3.  
  4. int minHitCount;
  5.  
  6. [numthreads(64,1,1)]
  7. void CS(uint3 tid : SV_DispatchThreadID)
  8. {
  9.     uint c,stride;
  10.     HitCountBuffer.GetDimensions(c,stride);
  11.     if (tid.x >= c)
  12.         return;
  13.     
  14.     int hitcount = HitCountBuffer[tid.x];
  15.     if (hitcount >= minHitCount)
  16.     {
  17.         AppendObjectIDBuffer.Append(tid.x);
  18.     }
  19. }


This is that easy, of course instead of only rendering ObjectID in the map, we can easily add some extra metadata (triangle ID, closest vertexID) for easier lookup.


Now in order to perform multi user detection (for example, using Kinect2 body Index texture), process is not much different.

Instead of having a buffer of ObjectCount, we create it of ObjectCount*UserCount

Accumulator becomes:

Code Snippet
  1. Texture2D<uint> ObjectIDTexture;
  2. Texture2D<uint> UserIDTexture;
  3.  
  4. RWStructuredBuffer<uint> RWObjectBuffer : BACKBUFFER;
  5.  
  6. float Minvalue;
  7. int maxObjectID;
  8. int objectCount;
  9.  
  10. [numthreads(8,8,1)]
  11. void CS(uint3 tid : SV_DispatchThreadID)
  12. {
  13.     uint obj = ObjectIDTexture[tid.xy];
  14.     uint pid = UserIDTexture[tid.xy];
  15.  
  16.     if (pid != 255 < maxObjectID)
  17.     {
  18.         uint oldValue;
  19.         InterlockedAdd(RWObjectBuffer[pid*objectCount+obj],1,oldValue);
  20.     }
  21. }

And filtering becomes:

Code Snippet
  1. StructuredBuffer<uint> HitCountBuffer;
  2. AppendStructuredBuffer<uint2> AppendObjectIDBuffer : BACKBUFFER;
  3.  
  4. int minHitCount;
  5. int objectCount;
  6. [numthreads(64,1,1)]
  7. void CS(uint3 tid : SV_DispatchThreadID)
  8. {
  9.     uint c,stride;
  10.     HitCountBuffer.GetDimensions(c,stride);
  11.     if (tid.x >= c)
  12.         return;
  13.     
  14.     int hitcount = HitCountBuffer[tid.x];
  15.     if (hitcount >= minHitCount)
  16.     {
  17.         uint2 result;
  18.         result.x = tid.x % objectCount; //objectid;
  19.         result.y = tid.x / objectCount;
  20.         AppendObjectIDBuffer.Append(result);
  21.     }
  22. }


We now have a tuple userid/object id instead, as shown in the following screenshot:




Please also note this technique can also easily be optimized with stencil, setting a bit per user. You get then limited to 8 users tho (7 users in case you also want to reserve one bit for object itself).

You will need one pass per user also (so 6 pass with proper depth stencil state/reference value).

If you lucky enough and can run on Windows10/DirectX11.3, and have a card that allows you, you can also simply do :


Code Snippet
  1. Texture2D<uint> BodyIndexTexture : register(t0);
  2.  
  3. uint PS(float4 p : SV_Position) : SV_StencilRef
  4. {
  5.     uint id = BodyIndexTexture.Load(int3(p.xy, 0));
  6.     if (id = 255) //No user magic value provided by Kinect2
  7.         discard;
  8.     return id;
  9. }


Here is a simple stencil test rig, to show all of the intermediates:


That's it for part 2 (that was simple no?)

For the next (and last) part, I'll explain a few more advanced cases (triangle raycast, scene precull....)


Intersections Part1

So last month been working on latest commercial project (nothing involving any extreme creative skills), so back into research mode.

I got plenty of new ideas for rendering, and quite some parts of my engine are undergoing some reasonable cleanup (mostly new binding model to ease dx12 transition later on).

There's different areas in my tool that I'm keen on improving, many new parts will be for other blog posts, but one has lately drawn my attention and I really wanted to get this one sorted.

As many of you know (or don't), I've been working on many interactive installations around, from small to (very) large.

One common requirement for those are some form of Hit Detection, you have some input device (Kinect, Camera, Mouse, Touch, Leap....), and you need to know if you hit some object in your scene in order to have those elements to react.

After many years in the industry, I've been developing a lot of routines in that aspect, so I thought it would be nice to have all of that as a decent library (to just pick when needed).

After a bit of conversation with my top coder Eric, we wanted to do a bit of feature list, what do we expect of an intersection engine, then the following came up:
  • We have various scenarios, some routines are better fit to some use cases, so we don't want a "one mode to rule them all". For example, if our objects are near spherical, we don't want to ray cast mesh triangles, ray cast on bounding sphere is appropriate (and of course much faster).
  • We want our routines sandboxed, so 4v/flaretic subpatch, it should be one node with inputs/outputs, cooking done properly inside and optimized. That saves us load time, reduce compilation times for shaders (or allow precompiled), and easier to control workflow (if our routine is not needed it costs 0).
  • We want our library minimal, so actually hit routines should not even create data themselves, they are a better fit as pure behaviours (It also helps to have those routines working in different environments).
  • We don't want to be gpu only, if a case fits better as CPU, then we should use CPU (if preparing buffers costs more time than performing the test directly, then let's just do it directly in cpu).

Next we wanted to decide which type of outputs we needed, this came out:

  • bool/int flag, which indicates if object is hit or not
  • filtered version for hit objects
  • filtered version for non hit objects

Then here are the most important hit detection features we require (they cover a large part of our use cases in general)
  • Mouse/Pointer(s) to 2d shape (in most cases we want rectangle, circle).
  • Pointer(s) to 3d object (with selectable precision, either raycast bounding volume eg sphere/box, or go at triangle level).
  • Area(s) to shapes (rectangle selection)
  • Arbitrary texture to shape (most common scenario for this is infrared camera, or kinect body index texture). In that case we also want the ability to differenciate between user id as well as object id.
  • In any 3d scenario, we also eventually want either closest object or all objects that get from the test.
  • We also have 3 general cases in 3d : Intersect (ray), Containment (is our object inside another test primitive), Proximity (is our object "close enough" to some place).
So once those requirements are set, to perform hit detection we generally have the 2 main following scenarios, you use analytical function or you use a map.

So let's show some examples, if that first post about it, I'll only speak about analytical functions.

In this case, we generally follow the usual pattern, convert our input into the desired structure (point to ray for example), and every mode follow the current pseudo code (c# version)


Code Snippet
  1. bool[] hitResults = new bool[objectCount];
  2. List<int> hitObjectList = new List<int>();
  3.  
  4. for (int i = 0; i < objectCount; i++)
  5. {
  6.     var obj = testObjects[i];
  7.  
  8.     bool hr = Performtest(userObject, obj);
  9.     hitResults[i] = hr;
  10.  
  11.     if (hr)
  12.     {
  13.         hitObjectList.Add(i);
  14.     }
  15. }


Pretty much all test modes will follow this pattern, only difference after is the test function.

Obviously when we start to reach a certain number of elements, this can become slow. And many times, our objects might be on our GPU, so we are not gonna load them back into CPU.

Translating this into hlsl is extremely straightforward, here is some pesudo code for it.


Code Snippet
  1. bool PerformTest(SomeUserObject userInput, SomeStruct object)
  2. {
  3.     return //Perform your intersection/containment/proximity routine here
  4. }
  5.  
  6. StructuredBuffer<SomeStruct> ObjectBuffers  : register(t0);
  7.  
  8. RWStructuredBuffer<uint> RWObjectHitResultBuffer : register(u0);
  9.  
  10. AppendStructuredBuffer<uint> AppendObjectHitBuffer : register(u1);
  11. RWStructuredBuffer<uint> RWObjectHitBuffer : register(u1); //In this case, UAV should have a counter flag
  12.  
  13. cbuffer cbUserInput : register(b0)
  14. {
  15.     SomeStruct userInput;
  16. };
  17.  
  18. cbuffer cbObjectData : register(b1)
  19. {
  20.     uint objectCount;
  21. };
  22.  
  23. [numthreads(128, 1, 1)]
  24. void CS(uint3 i : SV_DispatchThreadID)
  25. {
  26.     if (i.x >= objectCount)
  27.         return;
  28.  
  29.     uint oid = i.x;
  30.  
  31.     SomeUserObject object = ObjectBuffers[oid];
  32.  
  33.     bool hitResult = PerformTest(userInput, object);
  34.     RWObjectHitResultBuffer[oid] = hitResult;
  35.     if (hitResult)
  36.     {
  37.         //If we use append buffer
  38.         AppendObjectHitBuffer.Append(oid);
  39.  
  40.         //If we use counter buffer
  41.         uint idx = RWObjectHitBuffer.IncrementCounter();
  42.         RWObjectHitBuffer[idx] = oid;
  43.     }
  44. }


As you can see there's no huge difference into that.

It's pretty straightforward to perform ray to sphere/triangle/box as a starter.

Rectangle selection is also extremely simple:

  • Construct a 2d transformation for the screen area to check
  • Multiply inverse by camera projection
  • Build a frustrum from this
  • Perform a object/frustrum test insqtead of ray test.
here is a small range test example



Simple no? ;)

Now I can foresee 2 important question that our acute reader is probably already thinking of:
  • How do we get closest object?
  • What if we perfom several user inputs?


Of course, there are solutions for that.

Closest object.

First we will consider that our test function is also capable of returning distance.

So we modify our code by:


Code Snippet
  1. struct HitResult
  2. {
  3.     uint objectID;
  4.     float distanceToObject;
  5. };
  6.  
  7. bool PerformTest(SomeUserObject userInput, SomeStruct object, out float distanceToObject)
  8. {
  9.     return //Perform your intersection/containment/proximity routine here
  10. }
  11.  
  12. StructuredBuffer<SomeStruct> ObjectBuffers  : register(t0);
  13.  
  14. RWStructuredBuffer<uint> RWObjectHitResultBuffer : register(u0);
  15.  
  16. AppendStructuredBuffer<HitResult> AppendObjectHitBuffer : register(u1);
  17.  
  18. cbuffer cbUserInput : register(b0)
  19. {
  20.     SomeStruct userInput;
  21. };
  22.  
  23. cbuffer cbObjectData : register(b1)
  24. {
  25.     uint objectCount;
  26. };
  27.  
  28. [numthreads(128, 1, 1)]
  29. void CS(uint3 i : SV_DispatchThreadID)
  30. {
  31.     if (i.x >= objectCount)
  32.         return;
  33.  
  34.     uint oid = i.x;
  35.  
  36.     SomeUserObject object = ObjectBuffers[oid];
  37.  
  38.     float d;
  39.     bool hitResult = PerformTest(userInput, object,  d);
  40.     RWObjectHitResultBuffer[oid] = hitResult;
  41.     if (hitResult)
  42.     {
  43.         HitResult hr;
  44.         hr.objectID = oid;
  45.         hr.distanceToObject = d;
  46.         //If we use append buffer
  47.         AppendObjectHitBuffer.Append(hr);
  48.     }
  49. }


Now our buffer also contains our distance to object, the only leftover is to grab the closest element.

We have 2 ways to work that out:

  • Use Compute shader (Use InterlockedMin to filter closest element, since distance is generally positive there's no float to uint tricks to apply), then perform another pass to check if element distance is equal to minimum.
  • Use Pipeline ; DepthBuffer is pretty good to keep closest element, so we might as well let him do it for us ;)
Using pipeline is extremely easy as well, process is as follow:
  • Create a 1x1 render target (uint), Associated with a 1x1 depth buffer
Prepare an indirect draw buffer (from the UAV counter), and draw as point list, write to pixel 0 in vertex, and pass distance so it's written to depth buffer, since code speaks more, here it is:


Code Snippet
  1. struct HitResult
  2. {
  3.     uint objectID;
  4.     float distanceToObject;
  5. };
  6.  
  7. StructuredBuffer<HitResult> ObjectHitBuffer : register(u0);
  8.  
  9. cbuffer cbObjectData : register(b1)
  10. {
  11.     float invFarPlane;
  12. };
  13.  
  14. void VS(uint iv: SV_vertexID, out float4 p : SV_Position,
  15.     out float objDist : OBJECTDISTANCE,
  16.     out uint objID : OBJECTID)
  17. {    
  18.     p = float4(0, 0, 0, 1); //We render to a 1x1 texture, position is always 0
  19.     HitResult hr = ObjectHitBuffer[iv];
  20.     
  21.     objID = hr.objectID;
  22.     //Make sure we go in 0-1 range
  23.     objDist = hr.distanceToObject * invFarPlane;
  24. }
  25.  
  26. void PS(float4 p : SV_Position, float objDist : OBJECTDISTANCE, uint objID : OBJECTID,
  27.     out uint closestObjID : SV_Target0, out float d : SV_Depth)
  28. {
  29.     //Just push object id
  30.     closestObjID = objID;
  31.     d = objDist; //Depth will preserve closest distance
  32. }


Now our pixel contains our closest object (clear to 0xFFFFFFFF so this value will mean "no hit")

To finish for this first part, let's now add the fact that we have multiple "user Inputs".

We want to know the closest object per user.

This is not much more complicated (but of course will cost a test for each user/object).

Code Snippet
  1. struct HitResult
  2. {
  3.     uint objectID;
  4.     float distanceToObject;
  5. };
  6.  
  7. bool PerformTest(UserInput userInput, SomeStruct object, out float distanceToObject)
  8. {
  9.     return //Perform your intersection/containment/proximity routine here
  10. }
  11.  
  12. StructuredBuffer<SomeStruct> ObjectBuffers  : register(t0);
  13. StructuredBuffer<UserInput> UserInputBuffer : register(t1);
  14.  
  15. RWStructuredBuffer<uint> RWObjectHitResultBuffer : register(u0);
  16.  
  17. RWStructuredBuffer<HitResult> RWObjectHitBuffer : register(u1); //Counter flag
  18. RWStructuredBuffer<uint> RWObjectHitUserIDBuffer : register(u2);
  19.  
  20. cbuffer cbObjectData : register(b0)
  21. {
  22.     uint objectCount;
  23.     uint userCount;
  24. };
  25.  
  26. [numthreads(128, 1, 1)]
  27. void CS(uint3 tid : SV_DispatchThreadID)
  28. {
  29.     if (tid.x >= objectCount)
  30.         return;
  31.  
  32.     uint oid = tid.x;
  33.  
  34.     SomeUserObject object = ObjectBuffers[oid];
  35.     uint hitCount = 0;
  36.     for (uint i = 0; i < userCount; i++)
  37.     {
  38.         float d;
  39.         bool hitResult = PerformTest(userInput, object, d);
  40.  
  41.         if (hitResult)
  42.         {
  43.             hitCount++;
  44.             HitResult hr;
  45.             hr.objectID = oid;
  46.             hr.distanceToObject = d;
  47.  
  48.             uint idx = RWObjectHitBuffer.IncrementCounter();
  49.             RWObjectHitBuffer[idx] = hr;
  50.             RWObjectHitUserIDBuffer[idx] = i;
  51.         }
  52.     }
  53.     RWObjectHitResultBuffer[oid] = hitCount;
  54. }


Now we have a buffer with every hit from every user (here is a small example screenshot):





So instead of using a 1x1 texture, we use a Nx1 texture (where N is user Input count).

Process to get closest element is (almost) the same as per the single input.

Only difference, in Vertex Shader, route the objectID/Distance to the relevant user pixel, and you're set!


That's it for first part, next round, I'll explain how the "map technique works", stay tuned.

Friday, 20 February 2015

Profiling Direct2d (and hybrid ui rendering)

For a while I really wanted to know how much my User interface rendering costs me in pure reality.
I can consider my ui pretty smooth, but smoother is better than smooth ;)

So to debug Direct2d, you can use the Visual Studio Graphics debugger, but since I have several windows/panels, It's quite hard to get a screenshot.

Also I noticed that sometimes a snapshot doesn't include all the elements.

Technically I know that Direct2d uses a DirectX11 (with feature level 10) device, so if I can get this device pointer, I can easily run queries between those BeginDraw/EndDraw calls.

But it looks like it's not possible....

However...

It is now possible to provide our own DirectX11 device to a Direct2d context. I looked at that feature earlier on and thought it would be really complicated to update, but this was so simple that I feel embarrassed that I didn't do it before ;)

So reasoning is simple, instead of creating a HwmdRenderTarget, we create a SwapChain using our DirectX11 device instead.

Then we can create a Direct2d Render Context from this:

Code Snippet
  1. var context2d = new DeviceContext(this.swapChain.Texture.QueryInterface<SharpDX.DXGI.Surface>());
  2. //Call release on texture since queryinterface does an addref
  3. Marshal.Release(this.swapChain.Texture.NativePointer);


Yes, this is that easy.

Context is create from SwapChain, so it will use the device which own that one, instead of creating a new one.

Also since our context implements Direct2d RenderTarget, there was no code update for the rest of the ui rendering, life is good at times ;)

There's only one difference now, calling EndDraw does not trigger a Present call on the swapchain (technically I could use a standard render target instead of a SwapChain), so you have to call Present on your swapchain manually (not a big deal really ;)


So now I have device context working that way, I can just create a Pipeline Statistics and a TimeStamp query.

I'm pretty interested in primitive count and render time obiously.

1/Primitive cost

So first let's have a look at each geometry I use and check the cost at IA Stage. 
Since I draw a border, I check the cost for the "Fill[Primitive]", and for the "Draw[Primitive] which builds me outline.

One I first use a lot is RoundedRectangle (with a 1 pixel corner).

FillRoundedRectangle (1 pix corner) : 96
DrawRoundedRectancle: 264

So a single round rectangle is more than 350 primitives per element, that hurts (a lot)

Let replace those by standard Rectangle (as a side note since I have the grid snap, the visual difference was actually what I could call: "None")

FillRectangle : 6

This was pretty expected, 2 triangles

DrawRectangle : 190

This was much less expected, and feels rather high.

Still, replacing Round Rectangle by Rectangle pretty much half you poly count (and gave a pretty high boost to my rendering of course)

Now let's go for Ellipse (Which I use for pins/keyframes in timeliner):

FillEllipse : 204
DrawEllipse : 204

This is quite a staggering cost, I often have more than 1000 keyframes in my timeliner nowadays (Ok I cull keyframe rendering already), so my worst case scenarios (fully unzoomed ui and panel with all visible) is a whooping 408000 primitives!

In case of a patch, I can replace pin from Ellipse to Rectangle as well (I still learned to prefer ellipse which is visually much more pleasing, but then I can also add a render mode param (to shoose low/high quality).

In case of Timeline, can't really use quad, so I need a solution (see below)

Next we have Lines/Beziers

Line (whatever vertical/size...) : 46
Bezier : from 312 -> 620
Dashed Bezier : same as above

So links are also rather expensive, switch to choose link style is definitely a nice thing to add.

And obviously, finally : 

Text : 6 

I'll go back into this later, but pretty much text is 6 primitives (and likely a sprite sheet texture bound as well).

2/Draw ordering and Buffers

This is one thing which I actually looked more while using the graphics debugger, but this also gives you very valuable information.

For any solid color brush, direct2d fills the geometry content into a buffer, and either when it gets a context change (see below) or buffer is full, It copies the buffer and do a draw. 

So it does a pretty cool job at limiting draw calls, but does not use so much of GPU instancing, except for Text rendering.

So let's take a standard Node draw routine, and well see what's bad in there (pseudo code)

for each node
    fill rectangle
    draw outline
    draw title (text)
    draw pins (another loop)
end for

This translates this way in Direct2d (let's say we have 2 nodes, and I'll remove pins for clarity)

context->Draw(pcount, offset); //This is rectangle + first outline
context->DrawInstanced(6,1,0,0,0); //Text
context->Draw(pcount, offset); //This is rectangle + first outline
context->DrawInstanced(6,1,0,0,0); //Text

So each node need 2 draw calls. 
Obvious issue is that text rendering requires a different set of shaders, so Direct2d has to swap and can't batch efficiently anymore.

So let's reorganize out drawing this way:

for each node
    fill rectangle
    draw outline
end for

for each node
    draw title
end for

so now we are instead building 2 loops, first we render all the rectangles, then we render all the text.

And then magically :
context->DrawInstanced(6,n,0,0,0); //Node count

So now all our text is batched in a single draw, and rectangle is also reduced (depending on node count, but pretty often it reduces to a couple of calls maximum).


3/Hybrid rendering

So now by replacing some elements, I already managed to get quite a significant gain, 

Here is roundrect to rect cost on a reasonably large patch (please note that cpu/gpu times are not additive, since they work in tandem)

RoundRect + Ellipse
CPU : 6.5ms
GPU : 4ms

Rectangles
CPU : 3ms
GPU : 0.5ms

This is a pretty huge boost (specially considering we have the same quality).

Now I mentioned that swapping Ellipse for Rectangle was not an option for timeline, so I need a solution.

The obvious first choice is to use a small circle texture, but that does not fit really well with antialias (quite a big loss of quality).

So I need another solution....

And...

Do you remember? I am now drawing on a DirectX11 Swapchain, and I got access to it.

So let's move hybrid

Here is a 1000 + keyframe rendering profile (full redraw every frame, all keyframes visible)
CPU : 14ms
GPU : 9ms
Primitives : 388k

We can clearly see that hurts our graphics card quite a lot, since Direct2d doesn't instance in the GPU side, we have 388000 primitives uploaded on our GPU.

Rendering process as follow (pseudo code again)

for each track
    render header 
    for each keyframe
        if keyframe in timeline time range
           calculate position
           draw keyframe
        end if
    end for
end for
render other bits (rulers...)

So now let's give DirectX11 a bit of work, we create a simple instancing shader (rebuilds size and get color from a small buffer)

We create 2 structured buffers (1 for screen space position, 1 for color index)

and change the rendering as follow

reset keyframe counter
for each track 
   render header
    for each keyframe
        if keyframe in timeline time range
            add position/colorid into the cpu array
            increment keyframe counter
        end if
    end for
end for   
if keyframe counter > 0
    end draw (give hand from d2d to d3d)
    copy position/colorid to buffers
    draw instanced circles (outline) -> only need to upload position buffer + single draw => huge win)
    draw instanced circles (background) -> reuse the same buffer but scale down the circle in VS
    begin draw (we give back direct2d drawing rights)
end if
render other bits

Now using instanced circles here we are
CPU  : 3ms
GPU : 2.4ms
Primitives : 51k

That's 5 times faster on cpu workload, and 4 times faster on GPU, not bad ;)
Also we divided our primitive count by more than 6, without a loos of quality!

Let's try other techniques (Note: here we lose antialias in that case)
Instanced Rectangles-> clip/discard in pixel shader
CPU  : 3ms
GPU : 2.0ms
Primitives : 13k

No initial geometry (build both Rectangles in GS, clip in PS)
CPU : 2.9ms
GPU : 1.7ms
Primitives : 11k

In case you accept to lose AA settings, that can be another reasonable gain (thinking lower end machines)

So be able to use DirectX11 alongside Direct2d is a pretty massive win :)

4/Next stage

Obviously realizing how much gain we can get out of hybrid rendering, it would be a shame to stop here, users love smooth UI, so let's strive to give them this :)

Also having access to device makes it much easier for some other features (like draw some texture inside the d2d viewport).

Also as a side note, yes idea is to render user interface every frame (no partial redraw for now). 
Maybe partial can feel more efficient, but only once you nailed the full draw (since a zoom/pan = redraw, I don't want a half a second drop when I do this action ;)

New results/post soon

Saturday, 25 October 2014

Hap attack (and Quicktime fun)

A little while ago I got asked to add Hap support in vvvv.

This is a rather simple format, idea is that you get a BC1/BC3 frame (with small snappy compression), so you can do fast GPU upload.

It's more or less the scheme used by many "media servers", one difference is that all is packed in a single file instead of a bunch of dds files.

It's a pretty useful format since frame load is very fast, and can even be done within the frame, so you can have perfect synchronisation between videos on a single (or multiple) machines.

So the first step is to simply decode a frame, as a test rig I just used a media foundation source reader, which hapilly gives me a sample (aka: a frame), in compressed form.

Once you have this, everything is reasonably straightforward:

First 4 bytes are [length] (3 bytes) + Flag (1 byte)

Flag gives you compression + format like this (c#):

Code Snippet
  1. public enum hapFormat
  2. {
  3.     RGB_DXT1_None = 0xAB,
  4.     RGB_DXT1_Snappy = 0xBB,
  5.     RGBA_DXT5_None = 0xAE,
  6.     RGBA_DXT5_Snappy = 0xBE,
  7.     YCoCg_DXT5_None = 0xAF,
  8.     YCoCg_DXT5_Snappy = 0xBF
  9. }

Once you have this, you need to call Snappy to decompress (if relevant):

Code Snippet
  1. int uncomp = 0;
  2. SnappyStatus st = SnappyCodec.GetUncompressedLength(bptrData, frameLength, ref uncomp);
  3. st = SnappyCodec.Uncompress(bptrData, frameLength, (byte*)snappyTempData, ref uncomp);
  4. initialData = snappyTempData;


I just used an existing P/Invoke wrapper, no need to waster time reinventing the wheel:

http://snappy4net.codeplex.com/

Now you have your frame ready, you just have to upload to your GPU :

Code Snippet
  1. Texture2DDescription textureDesc = new Texture2DDescription()
  2. {
  3.     ArraySize = 1,
  4.     BindFlags = BindFlags.ShaderResource,
  5.     CpuAccessFlags = CpuAccessFlags.None,
  6.     Format = format.GetTextureFormat(),
  7.     Height = this.frameSize.Height,
  8.     Width = this.frameSize.Width,
  9.     MipLevels = 1,
  10.     OptionFlags = ResourceOptionFlags.None,
  11.     SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
  12.     Usage = ResourceUsage.Immutable
  13. };
  14.  
  15. DataRectangle dataRectangle = new DataRectangle(initialData, format.GetPitch(this.frameSize.Width));
  16. Texture2D videoTexture = new Texture2D(this.device, textureDesc, dataRectangle);
  17. ShaderResourceView videoView = new ShaderResourceView(this.device, videoTexture);

format.GetTextureFormat() takes care of properly converting the hap format to the relevant BC texture format.

That was about it to decode a frame, hardcore work ;)

Now as usual this part is only the tip of the iceberg, you have to think how to handle playback.

My initial thought was to continue using Media Foundation source reader, so I write a little player and check decode time, around 1.5ms per full hd frame (on a laptop with no SSD).

So all is pretty promising, really fast decode access, but then you reach the point where you want to loop your video, (which involves calling SetCurrentPosition on your source reader).

Surprisingly, this is extremely slow, grabbing a frame after a seek suddenly takes 60ms (which is far too much obviouly). That completely removes random play (seek every frame) as well.

So back to the good old windows AVI api, which just parses file and allows you to load a random frame in memory.

First we load the file:

Code Snippet
  1. Avi.AVIFileInit();
  2.             fileHandle = 0;
  3.             int r = Avi.AVIFileOpen(ref fileHandle, @"E:\repositories\cartfile\other\EncodingTest\sample-1080p30-Hap.avi", Avi.OF_READWRITE, 0);
  4.  
  5.             Avi.AVIFileGetStream(fileHandle, out videoStram, Avi.streamtypeVIDEO, 0);
  6.  
  7.             Avi.AVISTREAMINFO streamInfo = new Avi.AVISTREAMINFO();
  8.             Avi.AVIStreamInfo(videoStram, ref streamInfo, Marshal.SizeOf(streamInfo));
  9.  
  10.             Avi.BITMAPINFO bi = new Avi.BITMAPINFO();
  11.  
  12.             int biSize = Marshal.SizeOf(bi);
  13.  
  14.             Avi.AVIStreamReadFormat(videoStram, 0, ref bi, ref biSize);
  15.  
  16.             SharpDX.Multimedia.FourCC fcc = new SharpDX.Multimedia.FourCC(bi.bmiHeader.biCompression);

Please note that we get the Hap FourCC in the bitmap compression header (so we of course add a check to verify our AVI is encoded using Hap.

Now to get a frame, we simply call:

Code Snippet
  1. Avi.AVIStreamRead(this.videoStram, frameIndex, 1, this.aviTempData, 1920 * 1080, 0, 0);


With our frame Index. Since Hap uses one keyframe per frame, this is extremely fast.
Once done we upload to GPU as previous.

Now bit of code to integrate into vvvv, just wrap all that lot into some plugins:



That's pretty much it. Please note that upload is so fast that I didn't bothered yet to do any buffering. I quite like the concept of "ask for this frame and get it" :)

Now one thing is that hap can have 2 containers. Avi (from the directshow codec), or MOV (from the quicktime codec).

Of course most hap files from people using this thing called Mac will encode using the second codec. It's actually easy to change container, but well, it would be much better to read quicktime files directly.

That causes an initial problem, you need Quicktime installed on Windows (which sucks), and that implies to use the QuickTime SDK for windows (which has been abandoned more than 5 years ago). So that also mean forget 64 bits support.

As a side note, I can limit my use case, I only want to read Hap, if a video is from another codec my player just will not accept it.

So let's see if we can't just parse that mov file and just extract raw data like we do using AVI.

Here only difference, I did not found any wrapper (like vfw.h does for AVI), so time to go read specifications and open Hexadecimal editor ;)

For people interested, I will leave you to read the whole specs :
https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFPreface/qtffPreface.html#//apple_ref/doc/uid/TP40000939-CH202-TPXREF101

But let's summarize,

QuickTime files use the concept of Atom (which more or less just a Node in a tree structure).
Each Atom has a length, a code (fourcc) and can either contain other atoms or data, this is structured this way:

[Length 4 Bytes][FourCC 4 Bytes][Data = Length - 8 Bytes]

Yes length parameter includes itself and FourCC.

Please note there is nothing in the file format to know automatically is an Atom is a Leaf (data) or a Container (contains other Atoms), so you have to go read the documentation and find by yourself.

First atom is called : File Type compatibility (contains a header to check is it's a valid quicktime file, plus few version info.

Next we have "wide", which is a special one to allow to add a flag for large files.

Then we have "mdat" , which contains all the sample data (where we want to read from). But of course for now we don't know how data is organized.

So we need to go to the next one (called "moov"). Which contains all the information we need. There's a really high amount of options, but roughly from there we retrieve frame per second, and track list ("trak" atom).

We can already go into the track header ("tkhd" atom) to retrieve track length / size.

Then our work is not finished, we need to check if our file is Hap, this is contained in the "stsd" atom (Sample Description).

Once we are in the sample table, the most important data is at the reach of our hand (how to find position/length of a frame).

First, data is organized in chunks. A chunk contains one or more samples (so for example you can load the whole chunk from file instead of one at a time).

So we need to enumerate file offset for each chunks, which is contained into the "stco" Atom (Chunk Offset Atom).

Data is simply a prefix table, which contains file offset for each chunk. Please note the offset is absolute to the file, which makes it much easier since once we get that data we don't need to check for child Atom anymore.

Here is the data for a test mov file (powered by Hex Editor and Windows calculator ;)

stco (chunkoffsets)
Chunk 1 Offset : 48
Chunk 2 Offset : 1015901
Chunk 3 Offset : 2030918
Chunk 4 Offset : 3045373
Chunk 5 Offset : 4058366
Chunk 6 Offset : 4348429

Pretty simple, since all is absolute search is also much faster.q

Now, we need to know each Sample (or frame) size.

All frames size are contained in a single Atom ("stsz"), so we go thought them and get frame length:

stsz (sample size table)
Sample 1 : 144974
Sample 2 : 145333
Sample 3 : 145210
Sample 4 : 145344
Sample 5 : 145065
Sample 6 : 144811

Now we still don't know how samples relate to chunks (the last missing piece of the puzzle).

Now we need to read data in "stsc" atom (Sample to Chunk), which is a prefix table.

In my sample mov, this is described this way:

stsc (sample to chunk)
First/Sample Per Chunk/ Descriptor
1 / 7 / 1
5 / 2 / 1
6 / 1 / 1

As you can see this is compressed (7+2+1 = 10), and my file has 31 frames.

So this simply expands, as from 1 to 5 (the first 4 chunks), we have 7 samples.

Which is correct, since 7*4+2+1 = 31


So that's about it, with all that data we are ready to roll, we first build a prefix sum for chunk offsets:
0 - 7 - 14 - 21- 28 - 30

From this it's pretty easy to retrieve in which chunk our frame is contained.

Once we know chunk + position in chunk, we need to iterate using each sample length until we reach our position.

So third frame location is :

First Chunk = 48 + 144974 + 145333

Of course we can precompute all this (per chunk or per sample), so we end up having 2 arrays:
frameindex->location
frameindex->size

Then we just load our frame from file into memory, and rinse and repeat the Hap upload.

So here we go, Hap decoder (small SharpDX standalone sample) , which reads Hap MOV file without any need for QuickTime installed.





Fun times