UWP SwapChainPanel Contents Blurry/Scaled - c#

I am using skia to do drawing on a SwapChainPanel (via ANGLE), and all is going well. However, the one issue that I am having is that the content is being scaled on high DPI screens.
I have a hi-res (x2) and a normal (x1) screen on my machine here. The contents of the panel look great on the 1x display, but when I drag it over, the window resizes and scales the contents. How do I tell the panel not to scale the contents, but to rather let me adjust my side?
My current solution is to create my renderbuffer at the desired scale (eg: panel.Width * 2) and then use the RenderTransform property and set the ScaleTransform (eg: 0.5).
The content now appears smooth and crisp, but now the layout is affected. Since the render transform has "resized" the panel.
What is the correct way to do this?
EDIT
This is what I am doing to create the buffer:
https://github.com/mono/SkiaSharp/blob/master/source/SkiaSharp.Views/SkiaSharp.Views.UWP/AngleSwapChainPanel.cs#L199-L285

You essentially have to set scale on DXGISwapChain to be inverse of swap chain panel composition scale using IDXGISwapChain2.SetMatrixTransform method:
SwapChainPanel panel = ...;
IDXGISwapChain2 swapChain = ...;
DXGI_MATRIX_3X2_F scale;
inverseScale._11 = 1.0f / swapChainPanel.CompositionScaleX;
inverseScale._22 = 1.0f / swapChainPanel.CompositionScaleY;
var hr = swapChain.SetMatrixTransform(ref scale);
if (hr < 0)
Marshal.ThrowExceptionForHR(hr);
I don't know exactly how to obtain the swapchain object reference through the library you're using, though.

Related

UWP: Using ScaleTransform on WebView to scale/zoom contents

