Jump to content
  • Advertisement
Sign in to follow this  
True_Krogoth

My graphic objects (Gfx)

This topic is 1216 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Graphic objects (Gfx) are objects like:

Point

Sprite

Base

that are created at graphic objects of Base type at specified position, to be able to make all things rendered in right order.

(Trying to avoid sorting here)

 

As there aren't much types of objects, and the majority of objects are sprites, I decided to implement all these special types in 1 class.

At first look

Cons: More members, Few short switches

Pros: Collections of direct objects (not pointers), No virtual methods

 

While writing my code I was thinking that:

1) I must highly support transparent objects, so I do not use zBuffer at this particular approach.

2) Collections of direct objects work pleasantly faster than pointers.

3) Switch is efficiently implemented by compiler, so it can be either branches or jump table whichever is better.

4) Unions is a way to not just alternate data types, but also name members in appropriate, readable way. (This is not intended union usage though)

5) Special types are good to sort in order of use frequency, this is why Sprite must go everywhere first (as the majority of objects are sprites).

(Might be incorrect at any point)

 

GFX.HPP (raw):

#ifndef GFX_HPP_INCLUDED
#define GFX_HPP_INCLUDED

#include <vector>

namespace keng {

class gfx {

public: // types
    enum types {
        sprite,
        base,
        point
    };
    static const int base_default_size = 10;

private: // default base
    static gfx* const default_base;

public: // special construct methods
    static gfx* create_sprite(
        int position = 0,
        int map = 0,
        int x = 0,
        int y = 0,
        float scale = 1.0f,
        gfx* base = default_base
    );

    static gfx* create_base(
        int position = 0,
        int size = 0,
        float scale = 1.0f,
        gfx* base = default_base
    );

    static gfx* create_point(
        int position = 0,
        int x = 0,
        int y = 0,
        float scale = 1.0f,
        gfx* base = default_base
    );

public: // switching methods
    void render();

    ~gfx();

private: // untitled special constructors
    // sprite
    gfx(
        int map,
        int x,
        int y,
        float scale
    );

    // base
    gfx(
        int size,
        float scale
    );

    // point
    gfx(
        int x,
        int y,
        float scale
    );

private: // help
    typedef std::vector<std::vector<gfx>> gfx_container_t;

    template<typename... Args>
    static gfx* construct(gfx* base, int position, Args... args);

private: // type data
    int type;

private: // switching data
  union {
    int map; // sprite
    gfx_container_t* gfx_container; // base
  };
  union {
    int x; // sprite / point
  };
  union {
    int y; // sprite / point
  };
  union {
    float scale; // sprite / base / point
  };
  union {
    int map_x; // sprite
  };
  union {
    int map_y; // sprite
  };
  union {
    bool mirror_x; // sprite
  };
  union {
    bool mirror_y; // sprite
  };

}; // class gfx

} // namespace keng

#include "gfx.hh"

#endif // GFX_HPP_INCLUDED

There are named constructors for each special type.

If you need to create an object at just screen you call one of them with no Base parameter specified. It uses Default Base that is highly related to screen then.

If you need to make something like window with its own render order, you create a Base at Default Base, and then create objects with itself specified as the base. Then also translation, scaling, filtering, etc are applied to Base's sub-objects.

 

Graphic objects are vector of vectors. The first coordinate is a rendering order (or also position).

 

This is where I would like to stop my introduction and ask a question.

 

Let's say, I planned 10 possible rendering orders, so my vector consists of 10 vectors. But sometimes there are vectors that are empty, because there is no single object of this particular rendering order, so I go through missing rendering orders every ~.025 sec loop.

Is it worth to do some optimization, like

1) make an array of indexes and pass through indexes, but this requires a lot of work with indexes upon creation/deletion,

2) make a list of vectors instead of vector of vectors, but this may work even slower,

or ?,

or better put things in user's hands and force him to use orders wisely, not setting too large bounds and adapting order indexes himself?

Or maybe just 1 vector for all orders that is sorted (despite all my tries to avoid sorting) every creation/deletion is still overall better?

Edited by True_Krogoth

