#Scribble and the Profiler

1 messages · Page 1 of 1 (latest)

spare steppe
#

Hi all!
Hoping this is the right place to ask - the discord link in the Scribble documentation is to this discord, though I couldn't see a Scribble specific channel.

I've been profiling recently trying to optimise certain noticeable sections of the game slowing. It's been rather successful being able to clean up inefficient calls and save those precious milliseconds, though my white whale is most definitely my one function that draws the text of a card. When there are enough cards on the screen that should draw, there's a noticeable drop in fps.
It looks like the biggest offender is this Scribble function (see attached image).
Scribble being a bit of a black box to me, and not being one to question the infinitely-smarter-than-me Juju, what is this and is it avoidable?
What is that one doing? Why is it so much greater than the other similarly named calls?

I've also attached an example of what my cards look like - that (poorly named lol) drawCardName function draws the 3 highlighted parts of the card: the number in the top corner, the name, and the textbox effect. I suspect its the textbox effect that corresponds to that particularly long Scribble call, but unsure if it's even relevant.

Any insight is greatly appreciated!

compact surge
#

Thats an anonymous function, we dont know which one that is from looking at your profiler

#

it would be extremly helpful if you posted the drawCardName function itself

#

and, it would also be worth examining that you're calling that function ~100 times per frame, is that the amount you're meaning to call it? and, interestingly it's calling a bunch of the functions inside of it more than it is calling drawCardName, again, hard to say if thats how it's supposed to be without you sharing the code in question

spare steppe
proper quarry
spare steppe
#

oh thank you! I'll ask in there too!

proper quarry
#

Additionally, looking over your function, it appears you're calling a scribble 8 times every time you draw a card?

#

and appears the following methods are calls this number of time:
blend = 8
origin = 8
msdf_border = 4
transform = 4
draw = 7

You are probably skipping a few of these draw's when they are not needed on the card right?

#

Given that you are drawing card 91 times roughly a frame, and the anon function is only called twice as many times as the draw card function (197)

spare steppe
proper quarry
#

thats helpful thanks

#

then msdf_border appears to be the function in question

spare steppe
#

in the worst case, it will draw a "ghost" version of a card, but thats for at most like 3 cards at a time and not captured in the profiler period here
those are the ones captured under the _card.shadowActivateEffect == 1 check on line 88

#

basically you can ignore that

proper quarry
#

A good way to work around this would be to draw this information to a surface then draw the surface. I would imagine the border is a moderately pricey to do, even if it's using a shader, jumping in and out of a shader drawing can be costly.

spare steppe
#

I am using an older version of Scribble as I started this project in an older version of GM that didnt support sdf fonts, so am using Scribbles msdf fonts to make up for it. I tried updating my version of GM some time ago but it broke a whole ton that was daunting to go through and fix 😭

proper quarry
#

that'll happen 😆

proper quarry
#

Yeah think the best solution if you're not trying to update would be to go about using surfaces and that'll save you a ton of performance alone.

spare steppe
#

im a bit of a noob when it comes to surfaces but have delved a bit

how would drawing it to a surface then drawing the surface be more efficient than just drawing normally? given the cards can always be moving around and scaling and stuff

proper quarry
#

well imagine you are drawing something complex 100 times

#

for your card you are calling a draw function for:

  • card background
  • card image
  • card border (for rarity)
  • card title
  • card mana icon
  • card mana
  • card info (and emotes as those get broken up internally by draw calls)
#

these are all the things you'd actually be drawing every frame, multiple times a frame

#

so 7-8 roughly draw calls per card, even if it's the same card

#

draw 91 cards like you are doing and you're looking at 637 draw calls roughly

#

but if you draw it to a surface, then it's 8 draw calls plus 91 times you need to draw that surface to the screen, because it would act much like a draw_sprite

#

but looking at your code, if you can manage this much work with scribble, learning surfaces should be pretty easy. they aren't as scary as people think, just remember to delete them when you're done with them.

spare steppe
#

