Window resize "jitter" using wgpu-ris and winit - rust

I'm experimenting with some rust code that is a slight re-structuring of the code shown in the Learn WGPU tutorial, including the addition of a uniform transform to draw objects at fixed (pixel) locations and sizes.
I notice that when I resize my window, that my boxes stretch, squash, and jitter drastically.
The stretching/warping follows the magnitude and direction of the delta, and once the resize is completed the boxes always settle at their correct position and size
GifCam's frame diff view shows the extent of the stretching during the move
To rule out any of my changes to the tutorial code, I cloned the repository, built and ran "tutorial6-uniforms", and it shows the same behavior with resizes stretching and squashing the shape as they occur.
Adding println's to my resize and render functions (and disabling redraws on MainEventsCleared) shows even pairs of resize events and redraws
redraw called PhysicalSize { width: 800, height: 600 } // initial draw
resize called PhysicalSize { width: 799, height: 600 } // resize begins
redraw called PhysicalSize { width: 799, height: 600 }
resize called PhysicalSize { width: 774, height: 589 }
redraw called PhysicalSize { width: 774, height: 589 }
This all makes me believe that there is something going on behind the scenes, where perhaps the frame is stretched first to match the window's new size before the redraw is completed? Is there a way to prevent this? When there are a lot of elements on the screen, especially text, this effect becomes pretty jarring. Not to mention most application's do not suffer from this, so it looks unprofessional and ugly.
Here is my event loop, I've omitted other code to keep my post size down but will add more if it helps troubleshoot the issue, or refer to the code here from the tutorial.
event_loop.run(move |event, _, control_flow| {
match event {
Event::WindowEvent {
ref event,
window_id,
} if window_id == window.id() => {
match event {
// ...
WindowEvent::Resized(physical_size) => {
renderer.resize(*physical_size);
},
_ => {}
}
},
Event::RedrawRequested(_) => {
match renderer.render() {
Ok(_) => {},
Err(e) => eprintln!("{:?}", e),
}
},
// no change in observed resizing behavior with or without
/*Event::MainEventsCleared => {
window.request_redraw();
},*/
// ...
_ => {}
}
})

I believe this is a winit issue, where it both sets the view to be automatically resized and also not giving the app chance to redraw before OS redraws the resized window. I haven't found a good workaround, so I guess the only path is for someone to dive in to winit code.

Changing the surface's presentation mode from Fifo to Immediate prevents distortion while resizing (on Windows 10).
// surface: wgpu::Surface, device: wgpu::Device
let config = wgpu::SurfaceConfiguration {
...
present_mode: wgpu::PresentMode::Immediate,
...
};
surface.configure(&device, &config);

Related

Using Konvajs, I need to drag an object that is under another object WITHOUT bringing it to the top