I am trying to use ScaleTransform to shrink a WebView, while scaling (zooming) the contents properly. i.e., If I scale the WebView to 50% of the size using ScaleTransform, the contents should likewise scale to 50% zoom level, so you can see the same contents as before, just shrunk to fit the new size of the WebView.
I know it's possible, because the AdControl does properly scale its contents when you apply a ScaleTransform, and the AdControl internally uses a WebView control to display the ad.
Example code that works with an AdControl:
double scale = 0.75;
ad.RenderTransform = new TransformGroup() {
Children = {
new ScaleTransform() { ScaleX = scale, ScaleY = scale }
}
};
If that same code is applied to a WebView control, it does shrink the size of the control, but the contents are clipped instead of shrunk.
Properly scaling the control's contents should not require the use of JavaScript injection or any other "kludgy" approaches, since it is highly doubtful that Microsoft's AdControl would use such techniques. It should all be able to be handled in the XAML or code-behind (C#).
Thanks for the help!

Redraw panel contents after ClientSizeChanged

So my application runs in fixed size window and in full screen. The problem I'm facing is how to properly scale the current contents of the panel (which depend on the application use) when the window is resized. This is my current code:
private void Form1_ClientSizeChanged(object sender, EventArgs e)
{
System.Drawing.Drawing2D.Matrix transformMatrix = new System.Drawing.Drawing2D.Matrix();
float px = panel2.Width;
float py = panel2.Height;
panel2.Width = this.Width / 2;
panel2.Height = panel2.Width;
panel2.Location = new Point(this.Width - panel2.Width - 30, 30);
transformMatrix.Scale(panel2.Width / px, panel2.Height / py);
panel2.Region.Transform(transformMatrix);
//Rest of the code
}
But the drawn content doesn't scale, and if I use Invalidate() or Refresh() the drawn content gets cleared (the panel is redrawn empty). What am I missing?
.NET doesn't remember what's drawn on the panel, as simple as that. As soon as anything invalidates the windows bitmap buffer (causing a WM_PAINT), it's going to be repainted again. So, you have to draw what you want to draw using the Paint event (or overriding OnPaint).
However, there is another way that might be easier to implement - don't paint into a Panel. Instead, paint into a PictureBox (or rather, a Bitmap assigned to the Image property of the PictureBox). The Bitmap will be reused when invalidating (and redrawing) the picture box, so nothing will be lost. By using PictureBox.ScaleMode, you can define how you want the picture box to scale the bitmap, and it will do so as well as it can.
In any case, transforming the Region property doesn't do anything useful - you're simply changing the region, not doing anything to the drawing itself. To use 2D transformation matrices, you want to apply them on a Graphics object during the drawing (in Paint handler or OnPaint override) - drawing anything on the Graphics object will then transform everything you're trying to draw, which in your case means scaling the painting.
So you have to decide: do you want to just scale a stored bitmap with the painted image, or do you want to redraw it all from scratch (which also means you can pick any level of detail you can provide)?
I think that you're mistaking what the Region property is meant for. According to the MSDN docs (empasis mine, replace 'window' with 'control' when reading):
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region. The coordinates of a control's region are relative to the upper-left corner of the control, not the client area of the control.
All that you're doing is changing the region that the OS will allow painting, which explains why you're not seeing anything. I think that you should be resizing the control when the form is resized, either through Anchor, or through my preference of Dock with several controls, or a panel like TableLayoutPanel where it will handle scaling and relative sizing for you.
Thank you for your answers, but I wrote my own function and logic that serves the purpose for this application. Basically the function checks for the state of the application variables, and calls the appropriate function that originally drew the content, and since those functions use the panel width and height as arguments they properly scale the drawn content and retain the drawing composition.
P.S. I'll accept Luaan's answers since it offers a valid alternative and is complete.

Render Large Canvases in UserControl

I've been having trouble trying to implement this for a couple of days now. I've searched extensively on similar questions in regards to what I'm trying to do but I haven't come across a question that helps my issues directly.
Basically I'm rendering tiles onto a grid on my UserControl class. This is for my Tile Engine based world editor I'm developing. Here is a screenshot of an open world document and some tiles brushed on.
Initially, I was going to use a Bitmap in my control that would be the world's preview canvas. Using a brush tool for example, when you move your mouse and have the left button down, it sets the nearest tile beneath your cursor to the brush's tile, and paints it on the layer bitmap. The control's OnPaint method is overridden to where the layer bitmap is draw with respect to the paint event's clipping rectangle.
The issue with this method is that when dealing with large worlds, the bitmap will be extremely large. I need this application to be versatile with world sizes, and it's quite obvious there are performance issues when rendering large bitmaps onto the control each time it's invalidated.
Currently, I'm drawing the tiles onto the control directly in my control's overridden OnPaint event. This is great because it doesn't require a lot of memory. For example, a (1000, 1000) world at (20, 20) per tile (total canvas size is (20000, 20000)) runs at about 18mb of memory for the whole application. While not memory intensive, it's pretty processor intensive because every time the control is invalidated it iterates through every tile in the viewport. This produces a very annoying flicker.
What I want to accomplish is a way to meet in the middle as far as memory usage and performance. Essentially double buffer the world so that there isn't flickering when the control is redrawn (form resize, focus and blur, scrolling, etc). Take Photoshop for example - how does it render the open document when it overflows the container viewport?
For reference, here's my control's OnPaint override that is using the direct draw method mentioned above.
getRenderBounds returns a rectangle relative to PaintEventArgs.ClipRectangle that is used to render visible tiles, instead of looping through all the tiles in the world and checking if it's visible.
protected override void OnPaint(PaintEventArgs e)
{
WorldSettings settings = worldSettings();
Rectangle bounds = getRenderBounds(e.ClipRectangle),
drawLocation = new Rectangle(Point.Empty, settings.TileSize);
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.None;
e.Graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.None;
e.Graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
for (int x = bounds.X; x < bounds.Width; x++)
{
for (int y = bounds.Y; y < bounds.Height; y++)
{
if (!inWorld(x, y))
continue;
Tile tile = getTile(x, y);
if (tile == null)
continue;
drawLocation.X = x * settings.TileSize.Width;
drawLocation.Y = y * settings.TileSize.Height;
e.Graphics.DrawImage(img,
drawLocation,
tileRectangle,
GraphicsUnit.Pixel);
}
}
}
Just comment if you need some more context from my code.
The trick is to not use a big bitmap for this at all. You only need a bitmap covering the visible area. Then you draw whatever is visible.
To achieve this you will need to maintain the data separately from the bitmap. This can be a simple array or an array/list with a simple class holding information for each block such as world position.
When your block is within the visible area then you draw it. You may or may not have to iterate through the whole array, but that isn't really a problem (you can also calculate the visible array on a separate thread). You can also make the function more intelligent by creating region indexes so you don't iterate all blocks.
To add a new block to the array, calculate it's canvas position to world coordinates, add it and then render the array again (or the area where the block is drawn).
This is how controls with scrollable areas are drawn by the system too.
Enable double-buffering will keep it clear and flicker-less.
In this case I would also use a panel with separate scroll bars and calculate the scroll-bars' relative position.

RenderTransform without affecting children possibly Inverse

I'm resizing a canvas with touch events as follows:
e.Handled = true;
var transformation = MyCanvas.RenderTransform as MatrixTransform;
var matrix = transformation == null ? Matrix.Identity :transformation.Matrix;
matrix.ScaleAt(e.DeltaManipulation.Scale.X,
e.DeltaManipulation.Scale.Y,
e.ManipulationOrigin.X,
e.ManipulationOrigin.Y);
MyCanvas.RenderTransform = new MatrixTransform(matrix);
The canvas has several child canvasses. I don't want to resize them and in fact need them to go smaller. So looked at RenderTransform.Inverse but am not having any joy.
You can create a custom canvas by inheriting from Panel with
A new dependency property: NonInheritableScale
A binding between bind the transform's scale to the NonInhertiableScale property
overrides of the MeasureOverride() and ArrangeOverride() methods,
so that the take 1.0/NonInhertiableScale.X and 1.0/NonInhertiableScale.Y into account during the layout.
Here is an article on creating custom WPF panels that might help you (a search result, haven't read it).
EDIT after reading the comment below
In that case of a chart you might want to redraw the chart with different axis ranges. A RenderTransform might be not accurate enough and indeed you will have to scale back everything else (axis, labels, gridlines,...)
previous answer, still valid
You will have to iterate through the child canvases and scale them individually. As far as I know there is no build in support for what you want.
You will have to apply both the inverse scale transformation to negate the parent's resize and a scale transformation that will make them smaller.
Post the code you are using to get more detailed help and or feedback.

Going fullscreen without stretching in an XNA game

I've got a 2D game that I'm working on that is in 4:3 aspect ratio. When I switch it to fullscreen mode on my widescreen monitor it stretches. I tried using two viewports to give a black background to where the game shouldn't stretch to, but that left the game in the same size as before. I couldn't get it to fill the viewport that was supposed to hold the whole game.
How can I get it to go fullscreen without stretching and without me needing to modify every position and draw statement in the game? The code I'm using for the viewports is below.
// set the viewport to the whole screen
GraphicsDevice.Viewport = new Viewport
{
X = 0,
Y = 0,
Width = GraphicsDevice.PresentationParameters.BackBufferWidth,
Height = GraphicsDevice.PresentationParameters.BackBufferHeight,
MinDepth = 0,
MaxDepth = 1
};
// clear whole screen to black
GraphicsDevice.Clear(Color.Black);
// figure out the largest area that fits in this resolution at the desired aspect ratio
int width = GraphicsDevice.PresentationParameters.BackBufferWidth;
int height = (int)(width / targetAspectRatio + .5f);
if (height > GraphicsDevice.PresentationParameters.BackBufferHeight)
{
height = GraphicsDevice.PresentationParameters.BackBufferHeight;
width = (int)(height * targetAspectRatio + .5f);
}
//Console.WriteLine("Back: Width: {0}, Height: {0}", GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight);
//Console.WriteLine("Front: Width: {0}, Height: {1}", width, height);
// set up the new viewport centered in the backbuffer
GraphicsDevice.Viewport = new Viewport
{
X = GraphicsDevice.PresentationParameters.BackBufferWidth / 2 - width / 2,
Y = GraphicsDevice.PresentationParameters.BackBufferHeight / 2 - height / 2,
Width = width,
Height = height,
MinDepth = 0,
MaxDepth = 1
};
GraphicsDevice.Clear(Color.CornflowerBlue);
The image below shows what the screen looks like. The black on the sides is what I want (and is from the first viewport) and the second viewport is the game and the cornflower blue area. What I want is to get the game to scale to fill the cornflower blue area.
Use a viewport http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.viewport_members.aspx
As is also the case in commercial games, you should provide an option to the user that allows them to switch between 4:3 aspect and 16:9 aspect. You should be able to just modify the camera viewing ratio accordingly.
EDIT:
As far as I have seen, there are no games that 'auto-detect' the proper aspect ratio to use.
As has been pointed out, there are ways to make a good guess as to what the proper aspect ratio is. If XNA allows you to get at the current Windows user's screen settings data, you can determine an aspect ratio based off of the monitor resolution.
Once you have determined the monitor resolution of the user, you can best decide how to deal with it. At first, the best bet may be to just put black bars on the left/right side of the screen to allow full-screen with a 16:9 aspect ratio that is essentially still using the 4:3 artwork.
Eventually you could modify the game so that it changes the viewing port size when the aspect ratio is 16:9. This wouldn't require changing any art assets, just how they are being rendered.
First of all I'm assuming you're talking about XNA 4.0, which AFAIK there are breaking changes between XNA 3.x and XNA 4.0.
I'm relatively new at XNA, however it seems to me that your assets does not fit the size of the window. Let's say that your game are is 320x240 and your window is bigger e.g. 640x480.
Thus you can specify PreferredBuffer in order to scale up your application. So, tell to XNA you are going to use 320x240 by setting the following values;
_graphics.PreferredBackBufferWidth = 320;
_graphics.PreferredBackBufferHeight = 240;
Additionally you can start fullscreen mode by setting:
_graphics.IsFullScreen = true;
Also, you have to handle manually the how the items should change their size once the Window has changed their size.
Checkout my sample at.
https://github.com/hmadrigal/xnawp7/tree/master/XNASample02
(BTW, you can press F11 to switch between fullscreen and normal view)
Best regards,
Herber
I'm not sure if you can actually scale your view port like that. I understand what you're trying to do, but to do it you'd have to do the following.
Set your screen backbuffer width and height to the 16:9 resolution.
Program in the displacement so that objects didn't draw in those borders.
The thing is, all major games these days, if you play them on a 16:9 monitor and select a 4:3 resolution, will stretch to fit the screen. This isn't something you usually want to overcome. You either support many resolutions in your game, or you will get stretching when a user uses the wrong resolution for his or her screen type.
Usually, one sets up their game, and their textures to work based on the relative dimensions of the current viewport or backbuffer width and height. This way, regardless of the resolution inputted, the game scales to work with that width/height ratio.
It's a bit more work, but in the end, makes your game far more polished and compatible with a wide array of systems.
The only time this may not be done is if the app runs in a window (NOT fullscreen).

Categories