ahh i think i see what you mean - you mean essentially drawing everything that makes up a card to a surface, then redrawing that surface whenever an instance of a card with the same parameters would need it?
if so, that was actually how i originally had it! my first attempt at a card rendering system involved when it was created it would draw everything to a surface, then save it as a sprite for the card. Then, if anything changed, it would regenerate it's sprite.
This had a few issues though. Cards' parameters are pretty dynamic - for things to line up to draw 'exactly' the same card to save on performance wasn't worth the effort, cards can be quite unique throughout the game, so would all have their own individual sprites. Then it was my RAM usage that would jump way up as I held all those sprites in memory.
But another major issue is that doing it that way meant that as cards scaled up, their text (not worth being re-rendered to a surface -> sprite just for the scale change) got all scrunched up, and testers explicitly calling out hard to read text lol

#

but the msdf_border function being the culprit here is actually a huge help though, I'll test a bit more with it to convince myself lol

proper quarry
#

are cards unique objects? or are you cycling through a list when you draw?

spare steppe
#

they're unique instances

proper quarry
#

ah

#

you could

spare steppe
#

actually, i just commented out those msdf_border calls, then retried the profiler in the same test scenario. Still the same timings 😦

proper quarry
#

So there is two ways you could go about this.

register_draw() which would be a function, which simply pushes the card into an array of the object obj_card_renderer
Then the renderer would loop through that list drawing, and if any repeats show up, simply use the cached surface, then destroy all the surfaces at the end of that loop
or
init a global global.cardSurfaces = {}, then when you are about to draw a card for the first time a frame inside the card's draw even struct_exists(global.cardSurfaces, cardName) if it exists, draw the surface which resides in that location, if it doesnt exists, create a surface, draw to it, then global.cardSurfaces[$ cardName] = _surf. and at the draw end event just loop through the keys of that struct and delete every surface, and delete the keys from the struct

proper quarry
#

well, try to repeat that process for blend, transform, and origin see which one is the culprit

#

it might even be draw which would make sense

#

as that would reasonably be the most costly

#

it's been some time since i used scribble though (like v6?) it might even already be doing optimizations for surfaces internally. That's something you'd need to wait for Juju to respond about.

spare steppe
spare steppe
proper quarry
#

but this would be a globally defined function
function globalFunc(){}

#

Good luck! feel free to come ask around if you need help

spare steppe
spare steppe
proper quarry
#

that is strange because your draw card function is called 91 times and the actual .draw is only done 197, means for some reason you're skipping 79 .draw idk if you implemented culling or anything like that.

#

additionally, you could save a lot of performance by just... not drawing 91 cards at once lol. your example shows only like 9 cards from what the user can see.

spare steppe
# proper quarry additionally, you could save a lot of performance by just... not drawing 91 card...

this was just a snippet of the whole screen haha

so you'll see that there are 6 of these 'boards', they can all have as many cards at them as you can put there, but it culls an only renders the top 10 of each board. You'll also notice that if a deck is face up (like where my cursor is hovered), it'll also show the card text rendered, but only for the top card.

so the drawCardName function gets called for all of the ones that are being drawn, even if theres no text to render (in which case the earlyEndCheck in drawCardName will be set to true and text not rendered) like in the fanned out cards in the deck that arent rendering their text.
but its not called at all if the card is not getting drawn at all (like the bottom 4 cards of the deck, only ten are drawn fanned out, or like the 1 card at each board not being rendered at all)

proper quarry
#

oh i see

#

gusty

spare steppe
#

i think i can figure out some smarts though with deciding if a card should be rendered on a board
at the moment, just the 'top' 10 are being rendered, but i could do some checks for if a card is 'on top of the current one' that it draws the card but not iit's text

spare steppe
# proper quarry gusty

i'm just in a debug mode atm for this, but there IS a boss fight in the game called "Windy Day" that plays out like this with a lot of Gusts hahaha

flint walrus
#

i'd look at using ScribbleJr here

#

when you think about the cost per draw 0.026ms is fast but ScribhleJr might be able to squeeze just a little more

#

also how are you using the transform? usually with cards you want to draw the entire card on a transform rather than just the text

spare steppe
#