Using Konvajs, I need to drag an object that is under another object WITHOUT bringing the bottom object to the top, the top object is NOT draggable.
Would appreciate any help, thank you.
Needing to detect clicks in stacked objects is a common requirement. Fortunately Konva makes this easy. Every shape will be 'listening' for events by default. So mouse over, mouse exit, click, double click, and their touch counterparts are all available to us (see docs).
In your case you need to make the shape that is higher up the stack stop listening so that one further down can hear it.
In the example below I am using the shape.listening(value) property which controls whether the shape is listening for events (true value) or not (false value).
Looking at the snippet, if we click the green spot (where the rectangles overlap), with the upper shape set listening(false) events pass through it to the next shape in the stack and we are informed that the click was on the lower shape. With the upper shape listening(true) the upper shape receives and eats the events.
I am also using an efficient approach to event detection, which is to listen for events on the layer rather than setting listeners per shape. In the case of a few shapes on the layer, using event listeners per shape is not going to be an issue, but when you have hundreds of shapes on the layer it will affect performance.
Of course delegating the listener to the layer leaves a requirement to recognise which shape was clicked. To do this I could have used logic to match on the shape names.
// when the user starts to drag shape...
layer.on('click', function(evt) {
if (evt.target.hasName('upperRect')) {
alert('Click on upperRect');
}
if (evt.target.hasName('lowerRect')) {
alert('Click on upperRect');
}
...
})
but since all I need to do here is show the name of the clicked shape, I chose to simply use
// when the user starts to drag shape...
layer.on('click', function(evt) {
showEvent('Click on ' + evt.target.name());
})
Finally it is worth noting that another approach to getting the clicked shape is to use the layer.getIntersection() method which requires a point parameter and will return the first shape (not a list of shapes!) that is on the layer at that point.
layer.on("click", function() {
var target = layer.getIntersection(stage.getPointerPosition());
if (target) {
alert('clicked on ' + target.name());
}
});
Bonus note: layer.on('click') does not detect clicks on the empty part of the layer.
// Set up a stage
stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
// add a layer to draw on
layer = new Konva.Layer(),
// make the lower rect
rectLower = new Konva.Rect({
name: 'Lower rect',
x: 40,
y: 20,
width: 100,
height: 40,
stroke: 'cyan',
fill: 'cyan',
fillEnabled: true
}),
// Make the upper rect
rectUpper = rectLower.clone({
name: 'Upper rect',
stroke: 'magenta',
fill: 'magenta',
x: 0,
y: 0
}),
// add a click target for user
spot = new Konva.Circle({
x: 80,
y: 30,
fill: 'lime',
radius: 10,
listening: false
});
// Add the layer to the stage and shapes to layer
stage.add(layer);
layer.add(rectLower, rectUpper, spot)
// when the user clicks a shape...
layer.on('click', function(evt) {
showEvent('Click on ' + evt.target.name());
})
// Start / stop listening for events on to rect when checkbox changes.
$('#upperListening').on('change', function(){
switch ($(this).is(':checked')){
case true:
rectUpper.listening(true);
showEvent('Upper rect <b>is</b> listening...');
break;
case false:
rectUpper.listening(false);
showEvent('Upper rect <b>is not</b> listening...');
break;
}
// Important - if we change lsitening shapes we have to refresh hit checking list.
layer.drawHit();
})
stage.draw();
function showEvent(msg){
$('#info').html(msg)
}
body {
margin: 10;
padding: 10;
overflow: hidden;
background-color: #f0f0f0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#^3/konva.min.js"></script>
<p>Click the green dot. Watch the events. Click the tickbox to stop upper rect catching events.</p>
<p><input type='checkbox' id='upperListening' checked='checked' ><label for='upperListening'>Upper is listening</label>
<p id='info'>Events show here</p>
<div id="container"></div>

Phaser 3. Change dimensions of the game during runtime

Hi please help me to find out how trully responsive game can be created with Phaser3.
Respnsiveness is critical because game (representation layer of Blockly workspace) should be able to be exapanded to larger portion on screen and shrinked back many times during the session.
The question is How I can change dimentions of the game in runtime?
--- edited ---
It turns out there is pure css solution, canvas can be ajusted with css zoom property. In browser works well (no noticeable effect on performance), in cordova app (android) works too.
Here is Richard Davey's answer if css zoom can break things:
I've never actually tried it to be honest. Give it a go and see what
happens! It might break input, or it may carry on working. That (and
possibly the Scale Manager) are the only things it would break,
though, nothing else is likely to care much.
// is size of html element size that needed to fit
let props = { w: 1195, h: 612, elementId: 'myGame' };
// need to know game fixed size
let gameW = 1000, gameH = 750;
// detect zoom ratio from width or height
let isScaleWidth = gameW / gameH > props.w / props.h ? true : false;
// find zoom ratio
let zoom = isScaleWidth ? props.w / gameW : props.h / gameH;
// get DOM element, props.elementId is parent prop from Phaser game config
let el = document.getElementById(props.elementId);
// set css zoom of canvas element
el.style.zoom = zoom;
Resize the renderer as you're doing, but you also need to update the world bounds, as well as possibly the camera's bounds.
// the x,y, width and height of the games world
// the true, true, true, true, is setting which side of the world bounding box
// to check for collisions
this.physics.world.setBounds(x, y, width, height, true, true, true, true);
// setting the camera bound will set where the camera can scroll to
// I use the 'main' camera here fro simplicity, use whichever camera you use
this.cameras.main.setBounds(0, 0, width, height);
and that's how you can set the world boundary dynamically.
window.addEventListener('resize', () => {
game.resize(window.innerWidth, window.innerHeight);
});
There is some builtin support for resizing that can be configured in the Game Config. Check out the ScaleManager options. You have a number of options you can specify in the scale property, based on your needs.
I ended up using the following:
var config = {
type: Phaser.AUTO,
parent: 'game',
width: 1280, // initial width that determines the scaled size
height: 690,
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT ,
autoCenter: Phaser.Scale.CENTER_BOTH
},
physics: {
default: 'arcade',
arcade: {
gravity: {y: 0, x: 0},
debug: true
}
},
scene: {
key: 'main',
preload: preload,
create: this.create,
update: this.update
},
banner: false,
};
Just in case anybody else still has this problem, I found that just resizing the game didn't work for me and physics didn't do anything in my case.
To get it to work I needed to resize the game and also the scene's viewport (you can get the scene via the scenemanager which is a property of the game):
game.resize(width,height)
scene.cameras.main.setViewport(0,0,width,height)
For Phaser 3 the resize of the game now live inside the scale like this:
window.addEventListener('resize', () => {
game.scale.resize(window.innerWidth, window.innerHeight);
});
But if you need the entire game scale up and down only need this config:
const gameConfig: Phaser.Types.Core.GameConfig = {
...
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT,
},
...
};
export const game = new Phaser.Game(gameConfig);
Take a look at this article.
It explains how to dynamically resize the canvas while maintaining game ratio.
Note: All the code below is from the link above. I did not right any of this, it is sourced from the article linked above, but I am posting it here in case the link breaks in the future.
It uses CSS to center the canvas:
canvas{
display:block;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
It listens to the window 'resize' event and calls a function to resize the game.
Listening to the event:
window.onload = function(){
var gameConfig = {
//config here
};
var game = new Phaser.Game(gameConfig);
resize();
window.addEventListener("resize", resize, false);
}
Resizing the game:
function resize() {
var canvas = document.querySelector("canvas");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = game.config.width / game.config.height;
if(windowRatio < gameRatio){
canvas.style.width = windowWidth + "px";
canvas.style.height = (windowWidth / gameRatio) + "px";
}
else{
canvas.style.width = (windowHeight * gameRatio) + "px";
canvas.style.height = windowHeight + "px";
}
}
I have used and modified this code in my phaser 3 project and it works great.
If you want to see other ways to resize dynamically check here
Put this in your create function:
const resize = ()=>{
game.scale.resize(window.innerWidth, window.innerHeight)
}
window.addEventListener("resize", resize, false);
Important! Make sure you have phaser 3.16+
Or else this won't work!!!
Use the game.resize function:
// when the page is loaded, create our game instance
window.onload = () => {
var game = new Game(config);
game.resize(400,700);
};
Note this only changes the canvas size, nothing else.

QML ignores width and height when setting anchors

I'm trying to understand how anchors work in QML (Qt Quick 2.0). I've got a simple Item like this:
AddButton.qml:
Item {
Button {
text: "ADD"
width: 100
height: 50
}
}
Which I add to the main QML file like this:
main.qml:
Item {
id: root
width: 800
height: 600
AddButton {
id: addButton
}
}
This works fine. However, as soon as I try to put the button in the bottom right corner using anchors, the button disappears:
main.qml:
Item {
.....
AddButton {
id: addButton
anchors.right: parent.right
anchors.bottom: parent.bottom
}
}
It only comes back if I set a width and height at the main QML file level:
main.qml:
Item {
.....
AddButton {
id: addButton
width: 100
height: 50
anchors.right: parent.right
anchors.bottom: parent.bottom
}
}
So I'm wondering, why does the button disappear when I set the anchors? And is there any way to make it work without setting the width and height in the main QML file (basically to make it use whatever size is set in AddButton.qml?)
The problem is that the encapsulating Item has not an explicit width height. In this case the engine refers to the "natural" witdh/height, i.e. the implicitWidth/implicitHeight properties. Such properties happen to be zero in most cases, even in this specific case. Hence, your custom type has zero dimension.
Therefore the AddButton.anchors.bottom is in fact at the very top of the encapsulated Button, which in turn protrudes the encapsulating Item
There are two things about this:
You don't need to encapsulate the Button with an Item unless you want to hide the internals of the Button.
If the latter is your desire, try this:
Item {
width: 100 //give the object a dimension!
height: 50
Button {
text: "ADD"
anchors.fill: parent
}
}
Now you can anchor it, and it won't be positionated somewhere else.

Displaying qTip hover on outer nodes of Cytoscape.js graph?

When hovering over the outer nodes of graph in Cytoscape.js, the qTip dialog is not displayed.
Is there anyway to display the qTip bubbles upon hovering on an outer node? I can have the qtip popup on nodes in the upper half but not much along the sides of the graph. Especially towards the bottom half.
cy.elements('node').qtip({
id: function() {
return this.data('id');
},
content: function() {
return this.data('name') + '<br /><em>$' + this.data('weight') + '</em>';
},
position: {
my: 'bottom right',
viewport: $(etl_cyto_div),
adjust: {
cyViewport: true,
method: 'flip none'
}
},
show: {
cyBgOnly: false
},
hide: {
cyBgOnly: false,
cyViewport: true,
delay: 100
},
style: {
classes: 'qtip-tipsy',
tip: {
width: 16,
height: 8
}
}
});
I suspect your options may be causing the issue. Because this extension is a relatively thin wrapper around qtip, you just use the qTip API and options.
Try leaving options.position.adjust.method default
Try a more permissive options.position.adjust.method; see qtip docs
adjust.cyViewport is expensive (especially on touch) and can be buggy depending on the version of qtip used.
Try all defaults and see if you can reproduce your issue with the events you want. If not, then the issue is due to the set options. If so, please file your example code in an issue

carouFredSel - set visible amount to actual amount of images - and still scroll?

I'm working with this scroller
http://coolcarousels.frebsite.nl/c/2/
I have this setup below.
My issue is I have it set to visible: 4 and I have 4 images, so it doesn't scroll. If I set it to visible: 3 then it works as expected. But I want to show all 4 images in one screen when you open the browser full width on a 1920px wide resolution. So the issue seems to be. If I set visible to the amount of images I have then it stops working.
Is there a way to have all 4 images on screen at one time then still scroll through them?
$(function() {
$('#carousel').carouFredSel({
width: '100%',
align: 'left',
items: {
visible: 4,
start: 0,
},
scroll: {
items: 1,
queue : true,
fx: "scroll",
easing: "swing",
duration: 1000,
timeoutDuration: 3000
},
prev: '.prev',
next: '.next',
auto: {
easing: "quadratic",
button: '.play',
pauseOnEvent: 'resume',
pauseOnHover: true
}
}).find(".slide .plusNav").hover(
function() { $(this).find("div").slideDown(); },
function() { $(this).find("div").slideUp(); }
);
});
try this
items: {
minimum: 0,
},
I have resolved this issue by setting minimum to 0.
items: {
minimum: 0,
Actually, setting the minimum attribute to zero forces the scroll bar to be displayed always irrespective of number of items currently displayed.
This was required for me because, automatic enabling of scroll bars was not working on certain screen resolutions- I had to add 2 more items to make the scroll bar visible which was not the expected behavior.
As a work around, I set minimum: 0 - it resolved the issue.
I was able to do this by editing the source :/
If you comment out this lines 554 & 556 in jquery.carouFredSel-6.2.0.js like this...
// not enough items
var minimum = (is_number(opts.items.minimum)) ? opts.items.minimum : opts.items.visible + 1;
if (minimum > itms.total)
{
// e.stopImmediatePropagation();
// return debug(conf, 'Not enough items ('+itms.total+' total, '+minimum+' needed): Not scrolling.');
}
...it worked for me.
Access the wrapper and set its height (assuming all children have the same height):
var carousel = [your_carousel],
carousel_wrapper = carousel.parent();
carousel_wrapper.height(function(){
return (carousel.children('[child_selector]').length) * [child_height];
});
The thing here is, there will be a weird behavior when the carousel animates. This is because the maximum height was done ((n-1) * child_height) intentionally as a mask, along with an overflow: hidden.
Another option would be to duplicate one of the children, but that isn't semantic.

Resources