Skip to content

Conversation

1bsyl
Copy link
Contributor

@1bsyl 1bsyl commented Mar 16, 2021

Second attempt to render triangles with SDL, adding SDL_RenderGeometry

In this version, triangles can be non uniform, with a per vertex color.
Textures can also have a per vertex modulation.

Using the adviced prototype:

typedef struct SDL_Vertex{
    SDL_FPoint position;        /**< Vertex position, in SDL_Renderer coordinates  */
    SDL_Color  color;           /**< Vertex color */
    SDL_FPoint tex_coord;       /**< Normalized texture coordinates, if needed */
} SDL_Vertex;

/**
 *  \brief Render a list of triangles, optionally using a texture and indices into the vertex array
 *  Color and alpha modulation is done per vertex (SDL_SetTextureColorMod and SDL_SetTextureAlphaMod are ignored).
 *
 *  \param texture      (optional) The SDL texture to use.
 *  \param vertices     Vertices.
 *  \param num_vertices Number of vertices.
 *  \param indices      (optional) An array of integer indices into the 'vertices' array, if NULL all vertices will be rendered in sequential order.
 *  \param num_indices  Number of indices.
 *
 *  \return 0 on success, or -1 if the operation is not supported
 */
extern DECLSPEC int SDLCALL SDL_RenderGeometry(SDL_Renderer *renderer,
                                               SDL_Texture *texture,
                                               SDL_Vertex *vertices, int num_vertices,
                                               int *indices, int num_indices);

Description

Back-end added: Software, Opengl, OpenGLES2, D3D11, METAL, D3D9, OpengGLES
Metal and OpenGLES2 are modified more internally so that 'color' isn't uniform anymore but an attribute.
Software Renderer tries also to reconstruct SDL_Rect from Vertex, and to use faster SDL_RenderCopy when possible.

