Strange SetHighDensity() behaviour

Post Reply
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Strange SetHighDensity() behaviour

Post by ColinP »

I'm wanting to use the VMD design pane to layout canvas objects to indicate where my custom UI components are in a conventional application.

The canvas objects all need to be in high density mode and if I select the High Pixel Density check box in the property pane then everything works fine.

VMD generates code like this...

knobCanvas.SetHighDensity( true );
knobCanvas.SetPosition( 282, 54 );
knobCanvas.SetSize( 50, 50 );

But it's a nuisance having to manually set every canvas to use high density so one would think one could leave the property unchecked and do it in my own code.

Unchecking the property just drops the SetHighDensity( true ) call from the generated code.

If I don't call SetHighDensity() in my own code then the canvas position is correct but the canvas is in low-res mode.

But if I call SetHighDensity() in my own code the position of the canvas mysteriously changes to x / 2 and y / 2.

Strange behaviour but one would then think that the following code would fix it...

int x = canvas.GetX();
int y = canvas.GetY();
canvas.SetHighDensity( true );
canvas.SetPosition( x, y );

But it doesn't - the coordinates are still halved. Interestingly if I try...

int x = canvas.GetX() ;
int y = canvas.GetY();
canvas.SetHighDensity( true );
canvas.SetPosition( x * 2, y * 2 );

Then the x and y coordinates are then two times too large!

--------

I just checked the value returning from GetX() either side...

Log( "x1 = " + knobCanvas.GetX() );
knobCanvas.SetHighDensity();
Log( "x2 = " + knobCanvas.GetX() );

and this outputs...

x1 = 282
x2 = 282

...whether or not the VMD property is checked. But if it's not checked the canvas displays in the wrong place.
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Strange SetHighDensity() behaviour

Post by ColinP »

I've managed to figure it out using this diagnostic code...

Log( "x1 = " + knobCanvas.GetX() );
int x = knobCanvas.GetX();
int y = knobCanvas.GetY();
knobCanvas.SetHighDensity( true );
Log( "x2 = " + knobCanvas.GetX() );
knobCanvas.SetPosition( x + 1, y );
Log( "x3 = " + knobCanvas.GetX() );
knobCanvas.SetPosition( x, y );
Log( "x4 = " + knobCanvas.GetX() );

Which as I suspected results in

x1 = 282
x2 = 282
x3 = 283
x4 = 282

... with the canvas displayed correctly

So there's a bug inside VoltageCanvas where an internal mirror of the coordinates (probably inside the underlying component) gets halved when SetHighDensity() is called and SetPosition() ignores the instruction to update if x and y match another x and y stored inside VoltageCanvas.

So the workaround is to fool it into updating...

int x = canvas.GetX();
int y = canvas.GetY();
canvas.SetHighDensity( true );
canvas.SetPosition( x + 1, y );
canvas.SetPosition( x, y );

So if you need to call SetHighDensity( true ) in your own code then use something like this last snippet to workaround the bug.
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Strange SetHighDensity() behaviour

Post by ColinP »

Hmmm.

I'm currently rewriting my UI component shadow code to generailze it so that the light source can come from any direction, the caster can have variable height (so a button can cast a shorter shadow when it's pressed) and so I can eventually support glow effects. But as I'm doing so I'm finding the way VoltageCanvas works to be very peculiar.

There's a distinct sense of Friday afternoon about the way coordinates and dimensions are treated when SetHighDensity( true ) is called.

If the parent is a high-density canvas too then the errors seem to be compounding so you end up with things being wrong by a factor of four not just two.

Trying to roll this crazy behaviour into affine transform concatenation so that an independent resolution blurred alpha source is mapped in one go has caused my brain to melt so I thought I'd put out a health warning for any devs left doubting their sanity when pursuing a similar path.
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Strange SetHighDensity() behaviour

Post by ColinP »

OK, so I've bypassed all the fancy stuff so that my code falls back to core test rendering.

This is what I'm after (ignore the LED)...

CanvasTest.png
CanvasTest.png (70.55 KiB) Viewed 6146 times

So around each component there's a shadow/fx region shown here as a semi-transparent rectangle. Here's where shadows and glow effect are to be rendered.

But to achieve this I have to double the x and y coordinates and quadruple the width and height of the regions.

This is when the parent canvas (with the gradient fill) has SetHighDensity( true ) called on it but if I don't do that then the result is as follows...

CanvasTest2.png
CanvasTest2.png (44.32 KiB) Viewed 6146 times

So the child canvas somehow inherits the resolution change from the parent canvas. I have no idea why. Perhaps someone can explain why this might happen?

BTW I attach things using AddChildComponent() to a canvas occupying the entire module because for some reason CA give AddComponent() protected status in VoltageComponent which means it's not possible to delegate component addition.
UrbanCyborg
Posts: 625
Joined: Mon Nov 15, 2021 9:23 pm

Re: Strange SetHighDensity() behaviour

Post by UrbanCyborg »

Woof. Also, Arf. Have CA had anything to say about this behavior?

