Welcome to my talk. Thank you for attending it. I'm Vodislav. I'm a tech and render
timlet and in my free time. I'm working as a launcher on zero AD. Zero AD is a free and open source
cross-platform game. If UI games like Hedou from Paris Empire Earth or StarCraft, then you
might find zero AD interesting as well. We use a cross-platform game, so it works only in
the Mac OS and Windows. It works on different architectures. We use our own custom
engine. It's called Pyogenesis. It's written in C++, so we have some technologies
under the hood. About rendering, we have abstraction rendering interface. It helps us to
have multiple backends. Open jail, Vulcan, and dummy. The latch-1, 4, test, and CPU performance
checks. Also, we use MotonViki to be able to run Vulcan on Mac OS. It converts Vulcan API
calls to a metal API calls. So it's how it looks like. We were trying to design our rendering
interface as close as possible to Vulcan API, but we have still limitations because we support
Open jail. For example, we still have a plot text-trained plot buffer, so no ringbuffers or
a plot buffer or a new form buffer on top. Also, we have been using your sources directly
in the device command context, so you might see the texture and set uniform. We are binding
slot. So we have several limitations. So let's talk about Vulcan. We have added the Vulcan
in 2020-23, and the first version of the game with Vulcan was released in 2025. It was
released in 2007. By the way, we are currently preparing the next version. It released
in 2008. We are hoping to release it soon. So that's how a timeline looks like. You might
see that in the submit timeline, we have re-ordered our uploads, because uploads are not
allowed with render passers approach, because we don't support the name rendering for all
platforms, but we need to support Vulcan for all platforms. So we split them into two
decay command buffers, prepange and mine. Why we have that, because we still have some legacy
components. For example, user interface, where a component might load a plot and use texture
right before the draw goal. So it might just get a texture path right before a draw goal
inside render pass. So we have to upload them separately before the main decay command
buffer. And because of that, we don't allow to upload the same texture or the same buffer,
let's put times during the single render pass. So let's talk about obstacles we had.
First one, Vulcan support detection. We've tried to do that with SDL. Nothing really hard
here, just trying to load library and get instant support. But it was crashing for some
drivers, because if you are trying to load Vulcan and then Vulcan jail, some drivers crashing.
So we just disable that. Unfortunately, we don't have any suitable solution for us.
So now users can switch in options or becans. Device detection. We have multiple physical
devices. And we have to choose one. Because we have OpenGL, we had a problem that OpenGL
sometimes uses integrated GPU instead of discrete one. So we thought that it would be
great to sort all devices. So first we sort by type. GPU, discrete, GPU integrated,
CPU and virtual. Then by device.com memory. And then by initial order. So the first
error was using device.com memory, because some drivers report like Wapers have more memory
than native cuts. The second problem was this one. So that's the initial order reported
by driver. We were selecting the second one, they discrete. But it wasn't working, because
it failed during installation. So we have to remove using type sorting as well. So we
always using the 0, the first one from the list to report it by driver. So the problem
that WCAF is equal to devices might be listed even if they're not going to work. So another problem,
texture comparison, we see some platforms. Don't support all BC formats. And because of
that texture comparison, BC might be false. So because of that, we were switching devices.
That's not correct, because actually those platforms were supporting all needed for us BC formats.
So instead of checking this property, we just checking all BC formats individually. Out of memory.
In our game, we have multiple options. True control quality. We have shadow quality,
it's a texture quality. We can enable water refraction, water reflection and so on. And it
consumes memory. And we need to handle that situation. So when that might occur, usually when
we allocate resources. So okay, memory creates something. It's easy to fall back on that stack,
because we can return the error in the color. But there are more rare situation when in
can fail in a choir and Q present and Q segment and wait for fences. In those situations,
we unfortunately can handle. So currently in those situations we just crush.
Because it's another way, it's a low level. But information about quality is a high level.
So low level can't access the high level. So currently we just crush. No proper solution for that.
We use in a low command mirror locator. So it's another helping for us. It's really
simplified. Some could relate to memory locations. But at the same time, if we freeze memory,
it doesn't go to GPU immediately, because all command mirror locator uses intermediate buffers.
Bigger buffers to allocate smaller buffers from them. We're trying to use VKX memory budget,
but it doesn't really help in those situations. The only solution we have is to use some ratio,
like 80% of total available memory and another, like, should be on operation system side.
GPU scanning artifacts. Skinning is a process of applying skeleton animation to a model,
to a mesh. And how the frames should look like. It's a regular frame from our game.
And that's how it looks like with the bug. And another one. So it might look like a driver bug,
or like another synchronization problem. But actually, it was pretty simple. It was just
incorrectly selected data. We didn't invalidate the flag when user was switching from
CPU scanning to GPU scanning. But it was looking like a serious bug, which weren't able to
produce. We are collecting GPU statistics to be able to optimize our game to know, like,
corner cases. And so, for example, for OpenGL, we have the following reported names for the same
game version, the same GPU, the same platform. We've welcome it's much better.
The same game version, the same GPU, but all supported platforms. It's better. It's simpler to
parts. It still has some problems, like it includes additional information and we need to
remove trained markets. So forth, but it's much simpler. There are some helpers that might help
you to distinguish different GPUs. For example, device ID, but it's not enough because it might
be equal for different GPUs. Device UID is possible if it presents, because in some cases, it might
be just zeroed. So it's not really helpful for that case. So the final solution, we just
parse device name, but it's much simpler than for OpenGL. You know, debugging, that's the most
interesting part for us. We have a lot of players, but not many of them have programming skills,
or might build the game, debug the game. So we have to introduce some helpers, configuration options
to be able to retrieve some useful information for us to debug the game. For example, we have
Android. Helpers. So if we can teach someone how to make a capture, or maybe not still
Android. We can enable debug labels. We can enable debug scope labels. So each resource in our
engine is marked with a constant name. So we can distinguish them. Also, we can enable messages.
If driver has something to report for us and we have debug context, it enables different features,
including validation layers, if they are present in the platform, in the user platform.
Also, currently, by default, we are working in Windows, so we are using discriminating indexing.
But there is a quarter of a case. When you enable validation without GPU assistance,
some validation layer might complain, because they don't really know when a resource will be accessed.
So, for example, in this single descriptor set, you might have a frame buffer target and some
like texture sample. And it will be evaluation for it. So in real case, it's not a problem.
So we need to be able to disable that. Also, we would like to have in the future an option to
choose GPU, not only backend, but also GPU for all cannot list. But can't even have.
So we don't have that. So we use configuration option. And the last one helps us debug
different situation problems and driver issues. We able to insert debug buyer. It's a barrier
from all stages to all stages, from all excess mask to all excess mask. So it's really hard execution
and memory barrier. Also, we have wait, wait for different stages to work present before and
after. Again, to artifacts. In the beginning of 2025, I found visual artifacts in demand new
in on Raspberry Pi 4, with metadata 24. So on the left, with bug on the right, without bug.
After investigation, after I had to enable mentioned debug buyers and for stages and masks,
didn't help. Try to locate device, wait, idle, didn't help. Don't help. Don't the thing is
that was working is to split VKQ submit on two. We have the similar four relation between them.
So the code was looking like this. And actually, it was a driver bug and thanks to Sam,
he's colleagues. But my device has a talk today about Raspberry Pi. It was, in my opinion,
the first fix I have seen for drivers and for vendor or driver author. So very much thanks to them.
The main conclusion is that the one usual application uses the Vulcan API.
The more likely a driver error will occur. So if you are doing like a simple quad rendering,
then the chance that you will have an error, an artifact or something like that, is way, way
low. But if you are doing something specific, for example, like we do, like I mentioned on the timeline,
we split our device common context on two. So we have prepared to the K common buffer and
main common buffer. And we have a synchronization between them. That already isn't so usual behavior
for some platform, so at least. Because usual recommendation is to avoid multiple case
of meets or multiple common buffer. So use only one, but not for all. And another thing that
really helped in that situation that I was able to reproduce that back myself on my Raspberry Pi 4.
And after a few evenings of debugging, I finally got that I would be a driver back and I made an
issue for the method. And it was fixed really fast. I am really glad about that. And the last thing,
GPU performance measurements, because we have players with different hardware from low to high,
we need to be able to measure how expensive our frame is. What do we need to optimize?
Usually we prefer using tools from vendors. So if we are debugging locally, we are trying to use
tools that are provided by vendors if available. Not all patrons have enough tools.
Else, we fall back to timestamp queries. It has limitations in terms of how it measures data,
because when you insert a timestamp query, you pass when you want to capture. So for example,
if you have to overlapping jobs, then you can really distinguish only single one.
And actually using timestamps might be affected. For example, it will be affected by other processes,
because they are using your GPU as well. They can be affected by temperature. It's not so
relevant to discrete GPUs. It's mostly for mobile GPUs or like energy conserving.
And sometimes you might have measurements that you get slower results than the previous one.
So you are sure that you're using a better code. That's happening because a GPU driver
sees the code or sees the real data. And it thinks that it might make sense to decrease the
GPU frequency. We are still on 60 FPS, but we are using low less energy. So better code doesn't mean
that it will be better performance. But in terms of energy consumption, it will be better anyway.
So that was the last one. Thank you very much.
I need tips about measurements for GPU performance, right?
It's really not a simple question, because usually you need to take a look at each platform independently.
For example, some wonders provide special functions, which can fix your GPU frequency.
In that case, or for example, not GPU frequency, but from energy bands to performance mode,
where it will be might be fixed. Not will, but might. Also some wonders too,
help you to get more metrics. For example, those, which are not available in Vulcan and P.
In the frame statistics somewhere else, or we are times some queries or something else.
So each platform independently and using window tools.
Looking back over the last three years now, was it best interest, which is the one kind
and on putting on the effort in it, but if you think if you have to do it again, it's better to do it again.
What's it worth to switch the welcome? I had the talking 2020-24, and yes, I said that for some platforms,
we get up to 300% improved of performance. The best improved was for macOS,
because we were using OpenGL there, and their OpenGL implementation is far from ideal.
We get 10% to 300% improvement, and we get more stable performance, less fluctuations during multiple frames.
So yes, it's worth it. Another reason why we switched to Vulcan, because I was really interested in Vulcan,
so I have internal motivation to add it. If you don't have it or you don't have time,
then it might be worth it to take a look at some other libraries that might be implemented in your
application. We were limited, because we still support OpenGL 2.1, it has limitations,
so more than a library is like Libangle or BGFX, not really useful for us yet.
So the short answer, yes, it was worth it. Yes?
You mean like choosing different GPUs, how does it affect performance?
Do we notice any difference for similar GPUs, or for the same?
I think no, because we don't have much GPUs available for us yet, so generally we have like developer machines,
they are pretty limited, so we don't have many of them, maybe 10 to 20, not more,
so we don't have much variety. From users we usually get pretty rough statistics,
I mean from their reports, because they're reporting for us voluntarily, so we are not
collection any data without their consent, so usually the most useful that we usually have,
it's like FPS, or FnChime or something like that.
Yes, in terms of supporting platforms, because for example, we still support OpenGL 2.1,
and for, yeah, the question. Do you have, do you notice any difference between
commercial implementation for engines and the open source one? So, a variety of support, like
commercial usually trying to avoid platforms where we don't have many people,
where we don't have money or something like that. In open source, it's vice versa,
where we have many people, we have many support, so in that case we need to support
much wider amount of hardware and much older hardware. It's, like, it's really interesting, it's
also, like, two different areas to investigate, so it's not worse, not better, but different,
so for me, it's both areas of those are interesting.
Yep. So, what's the problem on the Raspberry Pi?
I can show you a Ticket and Messer. What's the problem with Raspberry Pi?
If not mistaken, it was when two different common buffers there, internal state,
wasn't synchronized about some painted painting barriers, but I might show you more detailed.
So, that's all?
You mentioned you used the Bibles texture, so when you help to debug something, do you switch to
known Bibles? As far as I know, with the renderer, it's a little bit difficult to see,
to debug if you enable Bibles texture.
Yes, it's possible we're trying to do it trying, are we trying to disable Bibles?
Disgusting, if we have a bug. Yes, it's the first step, so if we have a bug, we're trying to
disable, just keep it in the same first. If it's still producing, then we are working on that.
If not, then we're trying to instigate the discreeting, but if I'm not mistaken,
we had only one bug related to discreeting, so most of our bugs are producing on both cases.
Because the client code, which is calling our backends, is absolutely the same.
Just building your sources is different.