I've checked that it integrates correctly with DearImGui. (see example files here: ocornut/imgui#3926 )
And also with Spine (using spine-c runtime)
With Nuklear ( Immediate-Mode-UI/Nuklear#280 )
With Librocket (libRocket/libRocket#301 )
With Microui ( rxi/microui#48 )

Some parts are used from the various patches

Existing Issue(s)

Referencing
#772 : SDL_RenderGeometry
#692 : Implement polygon rendering

@1bsyl 1bsyl changed the title Render triangles (with per vertex color) Triangles rendering (with per vertex color) Mar 16, 2021
@1bsyl
Copy link
Contributor Author

1bsyl commented Mar 22, 2021

Hey @slime73 @ligfx @rmg-nik

Any new opinions or use-case about this new draft patch that use the vertex color ?

I've been able to get it work with spine (with lots of code interface taken/modified from another repository),
but it can fly some dragon animation.

With Imgui, that is much more easier to integrate:
My ~10 years old laptop runs Imgui / with opengles2 / no vsync / 800 imgui triangles per frame, at 400 fps.
And with rmg-nik batching improvement (eg merging copy/geometry command into 1 big glDrawArray(GL_TRIANGLES) ), it goes at 800.fps.

But, the issue with current function interface is that it requests to draw triangles 1 per 1 (even they are batched afterwards).
Changing prototype to:

  int  SDL_RenderGeometry(SDL_Renderer *renderer,
                                                SDL_Texture *texture,
                                                float *xy, int xy_stride,
                                                int *color, int col_stride,
                                                float *uv, int uv_stride,
                                                int num_vertices,
                                                void *indices, int num_indices, int size_indice);

Allows to provide separates pointers for position/color/texture, with their own stride, and sending indices as int/short/char.
It's more flexible and then imgui can just call the function once to send all the data.
And it goes up to 1050 fps.

Software rendering isn't too bad since it draws at 40 fps for the same example (window + demo window, nothing selected). But it can drop when adding more windows, or resizing.

Edit: add software rendering stats

@rmg-nik
Copy link
Contributor

rmg-nik commented Mar 23, 2021

@1bsyl Hi
It seems to me that there should be a trade-off between performance and usability.
An array of structures and a function with 4 parameters are easier to understand and use than a set of arrays and a function with 12 arguments.
If you need performance, you should always create a custom renderer for your pipeline and requirements.
Otherwise, the first letter in the SDL will cease to be "simple".
But again - this is my personal opinion ..)

@1bsyl
Copy link
Contributor Author

1bsyl commented Mar 23, 2021

hi @rmg-nik

Thanks for the feedback.
I agree the API could be simple, but if it gets integrated with imgui for instance, simple means also fewer lines of code and less data manipulations (mostly convert raw data to SDL_Vertex). With the 12 parameters raw prototype, it becomes straight forward as 1 function call.

Though a vertex API api could still be added, simply calling with :
xy_stride = color_stride = uv_stride = sizeof(SDL_vertex);
yx = ptr_vertex;
color = yx + 4;
uv = yx + 8;
size_indice = 4;

@akk0rd87
Copy link
Contributor

Guys, should this feature be merged into SDL master branch? Or we should forget about spine runtime integration?

@rmg-nik
Copy link
Contributor

rmg-nik commented Mar 25, 2021

It seems to me that before accepting anything, it is necessary to discuss all the points and hear the opinion of the main developers of the SDL.
For example, I believe that the method for drawing triangles should be as easy to use as possible and similar to the existing SDL functionality.
The proposed API SDL_RenderGeometry already has a lot of complexity and differences (the entire SDL uses non-normalized texture coordinates, but SDL_RenderGeometry uses normalized ones). The API must be consistent.
My proposal introduces some overhead and in some cases forces the developer to add some kind of cycle for packing the vertices, but the API does not raise any additional questions.
Therefore, I would like to hear the opinion of leading maintainers.

@1bsyl
Copy link
Contributor Author

1bsyl commented Mar 26, 2021

With previous tries / PR , it shows how to use the SDL_RenderGeometry to run:
Nuklear ( https://github.com/Immediate-Mode-UI/Nuklear )
libRocket ( https://github.com/libRocket/ ) .. which was actually the first library used by Gabriel Jacobo to propose the SDL_RenderGeometry patch

@rmg-nik
Copy link
Contributor

rmg-nik commented Mar 26, 2021

@1bsyl @akk0rd87 Hi
I made a simple demo https://github.com/rmg-nik/sdl_spine_demo where I download all dependencies (you have to run download_dependencies.sh the first time) and demonstrate how to use my API with Spine.

@akk0rd87
Copy link
Contributor

@rmg-nik, thanks. I'll try it at weekend.

@1bsyl
Copy link
Contributor Author

1bsyl commented Mar 26, 2021

@rmg-nik, thanks ! got it working with my sdl build
I have also tested previously an example of spine modified from this repository: https://github.com/GerogeChong/spine-sdl

@rmg-nik
Copy link
Contributor

rmg-nik commented Mar 27, 2021

@1bsyl My Spine example is based on https://github.com/GerogeChong/spine-sdl but I've simplified this code a lot for demonstration purposes.

@1bsyl
Copy link
Contributor Author

1bsyl commented Apr 1, 2021

With previous commit, with Dear ImGui + software renderer gets better results.
It draws:

  • by default at 250 fps
  • drops to 70 fps if you show the color picker
  • drops to 10 fps if put the color picker fullscreen

@baidwwy
Copy link

baidwwy commented Apr 1, 2021

Hello, is there any plan to add D3D9?

@1bsyl
Copy link
Contributor Author

1bsyl commented Apr 1, 2021

@baidwwy I have no D3D9 build set up. But in the @rmg-nik inital patch, there was a D3D9 implementation.
If you can test this branch, I can try to commit something. Let me know

@1bsyl
Copy link
Contributor Author

1bsyl commented Apr 1, 2021

@baidwwy just added the D3D9, but not tested

@rmg-nik
Copy link
Contributor

rmg-nik commented Apr 2, 2021

@1bsyl Hi
Have you compared performance on a Mac with different renders? I did and got a little weird result.

Mac-mini:demo user$ ./demo
2021-03-26 15:38:31.612 demo[16930:23276659] INFO: Renderer: metal
2021-03-26 15:38:32.625 demo[16930:23276659] INFO: FPS: 259
2021-03-26 15:38:33.626 demo[16930:23276659] INFO: FPS: 445
2021-03-26 15:38:34.626 demo[16930:23276659] INFO: FPS: 461
2021-03-26 15:38:35.627 demo[16930:23276659] INFO: FPS: 466
2021-03-26 15:38:36.627 demo[16930:23276659] INFO: FPS: 455
...
Mac-mini:demo user$ ./demo
2021-03-26 15:39:17.744 demo[16996:23277225] INFO: Renderer: opengl
2021-03-26 15:39:18.757 demo[16996:23277225] INFO: FPS: 1324
2021-03-26 15:39:19.757 demo[16996:23277225] INFO: FPS: 2152
2021-03-26 15:39:20.757 demo[16996:23277225] INFO: FPS: 2235
2021-03-26 15:39:21.757 demo[16996:23277225] INFO: FPS: 2233
2021-03-26 15:39:22.757 demo[16996:23277225] INFO: FPS: 2241

@atticus5
Copy link

atticus5 commented Apr 2, 2021

@icculus Could you please offer some feedback/direction for this? Thanks!

@icculus
Copy link
Collaborator

icculus commented Apr 2, 2021

@icculus Could you please offer some feedback/direction for this? Thanks!

I believe we're planning to merge this shortly.

@1bsyl
Copy link
Contributor Author

1bsyl commented Apr 2, 2021

@rmg-nik
I've just checked that with a very basic triangle test-case, and I also see a big difference (metal:1000 FPS vs opengl: 6000 FPS).
but:

  • using only SDL_RenderCopy shows the same difference
  • reverting to the previous METAL version also have the same difference.
    So, this may be a bug, but doesn't seem to be a regression.

*/
extern DECLSPEC int SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer,
SDL_Texture *texture,
const float *xy, int xy_stride,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect these parameters to use the same types as the ones in the SDL_Vertex struct (eg an array of SDL_Color instead of an array of int).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SDL_Color are int anyway.
SDL_RenderGeometryRaw is added for app that are non SDL (eg: librocket, dear imgui, nuklear, spine).
So it's better to present them a basic type than something they don't know, they will need to look for, and that are going to cast anyway.

If you want to draw triangles with an SDL app, more friendly to use SDL_Vertex and SDL_RenderGeometry.

*
* \return 0 on success, or -1 if the operation is not supported
*/
extern DECLSPEC int SDLCALL SDL_RenderGeometryRaw(SDL_Renderer *renderer,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there much point to having this function considering all the vertex attributes are mandatory (you can't pass in nulls)? Flexibility is nice I guess, but IMO API flexibility should come from real-world use cases after determining simpler APIs can't satisfy specific needs rather than providing a kitchen sink in the hope that people will actually need everything there.

Personally I'd prefer it if just SDL_RenderGeometry existed - there's pros and cons to accepting an array of structures versus multiple independent arrays, but picking one of the two and sticking with it feels more in line with SDL_Render's simple design philosophy compared to lower level graphics APIs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having just a single simpler / less catch-all external API will also help keep backend implementations simple too. For example the ability to memcpy an array of SDL_Vertex structs directly, and pass that all the way through to the GPU without looping over each vertex, is possible on several backends if the higher level API isn't too loose with its constraints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(uv can be null, indices can be null)
After trying to integrate the SDL_vertex with real-world libs, it appears I need to allocate/loop/re-assign the datas.
So this the common denominator when integrating with the tested libs. The simpliest if you don't want to re-alloc and re-order datas.

SDL_RenderGeometry and SDL_RenderGeometryRaw share the same backend interface. (SDL_RenderGeometry is just 5 lines)

Copy link

@TerensTare TerensTare May 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to rename this to SDL_RenderGeometryEx just to have a consistent naming (we have SDL_RenderCopyEx)?

int j;
float *xy_;
SDL_Color col_;
if (size_indice == 4) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better to pass indices directly into graphics API backends instead of doing CPU-side remapping. Every GPU backend supports hardware-accelerated indexed triangle rendering. There a couple considerations though:

  • uint8 indices aren't supported by most modern graphics APIs. Personally I wouldn't bother supporting them.
  • uint32 indices aren't supported by some GLES devices. They're still useful to support natively but you might need a fallback like this, just for the GLES backend (I don't know whether D3D9 and D3D11 have a feature query for that, too).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the infos about int8/32
Actually: librocket uses 'int' and imgui 'short'. Hence this size parameter.
(And I just added the extra uint8 possibility just in case ...)

I agree, it's better to pass indices directly. But this is more work for this PR, and this remains a possibility for improvement.
(Btw, this is the reason why indice/size/int is sent the back-end and not converted in SDL_render.c)

@@ -279,26 +281,35 @@ - (void)dealloc

switch (cache->vertexFunction) {
case SDL_METAL_VERTEX_SOLID:
/* position (float2) */
vertdesc.layouts[0].stride = sizeof(float) * 2;
/* position (float2), color (float4) */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colors are unsigned normalized 8 bit values in the higher level API, so you don't need the uint8 -> float conversion in backends like Metal where it's easy to pass the same type all the way through to the GPU.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the info, I think gles2 back-end can do the same with (glVertexAttribPointer with 'normalized' param).
I am not so familiar with metal to change this quickly, I'll do it probably when I have some spare time. (no need to change the shader, right ?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. It's not a big deal either way, it just might simplify some code (or it might add more complexity elsewhere, I'm not sure).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just modified this way ..

@aganm
Copy link

aganm commented Apr 11, 2021

I know you made the change, but if I may chip in, I would prefer to have colors be floats instead of int. Since this is a lower level API, the user should expect to work closer to the metal when using it.

More importantly, for multithreading, I always want to compute all the required data on threads then feed the raw data to the renderer on the main thread so there's as little blocking as possible. By taking float colors, I can easily do the conversion from SDL_Color myself if needed.

Ideally, SDL_RenderGeometryRaw wouldn't do any transformation whatsoever, just take the raw data and feed it to the GPU without touching it. And we can still have a highler level SDL_RenderGeometry that takes an SDL_Color type and does the conversion for SDL_RenderGeometryRaw.

@slime73
Copy link
Contributor

slime73 commented Apr 12, 2021

Since this is a lower level API, the user should expect to work closer to the metal when using it.

From a GPU perspective, normalized byte colour components are exactly as close to the metal as float colour components. GPUs will take either one natively. I'm not sure whether the software renderer backend could take float colours natively or not.

If you're suggesting the RenderGeometryRaw API should have a parameter specifying the vertex format, that's not unheard of for similar APIs but also seems a bit complicated.

@1bsyl 1bsyl force-pushed the br_triangles_multicolor branch from 1dc443c to 018541b Compare August 18, 2021 22:05
@1bsyl 1bsyl merged commit 154384a into libsdl-org:main Aug 18, 2021
@1bsyl
Copy link
Contributor Author

1bsyl commented Aug 18, 2021

@slouken: just re-based and committed !

@sezero
Copy link
Contributor

sezero commented Aug 18, 2021

Android builder in buildbot is showing a lot of -Wshorten-64-to-32
warnings: http://buildbot.libsdl.org/#/builders/24/builds/830

I also suggest using intptr_t instead of size_t in SDL_OFFSETOF

I suggest the following, but can't test it - is it OK?

diff --git a/src/render/SDL_render.c b/src/render/SDL_render.c
index a4e59e3..77c0c6e 100644
--- a/src/render/SDL_render.c
+++ b/src/render/SDL_render.c
@@ -3343,7 +3343,7 @@ SDL_RenderCopyExF(SDL_Renderer * renderer, SDL_Texture * texture,
 }
 
 
-#define SDL_OFFSETOF(_TYPE,_MEMBER)  ((size_t)&(((_TYPE*)0)->_MEMBER))
+#define SDL_OFFSETOF(_TYPE,_MEMBER)  ((intptr_t)&(((_TYPE*)0)->_MEMBER))
 int
 SDL_RenderGeometry(SDL_Renderer *renderer,
                                SDL_Texture *texture,
diff --git a/src/render/software/SDL_triangle.c b/src/render/software/SDL_triangle.c
index 8c9bc63..ad1812d 100644
--- a/src/render/software/SDL_triangle.c
+++ b/src/render/software/SDL_triangle.c
@@ -156,21 +156,21 @@ static void bounding_rect(const SDL_Point *a, const SDL_Point *b, const SDL_Poin
 
 /* Use 64 bits precision to prevent overflow when interpolating color / texture with wide triangles */
 #define TRIANGLE_GET_TEXTCOORD                                                                          \
-                    int srcx = ((Sint64)w0 * s2s0_x + (Sint64)w1 * s2s1_x + s2_x_area.x) / area;        \
-                    int srcy = ((Sint64)w0 * s2s0_y + (Sint64)w1 * s2s1_y + s2_x_area.y) / area;        \
+                    int srcx = (int)(((Sint64)w0 * s2s0_x + (Sint64)w1 * s2s1_x + s2_x_area.x) / area); \
+                    int srcy = (int)(((Sint64)w0 * s2s0_y + (Sint64)w1 * s2s1_y + s2_x_area.y) / area); \
 
 #define TRIANGLE_GET_MAPPED_COLOR                                                                       \
-                    int r = ((Sint64)w0 * c0.r + (Sint64)w1 * c1.r + (Sint64)w2 * c2.r) / area;         \
-                    int g = ((Sint64)w0 * c0.g + (Sint64)w1 * c1.g + (Sint64)w2 * c2.g) / area;         \
-                    int b = ((Sint64)w0 * c0.b + (Sint64)w1 * c1.b + (Sint64)w2 * c2.b) / area;         \
-                    int a = ((Sint64)w0 * c0.a + (Sint64)w1 * c1.a + (Sint64)w2 * c2.a) / area;         \
+                    int r = (int)(((Sint64)w0 * c0.r + (Sint64)w1 * c1.r + (Sint64)w2 * c2.r) / area);  \
+                    int g = (int)(((Sint64)w0 * c0.g + (Sint64)w1 * c1.g + (Sint64)w2 * c2.g) / area);  \
+                    int b = (int)(((Sint64)w0 * c0.b + (Sint64)w1 * c1.b + (Sint64)w2 * c2.b) / area);  \
+                    int a = (int)(((Sint64)w0 * c0.a + (Sint64)w1 * c1.a + (Sint64)w2 * c2.a) / area);  \
                     int color = SDL_MapRGBA(format, r, g, b, a);                                        \
 
 #define TRIANGLE_GET_COLOR                                                                              \
-                    int r = ((Sint64)w0 * c0.r + (Sint64)w1 * c1.r + (Sint64)w2 * c2.r) / area;         \
-                    int g = ((Sint64)w0 * c0.g + (Sint64)w1 * c1.g + (Sint64)w2 * c2.g) / area;         \
-                    int b = ((Sint64)w0 * c0.b + (Sint64)w1 * c1.b + (Sint64)w2 * c2.b) / area;         \
-                    int a = ((Sint64)w0 * c0.a + (Sint64)w1 * c1.a + (Sint64)w2 * c2.a) / area;         \
+                    int r = (int)(((Sint64)w0 * c0.r + (Sint64)w1 * c1.r + (Sint64)w2 * c2.r) / area);  \
+                    int g = (int)(((Sint64)w0 * c0.g + (Sint64)w1 * c1.g + (Sint64)w2 * c2.g) / area);  \
+                    int b = (int)(((Sint64)w0 * c0.b + (Sint64)w1 * c1.b + (Sint64)w2 * c2.b) / area);  \
+                    int a = (int)(((Sint64)w0 * c0.a + (Sint64)w1 * c1.a + (Sint64)w2 * c2.a) / area);  \
 
 
 #define TRIANGLE_END_LOOP                                                                               \

@1bsyl
Copy link
Contributor Author

1bsyl commented Aug 19, 2021

@sezero
Thanks
I'll double check with my test case. Because the 64bits is really needed not to lose precision

@AJenbo
Copy link
Contributor

AJenbo commented Aug 19, 2021

Is this not supported when the Vulkan backend is in use?

@sezero
Copy link
Contributor

sezero commented Aug 19, 2021

I'll double check with my test case. Because the 64bits is really needed not to lose precision

The patch only adds an explicit int cast onto the whole result to avoid the warning
otherwise the implicit conversion is already happening.

@1bsyl
Copy link
Contributor Author

1bsyl commented Aug 19, 2021

@AJenbo This an API to use with the SDL renderer (SDL_renderer), and there is no Vulkan backend with it, so it's just not applicable. (I think @icculus has one SDL_renderer/Vulkan in development, but it's not in the main branch)

@sezero : ok, then feel free to commit it, and I'll double-check (later)

@sezero
Copy link
Contributor

sezero commented Aug 19, 2021

ok, then feel free to commit it, and I'll double-check (later)

Applied as 8270172

@1bsyl
Copy link
Contributor Author

1bsyl commented Aug 19, 2021

ok, then feel free to commit it, and I'll double-check (later)

Applied as 8270172

I've double checked and this is ok, thanks

@icculus
Copy link
Collaborator

icculus commented Aug 20, 2021

I think @icculus has one SDL_renderer/Vulkan in development, but it's not in the main branch

I had the barest of starts, a long time ago. It's definitely worth finishing at some point, but it's not even close right now.

@royalstream
Copy link

For those interested in Spine, I modified the spine-sfml runtime to use SDL2 instead of SFML using the wonderful SDL_RenderGeometry.

The call to SDL_RenderGeometry is equivalent to the one rmg-nik did. I'm providing spine-c and spine-cpp versions as well as working examples (matching the spine-sfml folders) and basic documentation:

https://github.com/royalstream/spine-sdl

@1bsyl
Copy link
Contributor Author

1bsyl commented May 12, 2022

Information about SDL integration with other libraries can be found there: https://wiki.libsdl.org/Libraries

@1bsyl 1bsyl deleted the br_triangles_multicolor branch February 6, 2023 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.