Reid
Cyberwerks Heavy Industries -- viewforum.php?f=76
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Strange SetHighDensity() behaviour

Post by ColinP »

I think I might have sorted it out, it's most likely that it's just me getting confused. Wouldn't be the first time!

When a double res canvas is a child of a double res canvas I thought that I had to quadruple the dimensions of the child canvas but I now think I only need to double the child canvas dimensions as it's the dimensions of the rendering in the child canvas that needs to be quadrupled.

So the scaling is inherited but in a symmetrical fashion.

At least I think so. There are four permutations and several sources of confusion so I need to sleep and attack it again tomorrow with plenty of coffee!
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Strange SetHighDensity() behaviour

Post by ColinP »

OK, I think I've figured it out.

My initial problem was partly because of the call order sensitivity and caching but principally because I forgot that I'd switched over the shadow/fx parent from being the VoltageModule object (the this object in the main code) to being the background VoltageCanvas object. The background canvas is a child of the this object and is required to do live painting of the background and has SetHighDensity( true ) called on it so that the background isn't pixellated when zoomed.

Now sometimes I want to be able to layout things in the VMD design pane so then I use the canvases generated there for the component canvases. so components are children of the this object. But because VoltageModule.AddComponent() is protected it makes the code messy to make the shadow/fx canvases children of this too, so I made them children of the background instead so that I can then use the public AddChildComponent() which can be delegated. But doing so moves them into a coordinate space that is double the width and height of the this object one.

So I've gone through all four permutations of a situation where a canvas is a child of a canvas that's a child of this...

If neither canvas is high-res then to get the expected result everything is straightforward and the child canvas x scale is 1, y scale is 1, w scale is 1, h scale is 1 and rendering is to a buffer with w by h pixels.

If only the parent canvas is hi-res then the child xywh scale is 2 and the rendering is to a buffer with 2 * w by 2 * h pixels.

If only the child canvas is hi-res then the child xywh scale is 1 and the rendering is to a buffer with 2 * w by 2 * h pixels.

But if both the parent and the child are hi-res then the child xywh scale is 2 but the rendering is to a buffer with 4 * w by 4 * h pixels.

So basically calling SetHighDensity( true ) on a canvas changes the coordinate space of its children to one that has twice the width and twice the height (and four times as many pixels) but then if you call SetHighDensity( true ) on a child of that canvas then the coordinate space of its children expands again so the rendering is then to an object with 16 times as many pixels as the top level resolution.

Now I don't want that high a resolution so the solution seems to be to only call SetHighDensity( true ) on the parent canvas as this scaling is inherited by the child canvas and my final target then has only four times as many pixels.
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Strange SetHighDensity() behaviour

Post by ColinP »

Just to confirm that setting the parent canvas to hi-res, the children's canvases to low-res, doubling the children's xywh and rendering at 2 * w by 2 * h did the trick...

CorrectCanvasScaling.png
CorrectCanvasScaling.png (64.19 KiB) Viewed 6098 times
User avatar
honki-bobo
Posts: 310
Joined: Sat Nov 09, 2019 1:18 pm

Re: Strange SetHighDensity() behaviour

Post by honki-bobo »

This is very insightful. Thank you, Colin, for your efforts and persistence on solving this problem and, of course, for sharing your insights.
Image
Monkey Business Audio
Modules - Music - Twitter - YouTube
ColinP
Posts: 1000
Joined: Mon Aug 03, 2020 7:46 pm

Re: Strange SetHighDensity() behaviour

Post by ColinP »

I do hope this is helpful to somebody and thanks Martin, but slightly premature it seems...

This has been quite a difficult thing to sort out so I went back and checked through all my code, just to be methodical as I really do not want weird bugs lurking to come back and bite me again.

So although I have the shadow/fx hierarchy pinned down I've discovered that the original problem I described still applies.

This is to do with the component canvases rather than the shadow/fx canvases. The component canvases parent is the VoltageModule this object yet when I call SetHighDensity() on these canvases (to save having to do it manually in the property pane) the following workaround is still required to correctly set the canvases to hi-res...

canvasX = canvas.GetX();
canvasY = canvas.GetY();
canvas.SetHighDensity( true );
canvas.SetPosition( canvasX + 1, canvasY );
canvas.SetPosition( canvasX, canvasY );

So SetHighDensity() is still making a mess of things.

When you normally place a canvas on the panel using VMD's design pane and check the High Pixel Density option the generated code calls SetHighDensity() BEFORE SetPosition() and everything seems ok.

But this is because x and y are zero straight out of the constructor and zero divided by two is still zero and the problem doesn't show.

But if you call SetHighDensity() AFTER SetPosition() the canvas displays in the wrong place. The extenal coordinates returned by GetX() and Get() look right but an internal mirror of the values gets halved. And when you call SetPosition() to correct this the caching ignores the call because it thinks you are just setting the values to what they already are even though the internal values are wrong.

So calling SetHighDensity( true ) on a canvas who's parent isn't a canvas when x and y are non-zero appears to make it incorrectly apply the expanded coordinate space of its chidren to its own position.
Post Reply

Return to “Module Designer”