Jump to content

  • Log In with Google      Sign In   
  • Create Account

reuse 3d primitives


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
28 replies to this topic

#1 burnt_casadilla   Members   -  Reputation: 443

Like
0Likes
Like

Posted 14 May 2013 - 12:50 AM

Is there any way to make a primitive and use it over and over again? ex: if I make one cube, can I create 100 and make a 10x10 grid? I've tried using a for loop and updating the x and z coords with each loop thru, but it only moves the one cube thats created in the beginning

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cube_Chaser
{
    class Cube
    {
        private GraphicsDevice device;
        private Texture2D texture;

        public Vector3 location;

        private Vector3 position;

        private VertexBuffer cubeVertexBuffer;
        private List<VertexPositionTexture> vertices = new List<VertexPositionTexture>();

        public Cube(GraphicsDevice graphicsDevice, Vector3 playerLocation, float minDistance, Texture2D texture)
        {
            device = graphicsDevice;
            this.texture = texture;

            PositionCube(playerLocation, minDistance);

            BuildFace(new Vector3(0, 0, 0), new Vector3(0, 1, 1));
            BuildFace(new Vector3(0, 0, 1), new Vector3(1, 1, 1));
            BuildFace(new Vector3(1, 0, 1), new Vector3(1, 1, 0));
            BuildFace(new Vector3(1, 0, 0), new Vector3(0, 1, 0));

            BuildFaceHorizontal(new Vector3(0, 1, 0), new Vector3(1, 1, 1));
            BuildFaceHorizontal(new Vector3(0, 0, 1), new Vector3(1, 0, 0));

            cubeVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, vertices.Count, BufferUsage.WriteOnly);

            cubeVertexBuffer.SetData<VertexPositionTexture>(vertices.ToArray());

            this.position = position;
        }

        private void BuildFace(Vector3 p1, Vector3 p2)
        {
            vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 1, 0));
            vertices.Add(BuildVertex(p1.X, p2.Y, p1.Z, 1, 1));
            vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 0, 1));
            vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 0, 1));
            vertices.Add(BuildVertex(p2.X, p1.Y, p2.Z, 0, 0));
            vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 1, 0));
        }

        private void BuildFaceHorizontal(Vector3 p1, Vector3 p2)
        {
            vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 0, 1));
            vertices.Add(BuildVertex(p2.X, p1.Y, p1.Z, 1, 1));
            vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 1, 0));
            vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 0, 1));
            vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 1, 0));
            vertices.Add(BuildVertex(p1.X, p1.Y, p2.Z, 0, 0));
        }

        private VertexPositionTexture BuildVertex(float x, float y, float z, float u, float v)
        {
            return new VertexPositionTexture(new Vector3(x, y, z), new Vector2(u, v));
        }

        public void PositionCube(Vector3 playerLocation, float minDistance)
        {
                location = new Vector3(.5f, .5f, .5f);
        }

        public void Draw(Camera camera, BasicEffect effect)
        {
            effect.VertexColorEnabled = false;
            effect.TextureEnabled = true;
            effect.Texture = texture;

            Matrix center = Matrix.CreateTranslation(new Vector3(-0.5f, -0.5f, -0.5f));
            Matrix scale = Matrix.CreateScale(0.05f);
            Matrix translate = Matrix.CreateTranslation(location);

            effect.World = center * scale * translate;
            effect.View = camera.View;
            effect.Projection = camera.Projection;

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                device.SetVertexBuffer(cubeVertexBuffer);
                device.DrawPrimitives(PrimitiveType.TriangleList, 0, cubeVertexBuffer.VertexCount / 3);
            }
        }
    }
}

 

 


If you see a post from me, you can safely assume its C# and XNA :)


Sponsor:

#2 ATEFred   Members   -  Reputation: 1127

Like
5Likes
Like

Posted 14 May 2013 - 02:47 AM

Sure, you can create one cube and reuse it.
there are a few options:
- create one cube geometry set (VB + IB (optional) ), and render it n times, setting different world transform matrices as constants for each draw call.
- create one cube geometry set and instance it n times with one draw call ( for example, drawIndexedInstanced in d3d11 ). You can then use SV_InstanceID to index into a cb containing an array of transforms, or just have some other simple shader code to offset each vertex based on instance id.

- You can also use GS to do the instancing, but that will almost certainly be alot slower, especially for large amount of instances. ( en example of this though could be have no vertex geometry, call draw with the number of verts set to the number of cubes you want on screen, and then expand each point into a cube in GS )



