Linux port tutorial for "Handmade Hero" using SDL |
David Gow
Guest
|
Hey all,
I've been writing a tutorial on how to port Casey Muratori's "Handmade Hero" game project to Linux, and am using SDL quite heavily. "Handmade Hero" is a set of videos detailing, line-by-line, the development of a game in C: http://handmadehero.org/ The Linux tutorial, which uses SDL, is located here: http://davidgow.net/handmadepenguin/ Hopefully people who are learning to use SDL might find a few bits and pieces there useful. Cheers, — David _______________________________________________ SDL mailing list http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org |
|||||||||||
|
MrPhil
|
Worth noting that Handmade Hero is currently showing software render on purpose as a learning exercise. The plan is to do hardware acceleration later after software rendering is done.
|
|||||||||||
|
Alex
|
I don't speak about Casey Muratori's "Handmade Hero" - it is not positioned as SDL tutorial. May be this tutorial is made for learning software rendering as you said. I speak about this tutorial (David Gow's). This is two different tutorials and this one is based on previous. But this one is positioned as SDL tutorial from the start. Because of this reason I post here my opinion about this tutorial. I think developers, who started to learn SDL, should not reinvent the wheel. In my opinion good teacher must teach them best practices in SDL development. This is looks strange when someone teach me bad practices, but he plan to teach me good later. May be better to write in SDL tutorial: what is SDL_Surface, what fields in this srtuct, and how we can use this fields including pixel buffer. And explain why SDL_UpdateTexture is not best function to use every frame. And if we using software rendering (for learning or other reason), we should use SDL functions and structures for software rendering (not only for hardware accelerated). Imagine that someone read this tutorial and think: "this is best practices in SDL" or "this is only way to do this tasks in SDL". May be this developer start to develop with SDL using wrong practices. If author want to teach SDL development, he must use "SDL way" (all SDL functions, best rendering practices etc). If he don't teach SDL, why his tutorial looks like this is SDL tutorial? All of this is my own opinion. In all cases (even if I am wrong), I don't understand why author don't use SDL_Surface for software rendering (if he teach SDL development). |
|||||||||||||
|
Linux port tutorial for "Handmade Hero" using SDL |
Jared Maddox
Guest
|
If you go back and check, then you'll find that all that he's doing is explaining how to translate a base-layer from SOMEONE ELSE'S tutorial so that it works with SDL. Further, while we can't know what capabilities will be required since the tutorials are very early into their development (it's expected that the tutorials will likely require somewhere in the vicinity of a year AT MINIMUM), we DO know what the tutorials are using as their base layer: a backbuffer, in main-memory, which will be used to implement a CUSTOM software renderer. Using software rendering is therefor EXACTLY what should be done.
The game is liable to use pixel-to-pixel rendering, including any number of mixtures of bitwise ANDs, IORs, XORs, and comparisons. Readeing to and from main memory is highly prefered over reading and writing over the peripheral buses. Updating a single texture once per frame is NOT a realistic performance concern for any half-way sanely sized texture. Updating textures starts becoming insane on the basis of the combination of the AMOUNT of data moved and HOW OFTEN you move it.
Actually the SDL rectangle rendering functions might be faster, but that's just some VERY EARLY stuff so it doesn't even count for determining the correct way to do things.
No, don't do this. This is not some retro version of C, if you want it to be constant then use a const qualifier on a variable (even if that can be cast away).
Why would you put it in a struct? To avoid globals? Global variables don't steal your fingernails and toenails while you sleep to build ships for Loki to speed the onset of Ragnarok, so you shouldn't act as if they're boogiemen. They aren't ideal, no, but a global stuct IS a global variable. Besides, the stuff that makes sense being packaged up gets packaged up later. You should REALLY be watching the primary tutorials if you want to critique this stuff.
This is a small glue layer for someone else's tutorials, based on the glue layer that THEY are doing, but done in SDL2 instead of Win32. Doing it your way might aid in understanding SDL2, but it would ALSO add unjustified incompatibilities to the glue layers, while ALSO increasing the likelyhood of typos.
This I probably WOULD do, particularly since you CAN do an update from one if you're willing to dig in to it.
The tutorial writer DOESN'T want hardware acceleration. The intent of the original tutorials is PRECISELY to pass along the tips and tricks of old-aschool software renderers, so hardware rendering would get in the way. _______________________________________________ SDL mailing list http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org |
|||||||||||||||||||||||||||||||||
|
MrPhil
|
Thanks Jared!
|
|||||||||||
|
Linux port tutorial for "Handmade Hero" using SDL |
David Gow
Guest
|
Hi all,
entirely successfully) not to position this as an SDL tutorial, but rather as a tutorial, which happens to use SDL, for porting the Handmade Hero code. I'm trying to keep the code as close to the original as possible, so that people following Casey's much more detailed tutorials don't have to change things unnecessarily.
firstly, the original tutorial is not very worried about performance at this point (using the Win32 GDI's StretchDIBits is not exactly the fastest function, either); and secondly, this function is more than fast enough given that's we're uploading a single, screen-sized texture. I've shipped a few games using this method (albeit in OpenGL, rather than the SDL rendering API), and it was recommended in the SDL Wiki's Migration Guide.
"idiomatic" ways to do things, and have just updated Chapter 5 to mention SDL_Surface as an option for those who are trying to write a game in SDL, rather than porting this existing one. As I'm writing these mostly during the hour-long Handmade Hero streams, there isn't time to go into these things in detail myself, but I've had several people email me to tell me that they have looked up and used SDL functions and features that I've simply mentioned in passing. Again, clearly I need to make it more obvious that this isn't intended to be an "SDL Tutorial". Thanks very much for reading the tutorial. As much as I've been trying to rebut all of your comments, they are very valuable, and will certainly affect how I continue to write it. Thanks, too, to Jared for your detailed response! Cheers, — David _______________________________________________ SDL mailing list http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org |
|||||||||||||||||
|
Alex
|
Ok, ok, everyone fight against me
I have not wish and time to make big discussion like this. I just post my own opinion about this tutorial. But I feel I must answer to some posts here. To Jared Maddox:
I agree, but you can use SDL_Surface: it contain memory buffer. You can use this buffer any way you like, including work with each pixel (pixel-to-pixel rendering) and bitwise operations.
And about peripheral buses. Every time you call SDL_UpdateTexture, you use peripheral buses. Many of other SDL functions use peripheral buses too. And if you use OpenGL functions you use peripheral buses. No way to output graphics to screen without using peripheral buses. You can make some rendering in main RAM, but you always use buses after this. And if you use hardware acceleration, which is much faster then rendering in RAM, when you use GPU and video memory to rendering, then you always use buses.
May be I use old-style code sometime (I think, I'm not), but show me please where in new C standard (ISO C99 for example) #define macro is prohibited? I try to use mostly C99 standard in my code. I use both #define macro constants and const qualifier, when I think this is better. I can use "const int BytesPerPixel = 4;", or with C99 types like "const int_fast8_t BytesPerPixel = 4;", or with SDL types like "const Uint8 BytesPerPixel = 4;" (in SDL_Surface Uint8 type used for bytes per pixel and bits per pixel), and I can use "#define BYTES_PER_PIXEL 4". And I can use enum's to make a set of constants. I learning programming on a source code of best (professional made) libraries (including, but not only, SDL ). Look to SDL source code: you can see many #define's in this code. This practice is not invented by me, I try to use best practices, which I learned reading professional source code.
Yes, I know this. I know we can't live without global vars. But this is common practice to make a struct with fields related to some "object". Global struct is better than tons of global vars, IMO. As I said above, I read sources of some professional software (many thanks to free software), and as I can see in most libraries (may be in all) this is common practice to use global structures instead of separate vars. As I can see in this tutorial, author in first chapter, started to use C++ (not a C), then he can make a class with all fields and methods what he need. I use C for developing and I use structures instead of classes and functions instead of methods. Look again to SDL code: all objects are structures. There are a lot of structures. But imagine all fields of this structs as a separate vars, then SDL goes to be very hard to use (may be impossible to use) library.
Hm, strange logic. Why should I watch primary tutorial? May be primary tutorial is very good, may be I watch it later, but I think I can start to read this tutorial even if I never watch original before. And I can write my opinion about this text what I reading. To David Gow: I have nothing against your tutorial, but I post here my opinion about how to make this tutorial better. This is my own opinion and as I see no one share my opinion . I think you can use SDL_CreateRGBSurface instead of malloc, but you can write about both ways. First way more SDL oriented and easy to use, second (malloc'ed buffer) you can use too: this is absolutely correct way. You can show both ways in your tutorial, if you want to show not only SDL oriented ways. I think original tutorial is good, but you don't need to keep your tutorial as close to the original as possible. You can follow the original, but add extra information above original. Show to your readers common SDL practices, which can replace that practices, used in original. You can show both ways: "SDL way" and "original-tutorial way". Make your readers think in correct directions, when they develop using SDL or even without SDL. Sometime I "reinvent a wheel" too, to better learn something, but this is not common practice in developing. One more example: look to SDL-Image library source code. The IMG_LoadTexture function (which load image from file to texture) use SDL_Surface as a buffer in the middle of IMG_Load (load from file function) and SDL_Texture. At first step it load an image from file to a surface with IMG_Load, next it use SDL_CreateTextureFromSurface to load image data to texture, after this it free surface (we don't need this surface anymore) and finally it returns a texture with image data. No one said: this is only way to do this - we can make all this in another way, but this way used in a library, this way is more "SDL way". PS: I have ported to SDL2 my own image file format. My function create a SDL_Surface with buffer size I need, then my code process image file and load pixel data immediate to surface pixel buffer (like IMG_Load). Next I can continue using this surface or use SDL_CreateTextureFromSurface to put this data to a texture. I can use malloc'ed buffer instead of surface and load data to texture with SDL_UpdateTexture, but I use surface because I need some fields from surface struct. Using surface free me from creating my own struct and creating malloc'ed buffer. For me surface is "all in one" solution, ready to use. Only one call to SDL_CreateRGBSurface function give me a struct with all fields I need and allocated pixel buffer. And I can use this surface later in my code if I need. Sorry for very long post And sorry if I made some mistakes in this text (English is not my native language). |
|||||||||||||||||||||||
|
MrPhil
|
It's because you are missing the intended spirit of Handmade Hero and a lot of your suggestions run counter to it. The primary goals is not to be a tutorial on modern game development or SDL2 practices but to show the "old school" way of doing things before graphics cards and SDL2. |
|||||||||||||
|
Linux port tutorial for "Handmade Hero" using SDL |
Jared Maddox
Guest
|
And on the preferability of SDL_Surface I did agree last time... but on further review and consideration I realized that allocating raw memory probably DID resemble more strongly the practice employed in the primary tutorial series. Regardless, were I writing a renderer myself I certainly would use a SDL_Surface.
I suppose I should give a better explanation. The operations that are likely to be performed are basically "anything that you can do in C". If you have render-to-texture support then this can be done in shaders, but doing that is a rather large leap, particularly since you have to translate between languages. You can get around that by doing reads and writes across the peripheral bus, but if you make very many of those then you rapidly approach the point where you do MORE peripheral bus transactions than if you had simply kept everything in memory in the first place. This is so much the case that the standard SDL2 advise is that if you'll be doing frequent modifications of a texture then instead of copying it from the GPU when you want to modify it, you should keep a copy in memory at all times, and only update it from the GPU copy if the GPU copy might have been modified, AND you need to modify it yourself. Some peripheral bus transactions can't be avoided with graphics, but by reducing the number that you use you can reduce your chances of negatively affecting OTHER peripheral bus transactions, such as those that the GPU driver might be performing in the background to move a texture from memory/disk back to the GPU (I don't know that it's common right now, but it's very possible for them to do). At the same time you reduce the chances of exceeding the peripheral bus's bandwidth, and simplify the code required. Conclusion? If you're doing this sort of rendering then you should start by keeping a copy of the texture in memory and just working with that.
You are using old-style code in this case. I don't think it's to the level of having a third argument in main(), though.
It's not PROHIBITED, otherwise you would have had an error during compilation at some point or another. Defines are done by a system that at the least is logically distinct from the actual compiler, and in some cases is a seperate program. The compiler I commonly use (a GCC branch) will pass info about defines from the preprocessor on to the compiler, which is useful for debugging, but it's still NOT a best practice: best practice converts THESE sorts of defines into const variables instead.
I try to restrain my use of define & friends to things that simply make the most sense in the preprocessor (this class of things is mostly include guards, code generators, and some feature/platform detection). I personally consider the preprocessor to be weaker than it should be (I'd like to see count-down loops, a for-each derivative with some inspiration from C++11 variadic templates, and a define syntax modelled on #if/#endif, which would be useful for e.g. automatically generating switch statements and array contents in conjunction with each other), but I still wouldn't use defines to replace cases where const variables can be successfully used instead (note that I might use them to INITIALIZE those consts, especially if I wanted that to be compile-time configurable, but I wouldn't use it as a replacement for them).
The only one I definitively advise against is using the define to REPLACE any of the others. The first two are conceptually the ideal cases if you just want A value instead of SEVERAL POSSIBLE values (in which case the enum is the way to go), but for historical reasons the third case makes sense as well.
Ironically enough, those very same libraries will often contain the sort of non-recomended practices that you're speaking against here! Honestly, "professional" often isn't the same as "clean", and "clean" seems to be what you're looking for. What I would actually recommend you seek out is "professional": just not the define thing, we have better practices than that now (it dates from a time when C didn't have the const qualifier).
It's too early into the codebase for tons of global vars to be an issue. Also, where you REALLY want to use a global struct instead of a global var is where you either have some logically distinct GROUPS of variables that you might want to have multiple sets of at a later point in time, so that you can simply make another instance of those variables then. The author of the original tutorials basically goes into this in one of the videos (maybe video 4? I forget). Regardles, I'm going to give you a piece of advice that the original Unix developers apparently followed: "If you do it more than once, consider writing a script for it". For the C/C++ case, that becomes "If you do it more than once, consider writing a struct, function, or class for it". Globals are globals, packaging them into structs when you'll neither have several instances or more understandable names is productively identical to slapping a coat of paint onto a car. You haven't actually changed anything, you've just told yourself that you have.
Some of them will be genuinely designed as objects: these are the ones that you should look to duplicating. Others will be designed as namespaces: this can be done, but if you can't concretely point to WHY you're doing it that way then I'd recommend AGAINST it, as it's usually a symptom of an anti-pattern (specifically, a symptom of an automatic knee-jerk reaction against globals: I believe this specific one came from a mixture of poor understanding of object-orientation, and experience with code bases that mostly or entirely used globals, sometimes due to the language involved lacking name scoping: even the goto statement has it's exceedingly, astonishingly rare, sensible use-cases).
It's not actually THAT hard to use (you replace pointers to objects with indexes into arrays: annoying, but actually practical), though it IS ill-advised if the variables really do have sufficient relation to deserve being an "object" (whatever you have to do in your language to actually implement it). The PARTICULAR problem in this case is that the changes that you called for in the code would have taken it further away from the tutorials. WELL DESIGNED implementation of your suggestion in a codebase that had already gone through all of the tutorials (including the many that haven't been done yet) would be good (unless you stuck with the advise to stick everything in a global struct: there's just no good reason to universally do that), but doing it in this case would just make the tutorial series more confusing.
The tutorials that you are critiquing are themselves a commentary on the ORIGINAL tutorials. Thus, to properly understand what THESE tutorials are trying to say you have to consider them in conjunction with the original tutorials, since THESE tutorials were never MEANT to be considered by themselves. You can certainly write your opinion of these tutorials in isolation, but the resulting critique will be mostly invalid due to the inherent disconnect. This is simply the way that things go within the context of inter-connected systems: understanding a part is NOT often enough to understand whether the part is well-designed.
This is the only part of this section of your post the I'll comment on. If I was designing an image format library I would probably base the interface on SDL_Surface as well (and if I couldn't, then I'd probably try to make a derivative of SDL_Surface that I COULD base it on), but I wouldn't incorporate this directly into the "core" API. Instead I would have the functions in question take pointers to the appropriate variables, and provide a seperate file that provided standardized wrapper functions that DID directly use SDL_Surface. The reason is simple: I have found that often you will want to use some particular library, but it's designed for another library that for some reason you want to avoid (example: you might be writing a plug-in for a program that uses something else), so you have to go pouring through the source code of the library (that you know from previous experience works perfectly fine), just for the sake of making minor modifications. It's basically to make it EASIER to not reinvent the wheel. I mention this because you seem to want your code to be inherently good, so I think you'll find the idea occasionally useful. _______________________________________________ SDL mailing list http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org |
|||||||||||||||||||||||||||||||||||||||||||||||||
|
Alex
|
I have posted before that I don't want (and have not possibilities) to make big discussion in this topic. This discussion is going to be offtopic. Sorry, Jared, no more answers.
Just my last words about #define: ok, as you said the SDL library developers using bad practices in development, then lets look to other libs, for example libc, zlib etc etc. Or look to linux kernel source (of course kernel developers is much better programmers than me ). Everywhere you can find macro constants. Is all of this software used bad practices (even kernel)? I don't mean you must use macro const everywhere, I mean this constants is not evil and not a problem, and not a source of errors if you correctly use it. Additionally the C99 standard allow me to use all types of constants (and I use it). If I make a function call like: "myfunction(MY_CONST);" or "myfunction(4);" this two calls absolutely equal (if MY_CONST is 4). Using macro constants is equal to using numeric constants (or other types, if you need). And every time you call something like: "SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO);", you use macro constants SDL_INIT_VIDEO and SDL_INIT_AUDIO. I must stop myself because I can't long discuss about this. And return back to the topic: If you really want to use clean old-style software rendering or you want to make your own software renderer, then stop using SDL_Texture and SDL_Renderer (this technologies is not a software rendering). Do your software rendering clean, like in this example. Make your own software renderer write to the window without a texture in the middle. |
|||||||||||
|