Share this post


Link to post
Share on other sites
Advertisement

I must agree here with what has been said above. When you work on the tech it's very hard to predict what you should do in certain cases if you actually don't have those cases.  You ask:

 

 

 

better put things in user's hands and force him to use orders wisely, not setting too large bounds and adapting order indexes himself?

 

Well, why don't you ask that question whoever is going to use your tech? I understand that you're not writing that engine/framework only for yourself as you expect other programmers to be using it. If this is the case work with at least one person, who will actually find usecases for your tech. Find your John Romero ;)

 

Also at this stage avoid:

 

- premature optimization ( which apparently you already think about, I understand it as it's very often also my problem ;) ) - first get things up and running, next iteration will apply more improvements and so on.

 

- hardcoding -  you want to make some sort of flexible and generic framework if I understand correct, so give the programmer chance to render what he wants and how he wants rather than limiting one to use your 3 predefined types of objects.

 

- magic numbers - in here limit of 10 in my opinion is a magic number, why 10? As example I worked with engine that had a limit of 2 texture samplers per shader ( everything highly abstracted, so what's given must be taken ). But why 2? - because not 3 nor 5 ;) Try to avoid limits like that.

 

Making tech just for a sake of making it doesn't make much sense. You must have a goal. Think of what game you want to make (even to demo your tech) and build features for this particular usecase. It's lot easier that way to figure out what you actually need. Also put yourself in shoes of a programmer who will use your tech. I used to do something that I call "phantom coding". I know what I want to make, so I start coding using API that doesn't exist. I code with API I wish to exist ;). Obviously I will not compile and run my code. The good thing of "phantom coding" is that before you actually implement anything, you already know whether it's going to be easy to use etc. But you always need usecase. Tech without purpose isn't worth much :)

Share this post


Link to post
Share on other sites

Ty, especially for VBO. Me noob, didn't even "hear" about that, lol.)))

 

Let's suppose, I am not stubborn, but I would like to implement my own render ordering efficient enough, at least for good practice.

 

This is how my "gfx container" (of Base) can look:

[attachment=28657:container.png]

 

- Column is an order index.

- Row is a gfx's index in a vector of certain order index.

- First go all blue gfxs, then all red ones.

- Blue gfx is a gfx that is visible thus rendered.

- Red gfx is a gfx that is not visible thus not rendered (for example, out of screen bounds).

- Blue part size, or the first red gfx index is well-known (marked with yellow dash).

 

- Gfxs have update and render phases.

- Blue gfxs pass through update and render phase, red ones - update only.

 

- If a blue gfx becomes not visible, it is swapped with the last blue gfx and becomes red (blue part size decreases by 1).

- If a red gfx becomes visible, it is swapped with the first red gfx and becomes blue (blue part size increases by 1).

 

- The whole container is an order indexed vector of pointers to vector of gfx (thus render orders can be easily swapped or moved).

 

Few additional statements, related to how such gfx is meant to use:

- There aren't much empty orders, that's why I let to pass through all empty ones, not caring much of efficiency.

- There aren't much active orders at all. If more orders are needed, they can be inserted between existing ones (modifying few existing order indexes). It might have to move nearly all existing orders, but it's fine as it's rare operation.

 

Why I personally think this all is fine:

- At the certain order index it passes through objects, not pointers to objects.

- When it goes through all order indexes, it passes through pointers to objects, not directly objects, but, compared to object count, order index count is meant to be little.

- At render phase it does not go through objects that do not render (does no check frequently).

 

It will work regardless all this requirements, but may go inefficient.

 

What do you think, please?  :-o

Edited by True_Krogoth

Share this post


Link to post
Share on other sites
What do you think, please?  :-o

Well, I haven't got the clue what you want to do really. Honestly, "graphic objects are vectors of vectors of graphic objects" sounds to me not to be a sane concept. It appears to me that you mix several aspects (at least game object composition, its graphical representation, its world placement, its rendering parametrization, visibility culling result and even its temporal coherency) into a single object. That highly violates the single responsibility principle.

 

There are named constructors for each special type.

I never heard of "named constructors" and "untitled constructors" … where does that came from? Usually the term "constructor" is used for what you name "untitled constructor", and those others are usually called "factory methods" or perhaps "generators" or "creators".

 

2) Collections of direct objects work pleasantly faster than pointers.

Not necessarily. Swapping pointers is definitely more efficient than swapping any non-POD objects.

 

4) Unions is a way to not just alternate data types, but also name members in appropriate, readable way. (This is not intended union usage though)