#3 NightCreature83   Crossbones+   -  Reputation: 3033

Like
1Likes
Like

Posted 14 May 2013 - 05:23 AM

Sure, you can create one cube and reuse it.
there are a few options:
- create one cube geometry set (VB + IB (optional) ), and render it n times, setting different world transform matrices as constants for each draw call.
- create one cube geometry set and instance it n times with one draw call ( for example, drawIndexedInstanced in d3d11 ). You can then use SV_InstanceID to index into a cb containing an array of transforms, or just have some other simple shader code to offset each vertex based on instance id.

- You can also use GS to do the instancing, but that will almost certainly be alot slower, especially for large amount of instances. ( en example of this though could be have no vertex geometry, call draw with the number of verts set to the number of cubes you want on screen, and then expand each point into a cube in GS )

Second and last options are out seeing this is XNA so no DX11 support, you can still do instancing but you have to look up DX9 instancing instead. See this link for hardware instancing in XNA 4. Depending on how many cubes you are going to draw instancing is your best bet for realizing a decent FPS. DX doesn't like small vertex buffers and slows down dramatically when it is faced with loads of them.


Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, Mad Max

#4 ATEFred   Members   -  Reputation: 1127

Like
0Likes
Like

Posted 14 May 2013 - 06:00 AM

Sure, you can create one cube and reuse it.
there are a few options:
- create one cube geometry set (VB + IB (optional) ), and render it n times, setting different world transform matrices as constants for each draw call.
- create one cube geometry set and instance it n times with one draw call ( for example, drawIndexedInstanced in d3d11 ). You can then use SV_InstanceID to index into a cb containing an array of transforms, or just have some other simple shader code to offset each vertex based on instance id.

- You can also use GS to do the instancing, but that will almost certainly be alot slower, especially for large amount of instances. ( en example of this though could be have no vertex geometry, call draw with the number of verts set to the number of cubes you want on screen, and then expand each point into a cube in GS )

Second and last options are out seeing this is XNA so no DX11 support, you can still do instancing but you have to look up DX9 instancing instead. See this link for hardware instancing in XNA 4. Depending on how many cubes you are going to draw instancing is your best bet for realizing a decent FPS. DX doesn't like small vertex buffers and slows down dramatically when it is faced with loads of them.

Heh, I guess I should have checked that it was xna. 

 

If you are just going to be rendering cubes (rather than looking for a unified solution for instancing / drawing loads of arbitrary meshes ), I believe it would be faster to just have one large VB with loads of cubes inside, storing the cube index in the W component of the position for example (if SV_VertexID or equivalent is not available), and then controlling how many you render by specifying different primitive count in the draw call. You can then use the cube index to index to resolve you final vertex positions as discussed above. 
 



#5 Norman Barrows   Crossbones+   -  Reputation: 2322

Like
0Likes
Like

Posted 14 May 2013 - 08:45 AM

i'm able to do about 11,000 batches of 8 triangles at 30 fps dx9 fixed function.

 

if your cubes don't change every frame, create a static (NOT dynamic) vb and ib and fill it with cubes, then draw that. i use this trick for ground meshes drawn one quad at a time depending on texture. then on top of that, i draw 11,0000 plants, rocks, and alpha blended clouds (thats 11,000 visible on the screen, after all culling).  and thats all done with just 4 plant meshes, 2 rock meshes, and one billboard quad - drawn over, and over again. the trick is to draw them ordered on texture. that way you set each texture in the scene just once.

 

note that indexed will (usually/almost always?) be faster than non-indexed.


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#6 burnt_casadilla   Members   -  Reputation: 443

Like
0Likes
Like

Posted 14 May 2013 - 10:36 AM

I read up on DrawUserIndexedPrimitives and I think its what I want because Im going to be drawing a lot of cubes eventually. But I have no idea how to use it within my cube class. I may just rewrite the class entirely lol. Also, the buffer will only change every time the player interacts with it so hopefully this will improve performance


Edited by burnt_casadilla, 14 May 2013 - 10:38 AM.

If you see a post from me, you can safely assume its C# and XNA :)


#7 phil_t   Crossbones+   -  Reputation: 4096

Like
1Likes
Like

Posted 14 May 2013 - 11:39 AM

DrawIndexedPrimitives (which uses a vertex buffer) will be significantly faster than DrawUserIndexedPrimitives (which sends all your vertex data to the GPU every time) if your cubes only change when the player interacts with them.



#8 Norman Barrows   Crossbones+   -  Reputation: 2322

