#[3D] Changing texture without having to rebuild vertex buffer?

241 messages · Page 1 of 1 (latest)

proud raven
#

I have a block object that can have animated side textures. I can think of 3 ways to do it:

A) I rebuild the vertex buffer every frame with texture data from sprite_get_uvs. Simple, but doesn't seem ideal if the geometry isn't otherwise changing.
B) Set all my animation frames to be on their own texture pages so that they'll all have the same uv data and I can just switch which frame I pass into vertex_submit(..., sprite_get_texture()). No rebuilding the vertex buffer, but otherwise this seems wildly inefficient.
C) Build the vertex buffer once, but set a uniform with the data from sprite_get_uvs, then manually look up the correct pixel data from the texture in a shader.

Are these basically my options? Option C seems like the best (most performant, though not simplest), but for whatever reason I'm a little hesitant to make a shader that does its own texture lookup

glad bear
#

D) Building out multiple vertex buffers for each and every possible frame, and render those.

#

Personally, I've gone with C in a project of mine, just because C works on multiple faces. The downside is that you need to ensure that all of the textures are on the same page. Or have a vertex buffer per texture page

#

C is most performant when you have a bunch of blocks batched together

#

But otherwise isn't any better than the rest

humble scarab
#

Does the whole block have to be a single vbo? Cause if not, couldn't you build a separate vbo for each face and then animate those "normally"?

glad bear
#

^ Usually when it comes to blocks in many games, their vertex buffer is composed of many multiple blocks, instead of a block per vbo

proud raven
proud raven
humble scarab
#

If each face is its own vbo, you can submit them with a separate texture each with no UV funnies.

proud raven
glad bear
glad bear
#

Meaning that the uvs would be 0 or 1

humble scarab
#

I'd just be 0/0, 1/0, 1/1, 0/1, yeah.

#

For each face, all the same.

proud raven
#

oh but that would require me putting each face on a separate texture page manually, right?

glad bear
#

It'll be on each sprite

#

But yes

proud raven
#

sorry, yes

humble scarab
#

Mm yeah.

glad bear
#

This would make it extremely optimal at rendering a bunch of blocks that are otherwise the same, with the same animations

#

Meaning that a worse case scenario, every block is different and its own vbo. Best case scenario, all blocks are the same and are one vbo.
Average case, you will have a couple blocks batched together

proud raven
#

(the sides of the blocks are animated to be faces so it's getting confusing for me, but that's a me problem)

humble scarab
#

I guess an important question would be what's the scope here? Do we have a few blocks or thousands of blocks?

proud raven
#

probably no greater than 400 blocks but not uncommon to have 80+

#

also is it not very inefficient to have a separate texture page for each animation frame? That's what I was trying to get at with my option B but my gut was that switching texture pages like that might kill performance

glad bear
#

Most modern hardware can handle a lot of texture swaps

#

You may have to only slightly worry for older weaker hardware

proud raven
#

good to know

proud raven
glad bear
#

Well a matrix transform is a batch break

proud raven
#

okay yeah that's what I figured

glad bear
#

But not too much, if a bunch of them are batched together

#

Since realistically the vbo themselves would be built at key points already

proud raven
#

in my case the blocks are entities that can move around, so I can't bake multiple into one VBO

glad bear
#

Okay well in that case

#

C is completely off the table then

#

I would lean more towards a mix of B and D

#

Since you've got matrix transforms anyway, the vbos themselves are going to benefit a bit more if they are at least prebuilt

#

Except you could do this without separate textures as well

#

And just have all of the block faces built + prepared

#

That's about the most optimial I'd see you getting

proud raven
#

yeah it feels more simple to me to have the vbos prebuilt than to have each sprite with its own texture page

#

that feels like it balloons up too fast for my liking, especially if I add more animation frames

glad bear
#

Personally, if we had instancing, I'd suggest that even more for optimization sake

proud raven
#

I don't know if I know what that means in this case

glad bear
#

GPU instancing, it's basically taking a single vertex buffer and telling the GPU to render it in multiple places

#

Instead of looping and submitting the same vbo X amount of times for all of the places

#

With GPU instancing
1 VBO submission + array of positions -> 1 call, 1 batch break
Without GPU instancing
1000 VBO submission + matrix calls -> 1000 calls, 1000 batch breaks

proud raven
#

wow damn

#

need that

glad bear
#

GameMaker (with the current runtime) doesn't have this, but there are DLLs out there that do cover that

#

Otherwise, GMRT will have GPU instancing

proud raven
#

wow

#

is there a basic intro to DLLs in GameMaker anywhere?

glad bear
#

But in any case, 80-400 vbo submissons + matrix calls, aren't the worse

glad bear
#

Cause there's making DLLs

#

And then there's using DLLs that people have made

#

Which is pretty much "read their documentation"

proud raven
#

I mean I'll mostly be using but I'd also be interested in knowing how it works and how it's all linking together

glad bear
#

Well with GMD3D11, it's using the new runner interface. Which is undocumented on purpose

proud raven
#

I've never developed software on windows that wasn't from a game engine, so I don't have DLL experience

glad bear
#

DLLs aren't as common in GameMaker as they used to be, but they can still be very handy for certain tasks such as adding Windows API features to GameMaker or computing tasks that require performance that you can't get from GML. Let's write a basic one!

I recommend Visual Studio for this kind of thing, but anything that can compile a 64-it DLL w...

▶ Play video

You can get a pointer to a buffer in GameMaker, and pass it off to a DLL. You can use this to accomplish all kinds of things that would be very difficult in regular GameMaker!

The DLL project on Github:
https://github.com/DragoniteSpam-GameMaker-Tutorials/GMDLLExperiments

The terrain editor I make, for example, relies heavily on this stuff.
ht...

▶ Play video
#

You got this instead

#

Which supports strings, numbers and pointers (pointers to buffers and textures)

proud raven
#

oh nice

#

there's really a DragoniteSpam for everything

glad bear
#

Almost everything 😆

proud raven
#

saved me countless times already 🙏

glad bear
#

Not exactly

#

The Runner Interface is "new" to GameMaker

#

But it's mostly a in-house injection designed for some official extensions that need it

#

If you have a lot of C++ experience, you can poke and dig around the GameMaker Steamworks C++ files for some examples

#

Outside of that

#

There's no hand holding whatsoever, and it won't be the same in GMRT

#

(In GMRT, the runner interface would instead be more blended in with your game code pipeline, not requiring to write DLLs)

#

(Via writing C++ directly)

proud raven
#