holy....... didn't think Juju himself would be commenting lol
i'll be real, i don't even know what Scribble Jr is, but I'll give it a look tomorrow.
having said that, you're absolutely right - it's already very fast so I could continue to look elsewhere to save on drawing and might not even bother with migrating to using Scribble Jr

the card frame/art is handled separately, basically calculating the 4 corners and using draw_sprite_pos as it moves around. That takes into account the rotation and scale.
then that drawCardName function handles drawing the scroll the text and name is drawn on, taking into account the rotation and scale of the card that's passed to the function; basically from line 285 onwards (using either draw_sprite_general or draw_sprite_ext depending on which part we're talking about)

flint walrus
#

are you familiar with matrices?

#

yeah looking at your code it would appear not

#

look up matrix_set() and matrix_build() and play around with them. you will rapidly realise that many things in that script can be simplified down

#

also you can use a preprocessor or Scribble macro for all of those string replacements

spare steppe
#

yeahhhhh i'm not familiar with either of those. I'll look into matrices, but do you think it'll be a significant optimisation here? or just a simplification of the code?
i've had a look at the preprocessor stuff - unfortunately i'm on an older version of GM that didn't have sdf font support, so am using an earlier version of scribble for the msdf font support, and theres no preprocessor stuff. I'll look into the Scribble macro though. Similarly, would I expect it to be more performant than the string replacements, or just more readable?

compact surge
#

dumb question

#

why are you drawing 100 cards?

spare steppe
#

i guess to put it simply - there are about 100 cards on the screen to draw 😆

not all of them are actually drawing though, theres culling going on, and the drawCardName (the heavy function) isn't necessarily being called for each card. And even if it is, there are catches to end it early and not draw the text of cards if they are 'hidden' (eg, when there are lots of cards in the deck and you hover over it and it fans out, only the top card's text is drawn, as you wouldn't be able to read the card behind's text anyway)

compact surge
#

and a stack like the one int he picture could theoretically have many different cards in there?

#

and I'm also assuming you're doing dynamic text updating on these cards, on the mana and the description if relevant.

spare steppe
#

yep, those boards can in theory have infinite cards at them (but in practice no, no infinite combos to get that high), but only the top ten cards are rendered. Until of course you click on the board and it expands and look through each card, but it does so in a way that only 6 cards are on screen at a time and rendered

spare steppe
compact surge
#

another question, how dynamic are these stacks?

spare steppe
#

so yeah, for our purposes here, the three things on the card (it's name, the textbox, and the number int he top left) are dynamic and can change

compact surge
#

could you pull any arbitrary card off of it, or just one

spare steppe
#

theyre quite dynamic.
other effects can add more cards to boards, move cards around, destroy them

compact surge
#

you could still save a lot of draw calls with a surface per stack in that case, but it makes those operations a lot more complicated and would need you to remake the whole thing every time you change a card

#

as in reconstruct the surface for removing cards

#

youw ould only need to handle drawing the 10 cards whenever the representors change.

#

of course if you're manually going in with the mouse to pull off a card from the stack in real time, then thats not really an option

spare steppe
#

yeah that's kind of what i was thinking too
i'm not super fluent with surfaces, but it seems like a lot of work to render cards pretty much the same way anyway, no?
i'm not going in with a mouse to move cards around once theyre on the board, but the cards moving around and updating is quite frequent during normal play

compact surge
#

the thing with the surface is you don't have to touch them if you dont change them. The reasony our cards are expensive because you're drawing the text a lot, calculating where it goes, how it's rotated, etc.

#

with a surface, you do that calculation once and plop it onto the surface

spare steppe
#

i think i can save quite a few draw calls on these stacks though
something like
if(card_pos_on_stack mod 9 is in (0,1,3,4,6,7) and array_length(card_in_stack) > card_pos_on_stack) then dont draw

compact surge
#

the following 5 minutes in which the card doesnt move you do not have to recalculate that

#

because it's on the surface

#

you just need to draw the surface every frame

spare steppe
compact surge
#

but you have the pixel data saved

spare steppe
compact surge
#

perhaps it's not worth it then.