Like
0Likes
Like

Posted 14 May 2013 - 05:42 PM

Also, the buffer will only change every time the player interacts with it so hopefully this will improve performance

 

sounds like a cube world. you'll want some sort of world map to tell you what cube is what and where. you use that to generate the static vb and ib. once at game start, and once every time the player changes the world. basically, you have a cube vb and ib in memory (your source mesh). then you have a big system memory vb and ib (your system mesh) that will hold lots of cubes. i use my own data structures for these. then you will have a big static directx or openGL vb and ib that you've allocated in default memory pool. you copy the source mesh into the system mesh multiple times, applying transforms as you go. then you lock the directx/openGL buffer and memcpy the whole system vb and IB to the directx/ openGL vb and ib. then draw to your heart's content til its time to regenerate.


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#9 burnt_casadilla   Members   -  Reputation: 443

Like
0Likes
Like

Posted 14 May 2013 - 10:09 PM

The world will be drawn from a text file:

 

"1, 1, 1, 1, 1, 1"

"1, 1, 1, 1, 1, 1"

"1, 1, 1, 1, 1, 1" 

 

that would be three rows of six cubes one cube high

 

new classes

class Cube
{
    private GraphicsDevice device;
    private VertexBuffer cubeVertexBuffer;

    public Cube(GraphicsDevice graphicsDevice)
    {
        device = graphicsDevice;

        var vertices = new List<VertexPositionTexture>();

        BuildFace(vertices, new Vector3(0, 0, 0), new Vector3(0, 1, 1));
        BuildFace(vertices, new Vector3(0, 0, 1), new Vector3(1, 1, 1));
        BuildFace(vertices, new Vector3(1, 0, 1), new Vector3(1, 1, 0));
        BuildFace(vertices, new Vector3(1, 0, 0), new Vector3(0, 1, 0));

        BuildFaceHorizontal(vertices, new Vector3(0, 1, 0), new Vector3(1, 1, 1));
        BuildFaceHorizontal(vertices, new Vector3(0, 0, 1), new Vector3(1, 0, 0));

        cubeVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, vertices.Count, BufferUsage.WriteOnly);

        cubeVertexBuffer.SetData<VertexPositionTexture>(vertices.ToArray());
    }

    private void BuildFace(List<VertexPositionTexture> vertices, Vector3 p1, Vector3 p2)
    {
        vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 1, 0));
        vertices.Add(BuildVertex(p1.X, p2.Y, p1.Z, 1, 1));
        vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 0, 1));
        vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 0, 1));
        vertices.Add(BuildVertex(p2.X, p1.Y, p2.Z, 0, 0));
        vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 1, 0));
    }

    private void BuildFaceHorizontal(List<VertexPositionTexture> vertices, Vector3 p1, Vector3 p2)
    {
        vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 0, 1));
        vertices.Add(BuildVertex(p2.X, p1.Y, p1.Z, 1, 1));
        vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 1, 0));
        vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 0, 1));
        vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 1, 0));
        vertices.Add(BuildVertex(p1.X, p1.Y, p2.Z, 0, 0));
    }

    private VertexPositionTexture BuildVertex(float x, float y, float z, float u, float v)
    {
        return new VertexPositionTexture(new Vector3(x, y, z), new Vector2(u, v));
    }

    public void Draw( BasicEffect effect)
    {
        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            device.SetVertexBuffer(cubeVertexBuffer);
            device.DrawPrimitives(PrimitiveType.TriangleList, 0, cubeVertexBuffer.VertexCount / 3);
        }
    }
}

 

cube class

 

public class DrawableList<T> : DrawableGameComponent
{
    private BasicEffect effect;
    private Camera camera;
    private class Entity
    {
        public Vector3 Position { get; set; }
        public Matrix Orientation { get; set; }
        public Texture2D Texture { get; set; }
    }

    private Cube cube;
    private List<Entity> entities = new List<Entity>();

    public DrawableList (Game game, Camera camera, BasicEffect effect) 
        : base( game ) 
    {
        this.effect = effect;
        cube = new Cube (game.GraphicsDevice);
        this.camera = camera;
    }

    public void Add( Vector3 position, Matrix orientation, Texture2D texture )
    {
        entities.Add (new Entity() { 
            Position = position,
            Orientation = orientation,
            Texture = texture
        });
    }