Well, your usage of unions in the OP's code snippet is mostly meaningless because (a) you use anonymous unions and so they do not add to variable names, and (b) include a single variable only. The only case where the latter is not the case is with the union that includes "map" and "gfx_container". However, the factory method for sprites hint at the usage of both "map" and "base" for sprites, so that would corrupt your union data though!

 

BTW: All of your factory methods are incomplete because they actually do nothing. They further are specified to return a pointer but do return nothing at all.

 

Let's say, I planned 10 possible rendering orders, so my vector consists of 10 vectors.

What exactly is a "planned rendering order"?

Edited by haegarr

Share this post


Link to post
Share on other sites

I never heard of "named constructors" and "untitled constructors" … where does that came from? Usually the term "constructor" is used for what you name "untitled constructor", and those others are usually called "factory methods" or perhaps "generators" or "creators".

It is nearly same

 

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Constructor

 

 

 


Not necessarily. Swapping pointers is definitely more efficient than swapping any non-POD objects.

The point is how often you swap your objects.

 

Usually the most frequent operation is passing through objects and updating/rendering them.

Passing through objects is faster than through pointers.

If objects aren't too large, it looks fine to swap their content (or it isn't?).

 

What I do is exchange faster swap ability for faster object passing.

If some objects are meant to be very active in terms of moving across container, I will do separate implementation for them, where pointers figure.

But common aren't.

 

Well, your usage of unions in the OP's code snippet is mostly meaningless

 

 

Because the class is unfinished.

This is what I have at the moment:

  union {
    int spriteset; // sprite
    gfx_container_t* gfx_container; // base
    int size; // point
  };
  union {
    int x; // sprite / base / point
  };
  union {
    int y; // sprite / base / point
  };
  union {
    int spriteset_x; // sprite
    int color_r; // point
  };
  union {
    int spriteset_y; // sprite
    int color_g; // point
  };
  union {
    int mirror_x; // sprite
    int color_b; // point
  };
  union {
    int mirror_y; // sprite
    int color_a; // point
  };
  union {
    float scale; // sprite / base
  };

 

What exactly is a "planned rendering order"?

 

I presumably know what objects I am gonna work with.

For example, I am working on simple 2D strategy. I know that I am gonna have some interface, ground tanks, aircrafts, healthbars and cursor. This might require 5 order indexes, as aircrafts are over tanks, healthbars are over aircrafts, etc.

So I will init my gfx to have just 5 orders.

If I need more, I can add it at any point. But for a simple purpose like I described it's unneeded.

Edited by True_Krogoth

Share this post


Link to post
Share on other sites


I presumably know what objects I am gonna work with.

For example, I am working on simple 2D strategy. I know that I am gonna have some interface, ground tanks, aircrafts, healthbars and cursor. This might require 5 order indexes, as aircrafts are over tanks, healthbars are over aircrafts, etc.

So I will init my gfx to have just 5 orders.

 

Are you expecting performance issues due to overdraw? Otherwise, you might not want to use ordering for this case, but use a z-buffer/depth-buffer to handle this type of ordering. Sorting is only ever good to reduce overdraw (in which case you draw top-to-bottom and still have to use a z-buffer), and reduce API-overhead by sorting by texture/shader/material. Again, you don't have to do any sorting at all if you give each object a specific z-order as part of the vertex, and use a z-buffer.

 


Sprite

 

This is part of the reason why such a design won't really work. Usually, mostly due to performance reasons, you would not want to have sprites be their own destinct class instance, but use something called "(sprite) batching", where you simply call a method on a batcher class telling it "draw this texture at position X/Y" for every sprite in the game, collecting this data in one big vertex buffer. After you have collected all your sprite calls, you usually sort by texture (which is not a big deal in this case since it eliminates unnecessary texture switches and draw calls), and draw all sprites in one draw call per texture. While performance is usually the main reason, after much experience with that kind of stuff I can safely say that this is also way easier to use than having all sprites be their own instance. You don't have to store any "Sprite" object and you can much easier create effects that require you to draw multiple sprites (text shadows, ...).

Share this post


Link to post
Share on other sites

 Are you expecting performance issues due to overdraw? Otherwise, you might not want to use ordering for this case, but use a z-buffer/depth-buffer to handle this type of ordering. Sorting is only ever good to reduce overdraw (in which case you draw top-to-bottom and still have to use a z-buffer), and reduce API-overhead by sorting by texture/shader/material. Again, you don't have to do any sorting at all if you give each object a specific z-order as part of the vertex, and use a z-buffer.

But how to do with transparency?

I am not blankly defending my concept, but I don't see a solution w/o making my own ordering.

Am I right, translucent objects can't be done with zBuffer. So I have to make my own ordering! (Am I indeed right?)

Even "non-obviously-translucent" sprites need to be translucent to "look nice", otherwise they are sharp-edged. (It needs more than just .0f and 1.0f alpha values to "look nice" at the edge)

Edge translucency is like free anti-aliasing. :-o (Please, don't beat me for saying such things )) I am just trying to explain it easy )

 

 

 


 where you simply call a method on a batcher class telling it "draw this texture at position X/Y" for every sprite in the game, collecting this data in one big vertex buffer. After you have collected all your sprite calls, you usually sort by texture (which is not a big deal in this case since it eliminates unnecessary texture switches and draw calls), and draw all sprites in one draw call per texture

This is a very good point.

You don't have to store any "Sprite" object and you can much easier create effects that require you to draw multiple sprites (text shadows, ...).

 

But batching is real only when zBuffer is ready.

 

Again, translucent objects.

 

No zBuffer.

 

No batching.

 

I have to draw each sprite in certain order.

 

 

 

I will give you few examples in addition, why I am defending my translucency support!

 

Let's take any unit, for example, footman! Footman can go inside a castle or go outside. If this is some turn-based RPG, I would like some appear/disappear effect. Let's say, footman's alpha goes from .0f to 1.0f when it comes out!

Logically, EVERY unit has to be able to go transparent, as they all can go inside/outside of castle.

 

Another example.

I would like to see a house with translucent roof so I can know from the top perspective what is going on inside, keeping some realism.

 

I am thinking of a game where translucency is not a rare thing.

On the other hand, such game isn't as much complicated in terms of objects that using own ordering, which is not as efficient as zBuffer, is performance-critical. At the first place, I implement more-or-less efficient translucent objects, then use it for everything, including opaque. If this will ever get performance-critical issues, I am gonna distinct translucent and opaque objects and apply zBuffer to opaque indeed.

But why do you need a 2D game so much complicated that you need only zBuffer to work well? If your game has billions objects drawn you probably need a special engine for this. This is logical to choose the engine that is the best suitable for your purpose.

My engine, or engine, as it's not gonna be something much complicated, is for common (90+%) 2D game. So I see no reason to distinct translucent and opaque objects at least at this phase.

 

I think, this post explains my logics the best.

 

I would like to get as much useful feedback as it's possible, as I am tired of coding things that do not make real sense.

(Like, I could already write 1 inefficiently-made game at this all time that I am trying just to organize my objects, reworking nearly the same code over and over again, as I can't get rid of feeling trash approach. This all comes from no game making experience. I am certainly able to make a game just to make it work somehow, but I would like to be patient and collect more experience.)

Edited by True_Krogoth

Share this post


Link to post
Share on other sites

You discard transparent pixels in the fragment shader or activate alpha test; that works together with depth testing and should be good enough to cut out 2D-sprites.

When your game is complete you can still add more fancy looking graphics with blending, but you should not endlessly complicate your code from the beginning by emulating things the GPU can do for you on its own!

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!