(does that mean it's compiling the C++ and integrating it into the project itself, vs writing a DLL that has to communicate through more rigid API calls)

#

(I don't know enough to phrase this question correctly, and at this point I don't think I need it for my game so it's fine)

proud raven
glad bear
#

You basically gain no performance benefit as separate blocks

#

Outside of not having to manually define extra uvs

#

You'd still have to at least manage a vertex buffer per texture page too

#

But that's besides the point

#

If they were batched up together, with their own pre-determined positions, then they would be upmost beneficial

#

And rebuilding them every frame isn't exactly ideal either

#

So C really doesn't give you anything better

#

In this case

proud raven
#

Coming from A right now, I figured that C saves me the overhead of building a new block every frame. So even if I don't save anything on batching that's better baseline

#

C and D would be equally good for that reason

glad bear
#

Right, but you're still submitting a whole vbo per block

#

C would make sense over A, B and D if you weren't doing that

proud raven
#

sure, but wouldn't I still be doing that with D?

glad bear
#

Yes, except that you don't need to throw a whole unnecessary shader in

proud raven
#

ah, okay

glad bear
#

It's basically more work, for little to no benefits

proud raven
#

is the shader bad because it's annoying to program and deal with, or is it a performance cost?

glad bear
#

It's a different kind of managemental cost

#

Both time, energy and having to keep track of vbos/textures in a different way

#

And you don't gain any additional performance

#

And you probably don't lose really anything here

#

Just your own time

proud raven
#

the big benefit of C is that I don't have to manually maintain a map of all the VBOs that correspond to sprites, I can just enter a sprite and subimage and it'll work

#

provided they're all on the same texture page, which is easy to guarantee

glad bear
#

Who said you had to maintain a map of all of the VBOs?

#

You have sprite_get_uvs

#

You only need to build the vbo once for that block, per frame, and store it, forever

#

And just refer to the sprite texture page (per frame)

#

There's two different kinds of costs here

#

D - A vbo per frame per block
C - A vbo per frame uniform per texture page, per block

#

C you may store less vbos in memory, which might be nice, but unless you have ridiculously high block count (like into the 10s of millions), you aren't gonna really notice much of a major memory gain

#

The reason why I mention per texture page, is because you can have a block that has a sprite on a different page

#

I also need to mention as well, you couldn't really use texture pages here as reliably

#

You'd have to also include your own texture atlas builder, to ensure that all of the frames are on the same texture page

#

Or otherwise, include more than 1 texture page to sample from, as its own uniform

#

...Which also means you are now keeping track of multiple texture pages, manually

#

Whereas D might take a bit more memory per frame per block

#

But tbh, it beats having to manually manage more than half of that, just for no real benefits with C

proud raven
#

All of the for C does sound really annoying and I have absolutely no idea where I'd even start on a texture atlas builder

#

but my sprites are 32x32, and I figure that gives me a lot of room before I run out of room on a texture page

glad bear
#

Believe me, I've dove deep into C for my own projects.
I've got it nicely for my 2D infinite sandbox game

proud raven
#

at what point is it not good enough to put the sprites in the same texture group?

glad bear
#

Only if you are doing something with the texture itself and you want zero interference from other sprites

proud raven
#

I'm very glad to be talking to someone who has gone down the rabbit hole already

glad bear
#

Mainly some kind of texture sampling from a shader

#

Or if the texture repeats itself with stretched UVs

#

(There's a GPU function for this)

proud raven
#

isn't option C some kind of texture sampling from a shader?

glad bear
#

It is yes, but depending on how your blocks are and what you need from them within the shader

#

And in the case of animation, you don't need to worry about that at all

#

It's only a problem when you want to go over/under the uv boundaries, and don't wish to poke at any other uvs, or have some kind of "bleed in"

#

C in my case, doesn't have this issue because I have it "snap" to the next nearest tile size

#

(In fairness though, even the default shader does texture sampling. That's just how drawing works)

#

But this is why I'm suggesting D

#

D, you only need the VBOs made once

#

Since you are using matrix transforms anyway

#

So you only need a vbo per frame, per block texture

#

And any time you wish to draw it, you can use the block sprite, use sprite_get_texture to fetch its current texture page, and reference the current vbo to pass into vertex_submit (assuming that the vbos are stored within an array)

proud raven
#

real quick, are texture swaps and batches the same? like is it an additional performance hit to do a texture swap vs just breaking a batch?

glad bear
#

They are both different

#

And happen with different reasons

#

But I will say, batch breaks can be caused by texture swaps

#

In 3D, it isn't always the case however

#

And it's usually matrix transforms/frozen vertex buffers/shader uniform changes, etc

#

That causes batch breaks

#

(Batch breaks don't stack on top of one another, they only cause a break when you draw something with new settings applied in between the last draw call til now)

proud raven
#

are all draw setting calls batch breaks? like draw_set_color, draw_set_alpha?

glad bear
#

Nah

#

Usually the ones that cause a batch break is more specific to shader uniforms (color and alpha would reflect the vertex buffer that gets built automatically for sprites/surfaces/primitives in some cases, which doesn't cause a break)

#

Shader uniforms would be like

  • Matrix sets
  • Settting specific GPU functions, like fog and lighting
proud raven
proud raven
glad bear
#

Regardless of what the uniform is

proud raven
#

cool, that makes sense

glad bear
#

But as before, the batch break only occurs when you draw something

#

So it's the best time to change multiple uniforms + matrix transforms + texture swap

#

Since it'll all be 1 batch break all together

proud raven
#

I'm having trouble immediately picturing how you'd even break those apart

glad bear
#
// Wrong - 3 batch breaks
set_uniform(foo, bar);
draw();
set_matrix(foo, bar);
draw();
change_texture(foo, bar);
draw();

// Right - 1 batch breaks
set_uniform(foo, bar);
set_matrix(foo, bar);
change_texture(foo, bar);
draw();
#

(Not that you'd be changing and drawing something one at a time with the top case, but it's a prime example)

#

It can happen though

proud raven
#

I know it's just an example but these feel like they would end up drawing 2 very different results

#

but I know what you mean, something to look out for

glad bear
#

I mean the top case could just be sprites with different draw events

#

😆

proud raven
#

ohhh haha I see

glad bear
#

Sprites, surfaces and draw_rectangle/circle/triangle/etc are just vertex buffers under the hood

#

When you draw a sprite or surface or one of the primitives

#

You are explicitly adding them to a vbo

#

When you change anything above, you cause a batch break

#

Another good thing to point out too, changing the shader also causes a batch break

#

It's why you find examples like

shader_set(shd_outline);
draw_self();
shader_reset();

To be bad, but

/// obj_entity Draw Event
draw_self();
shader_set(shd_outline);
with(obj_entity) {
  event_perform(ev_draw, ev_draw_normal);
}
shader_reset();

To be good

#

The former is a batch break per draw event per instance of obj_entity

#

The latter is a single batch break

proud raven
#

for sure, that all makes sense

#

and is mostly how I try to draw anything more intensive

#

if I draw 2 things in a row that are on the same texture page, and there's a batch break between them (e.g. matrix set), there wouldn't be a texture swap right?

glad bear
#

No texture swap yeah

#

Just the batch break

proud raven
#

cool cool

proud raven
#

because otherwise wouldn't the UVs baked into the vbos be wrong?

glad bear
glad bear
#

The idea is that

#
  • No separate texture page
  • You make a vertex buffer for every frame, and store it forever
#

Then whenever you fetch with sprite_get_texture and get the current frame of a block from a vbo look up

#

The uvs would be mapped already

#

There's no additional work

proud raven
#

I get that it's every frame, I just don't know where sprite_get_texture comes into it

glad bear
#
var texId = sprite_get_texture(sprite_index, image_index);
vertex_submit(vbo[floor(image_index) % image_number], texId);
proud raven
#

oh okay

#

that's very similar to what I was picturing earlier when I said I would need a map, only I was picturing it like this

vertex_submit(global.blockVbos[? sprite_index][image_index], texId)
glad bear
#

Yeah nah you don't need any of that

proud raven
#

I mean I don't need it but is there any reason to not use it other than preference?

glad bear
#

ds_maps aren't exactly fast to look up

#

You don't really need to look up the sprite index every time anyway

#

Given that the block isn't likely to change its sprite index once created

#

(Unless it is, which even then in that case, you'd move that elsewhere and just fetch an array of vbos)

proud raven
#

ah okay, fair point an array would be faster

#

well it feels like I have all my options laid out with a good sense of the tradeoffs

glad bear
#

It's not a bad idea to store the array of vbos within a ds_map though, just for data storage

#

(I'd use a struct and sprite name though, since accessing that is shockingly faster than ds_maps... I have the benchmarks too)

proud raven
#

how do you use the sprite name here?

glad bear
#

global.blockVbos[$ sprite_get_name(sprite_asset_goes_here)]

#

(Ignoring prehash here)

#

(That's just variable_get_hash + caching, and that won't offer you benefits here. the Struct Get/Set is your focus)

proud raven
#

you can use [$ ] notation on a struct??

#

(that speedup is insane but I just need to make sure I understand what you're doing with the Struct Get first)

glad bear
#

That's called a struct accessor

glad bear
#

global.blockVbos[$ sprite_get_name(sprite_asset_goes_here)]

#

The test case had a slightly different value put in, but it was ultimately a string being passed in

proud raven
#

oh shit my eyes glazed and read that as a question mark, like a map, sorry

glad bear
#

Hahahaha

proud raven
#

okay so then next question, it will let you name a field the same as a sprite?

glad bear
#

Yes

proud raven
#

I guess that's the real benefit of the accessor, huh

glad bear
#

struct_get/set itself allows you to name a key with almost anything (it'll be stringified whether it's a string or not, so both 0 and "0" is the same)

#

And the accessor is just them in a trenchcoat

glad bear
proud raven
#

cool cool. this makes sense, bc otherwise you'd just do struct.variableName, but with struct[$ "spr_spriteName"] you can do all that wacky fun stuff

#

wow this really opens up a lot

glad bear
proud raven
#

good to know I can just make all my map access 3x faster for nothing lol

glad bear
#

Structs have gotten a lot of optimizations over the years

proud raven
#

I took a big break from GameMaker from like 2018 to 2023, so structs are still this fancy fun new thing

#

I use them a lot, but it's a little thrill each time

glad bear
#

They are really nice

proud raven
#

alright, I think I have a good understanding of options and their tradeoffs here, thank you so much for your help!

#

I really appreciate you taking the time to explain everything to me