    public override void Draw (GameTime gameTime )
    {
        base.Draw (gameTime);

        foreach (var item in entities) {

            effect.VertexColorEnabled = false;
            effect.TextureEnabled = true;
            effect.Texture = item.Texture;

            Matrix center = Matrix.CreateTranslation(new Vector3(-0.5f, -0.5f, -0.5f));
            Matrix scale = Matrix.CreateScale(0.05f);
            Matrix translate = Matrix.CreateTranslation(item.Position);

            effect.World = center * scale * translate;
            effect.View = camera.View;
            effect.Projection = camera.Projection;

            cube.Draw (effect);
        }
    }
}
camera = new Camera (graphics.GraphicsDevice);
        effect = new BasicEffect (graphics.GraphicsDevice);
        cubes = new DrawableList<Cube> (this, camera, effect);

        Components.Add (cubes);

        for (int i=0 ; i < 50; i++)
        {
            cubes.Add (new Vector3( i*0.5f, 50.0f, 50.0f), Matrix.Identity, logoTexture);
        }

If you see a post from me, you can safely assume its C# and XNA :)


#10 NightCreature83   Crossbones+   -  Reputation: 3033

Like
0Likes
Like

Posted 15 May 2013 - 05:13 AM

That works but in those cases instancing is a far better solution as you already know how many cubes you are going to render, and you will only issue one drawcall, which means effect setup is only run once as well. And instead of setting the World param on each shader effect you pass this into a VB, removing an object then becomes axing the entity and not building that matrix into the instance data buffer. You can use multiple textures and an index passed through the instance data if you need to modify the texture on particular objects that are drawn through instancing.

 

Another minor optimisation in your code would be to calculate the center and scale combined matrix only once and storing that as a static variable of the DrawableList class if you are not intending to change it.


Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, Mad Max

#11 VladR   Members   -  Reputation: 722

Like
0Likes
Like

Posted 16 May 2013 - 10:54 AM

note that indexed will (usually/almost always?) be faster than non-indexed

The reason Indexed rendering is faster than Non-Indexed is due to the post-transform vertex cache.

 

But you do have to make an effort to traverse the vertex buffer in a way that maximizes the usage of the post-transform vertex cache. It's not automatic and this is exactly what gets confused all the time when people say they are using Indices, ergo it is faster.
If your index list does not reuse the same vertices within next ~20 indices, you're not making use of a post-transform cache anyway.

 

What was the last time you benchmarked it, anyway ?


VladR    My 3rd person action RPG on GreenLight:    http://steamcommunity.com/sharedfiles/filedetails/?id=92951596

 


#12 burnt_casadilla   Members   -  Reputation: 443

Like
0Likes
Like

Posted 16 May 2013 - 09:51 PM

I successfully drew a 50x50 area one cube high. FPS is at a whopping 11 -.- so I obviously did something very wrong lol


If you see a post from me, you can safely assume its C# and XNA :)


#13 VladR   Members   -  Reputation: 722

Like
1Likes
Like

Posted 17 May 2013 - 09:05 AM

I successfully drew a 50x50 area one cube high. FPS is at a whopping 11 -.- so I obviously did something very wrong lol

I don't think so. This is your first working version that is doing something that you wanted. Do not ever delete it or break it. You will break the functionality plenty times later - but it is very important that you have something working anytime you need to go back and figure out how you did it last time.

 

 

Easiest way to do it, is just to copy paste the current render method into another (say, named "Render2 () ) and keep messing with the new one.

 

 

Now, to address the performance, let's check the most obvious issues first:

1. Check that you only bind the VB/IB every frame (not recreate VB/IB every frame).

 

2. What is the number of draw calls ? 50x50x1 would imply 2,500 draw calls - which is a lot, for sure. Can you try 10x10, 20x20 and post the framerate ?

The first thing to do here, is to create a version where you have all cubes in a single VB / IB and render that in one Draw call - and post the framerate then.


VladR    My 3rd person action RPG on GreenLight:    http://steamcommunity.com/sharedfiles/filedetails/?id=92951596

 


#14 burnt_casadilla   Members   -  Reputation: 443

Like
0Likes
Like

Posted 17 May 2013 - 11:39 AM

10x10 works fine, fps stays at 60. How do I check if the vertex buffer is only being called once?

 

public override void Draw(GameTime gameTime)
            {
                base.Draw(gameTime);

                foreach (var item in entities)
                {

                    effect.VertexColorEnabled = false;
                    effect.TextureEnabled = true;
                    effect.Texture = item.Texture;

                    Matrix center = Matrix.CreateTranslation(new Vector3(-0.5f, -0.5f, -0.5f));
                    Matrix scale = Matrix.CreateScale(1f);
                    Matrix translate = Matrix.CreateTranslation(item.Position);

                    effect.World = center * scale * translate;
                    effect.View = camera.View;
                    effect.Projection = camera.Projection;

                    cube.Draw(effect);
                }

 

That method is getting called in my draw method in my game class

 

 public void Draw(BasicEffect effect)
    {
        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            device.SetVertexBuffer(cubeVertexBuffer);
            device.DrawPrimitives(PrimitiveType.TriangleList, 0, cubeVertexBuffer.VertexCount / 3);
        }
    }

And this method is getting called by the method above. I think this one is the problem. Its setting the VB every time its called I think


If you see a post from me, you can safely assume its C# and XNA :)


#15 Norman Barrows   Crossbones+   -  Reputation: 2322

Like
0Likes
Like

Posted 17 May 2013 - 09:26 PM

foreach (var item in entities)

...
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
...

device.SetVertexBuffer(cubeVertexBuffer);

 

thats the killer tight there.

 

you want to put all the cubes in one VB and call some sort of draw_my_vb() routine once.

 

so i guess in OO terms, you'll want a vb object, and you'll want a version of your cube draw method that "draws" cubes into your vb object or calls some "add_cube" method of the vb_object. then your vb object has its own draw method that talks to directx, and sends all its cubes at once.


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#16 VladR   Members   -  Reputation: 722

Like
0Likes
Like

Posted 18 May 2013 - 06:54 AM

10x10 works fine, fps stays at 60.

It looks like you have VSync On. Disable it first at the method where you construct the graphics object (and set the resolution), if you want some reasonable approximation of the performance (a frame time would be more precise). The framerate should skyrocket, depending on your HW configuration,. to 1000's.

 

As I said few posts back, now you need to create a single VB/IB. That means:

- pre-transforming the vertices on CPU when you fill the VB, since you will be able to set the World Matrix only once per VB - thus moving whole terrain at once (not the individual blocks). This is easy, if you don't do any rotation, just translation - just set the vertex position to (CubeX * Spacing, CubeY * Spacing, -CubeZ * Spacing)

- when rendering, you just set the VB/IB once and render it using single Draw command. With Vsync off, the FPS should be really high.


VladR    My 3rd person action RPG on GreenLight:    http://steamcommunity.com/sharedfiles/filedetails/?id=92951596

 


#17 burnt_casadilla   Members   -  Reputation: 443

Like
0Likes
Like

Posted 18 May 2013 - 11:59 AM

I used this line of code

 

graphics.SynchronizeWithVerticalRetrace = false;

 

To disable VSync and it didn't help the fps while running a 50x50. When you're talking about only calling the Vbuffers once, I'm assuming you mean create an object VBuffer and replace it with the foreach loop in each draw method? I'm a little confused on what you mean at your first bullet point


If you see a post from me, you can safely assume its C# and XNA :)


#18 VladR   Members   -  Reputation: 722

Like
0Likes
Like

Posted 18 May 2013 - 12:39 PM

Of course, Vsync surely couldn't have helped the framerate when rendering 50x50. But where did the framerate skyrocket when rendering 10x10 or 5x5 ?

 

As for the VB - right now, you keep 50x50 VBs, right ? Each of them has 8 vertices and 24 triangles. To render it all, you need to switch the VB 2,500 times and call a Draw method 2,500 times.

 

With the single VB approach, however, you will fill it with 50x50x8 vertices. Your IB will have 50x50x6 indices. Then, you will have only a single Draw call and all 2,500 cubes will get rendered from that VB/IB.


VladR    My 3rd person action RPG on GreenLight:    http://steamcommunity.com/sharedfiles/filedetails/?id=92951596

 


#19 phil_t   Crossbones+   -  Reputation: 4096

Like
1Likes
Like

Posted 18 May 2013 - 01:51 PM

foreach (var item in entities)

...
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
...

device.SetVertexBuffer(cubeVertexBuffer);

 

thats the killer tight there.

 

 

Actually, if that's the same vertex buffer each time, XNA will optimize that call out, so it only make the underlying SetStreamSource call once (you can check this in PIX). So I doubt that line is specifically the problem.

 

But separate Draw calls for each cube probably are the killer.



#20 burnt_casadilla   Members   -  Reputation: 443

Like
0Likes
Like

Posted 18 May 2013 - 03:36 PM

Would it make more sense for me to just combine the two classes?


If you see a post from me, you can safely assume its C# and XNA :)





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS