Here's also a regex approach for the shader file parsing: std::string shaders_str; // The shader file's contents as a string std::regex r("#type (.*)(?: | )((?:(?!#type )(?:.| | ))*)"); while(!shaders_str.empty()) { std::smatch match; std::regex_search(shaders_str, match, r); std::string shader_type = match[1]; std::string shader_source = match[2]; // Do whatever with the type and source shaders_str = match.suffix(); } Because everyone loves regex.
I have different approach to storing few shaders in 1 file, and I belive that my way is better. Instead of reading shader data you can add 1 line of code with some definition. So for example for pixel shader you add "#define _TYPE_PIXEL_SHADER ", and for vertex shader you add "#define _TYPE_VERTEX_SHADER ". And since glShaderSource accept array of strings, you can do it without modificating or copying orginal source. Then shader programmer will do #ifdef _TYPE_PIXEL_SHADER ... #endif. The biggest adventage from this method is reading shader errors. In your method once shader programmer get shader compile error with line number, that line is far off from actual line with error. In my method it's off by 1 line, witch is fine. That make shader programming much much easier. Other advantage is possibility to create common functions, definitions, variables, etc. This is also very huge thing. Your method can be easily changed to support common code at the top. But in my method it can be in any order, and sometimes we want it in different order. Or shader programmer can even use #ifdef inside function if he/she want to. Another thing is that in my method content of file is 100% correct GLSL code. So we can use generic GLSL minifier, or other tool for GLSL. The only advantage of your method over mine is that in your method you automaticly have information about witch shaders are in file, and witch arent. While I have to either enforce usage of certain shader types, or assume that shaders with certain compilation error doesn't exists, or detect type by search in shader source for string _TYPE_PIXEL_SHADER, _TYPE_VERTEX_SHADER, etc.
I liked your approach because now I can use .glsl extension without getting '#type' highlighted as a syntax error. Though I had to remove '#version' directive from shader files and put it into the define string like this: if (type == GL_VERTEX_SHADER) { shaderDefine = "#version 450 core #define VERTEX "; } else if (type == GL_FRAGMENT_SHADER) { shaderDefine = "#version 450 core #define FRAGMENT "; } Might be better to get correct version from opengl context but who cares
+TheChernoProject I think its time to show us how to create Unit Tests for a game engine. I believe they are very helpful and most of the people including me probably are struggling how to make tests specifically for a game engine. I know you have a video about tests but when it comes to a game engine things are more complicated...
Unit tests are for testing small units of your program and should work the same whether you're developing a game engine or anything else. The things you cannot test however is: 1. Code that is run asynchronously. You can only test single-threaded code (with exceptions of course). All OpenGL calls are actually just messages sent to the GPU, which the GPU will execute whenever it wants. Testing any code that includes an OpenGL call is thus not a good candidate for unit tests. 2. Non-deterministic code. If your code has behaviour that is random (or just can vary based on the state of an uncontrolled variable, like time), then it's not a good candidate for unit tests. 3. Visuals, audio, and other I/O operations. You could create other tests than unit tests. If you want to make sure your game engine is working correctly, you could set up larger scenes which you run from time to time to check if it looks correct.
@@naxaes7889 But you can make integrated test. Like unit tests, only that they run from inside the system (for example the game engine) instead of running in a different executable. Imagine tests which get called from the main loop of your game engine. Something like scripts, but the difference is that they get compile in Debug mode only and behave like unit tests. You can do a lot of crazy debugging with those.
@@nikoszervo Yes, there are many other good testing/debugging techniques than unit tests. What I meant was that if unit tests feel more difficult for a game engine than any other application, then you might be testing the wrong things. Unit tests should be small, easy and hardcoded. They are meant to test things like _"If I pass parameter X to function F, do I get Y?"_ or _"If I create object X and call method M, does its attribute change to Y?"._ Any more complicated than that, than maybe unit tests isn't the right tool (as usual, there are always exceptions).
Just wanted to add that in the case you were talking about reusing a vertex shader with multiple fragment shaders, we did that a lot for Cloudbuilt and Super Cloudbuilt. Almost all materials in the whole game uses one of 3 vertex shaders. There are those with very special vertex shaders of course, but one vertex shader can calculate more than it actually will use in the end. It will be automatically removed when liking the program. So we rather focused on the behavior and input we wanted from the vertex shader, and then calculated all kinds of outputs we wanted from that, as long as it didn't conflict with each other and didn't make the shader too complex to work with and so on. It was really useful for us. We had a button in our editor to derive a material, where we could select what to change and what to keep the same. So we could just select a material, drive, and only change the pixel shader. Then if we later patched or changed parts of how the vertex shader worked it applied to all that used it. Also, as we had a button to open the shader files in our preferred text editor from within the asset/level editor, it was no problem having them in multiple files. Now we are working on a new pipeline with SpirV though, as working with GLSL and HLSL conversions and optimizations for SCB was a bit bothersome (sometimes files getting out dated and so on). But we will probably look to keep the separate files and re-usability in our new pipeline if possible. Anyway, always interesting to hear other's perspective and experiences. Keep up the great work and this nice series. Would btw be nice if you had some C++ podcasts or something where you could talk over different perspectives and such with other devs now and then. I think that would help a lot, as a lot of beginners might find this channel and take all things that might be opinion based and subjective at times as facts. So it would be really nice to show them there are always more way to look at things that can be just as valid. And for experienced programmers it's also always interesting to see more perspectives and arguments in my opinion. And there are just too few good programming podcasts in general :D and judging from your production quality of the videos, I think you could pull it of well.
You could make the methods in rendercommand that should not be used by users private and a friend to renderer (for example rendercommand::init) so only the methods that should be used are accessible
It is indeed clear to have vertex and fragment shaders written in one file. However, plugins such as "GLSL language integration", which provides assistance for writing opengl shaders, seems to only support separate files for different shaders.
@@nashis5947 Sure but the problem is how to use them, how to avoid race conditions and deadlocks etc. there is a lot to learn, multithreading is one of the hardest things in all of programming.
I know we covered the shader compilation and program linking some episodes ago, but I still have a question from back then: On 32:00 Cherno is creating a variable of type GLuint called "program". He proceeds to making changes to it (compiling and attaching shaders to it), and then copies the value of "program" to m_RendererID - which supposedly means storing the value of "program" on another memory slot. So far I got it, but... Then he carries on to use glLink on the "program" (which is a variable that should disapear when scope ends, no?) and detaches shaders from it... Shouldn't we be working on m_RendererID instead? Thanks so much in advance for any help interpreting this!
ok my bad, at 34:18 he does move "m_RendererID = program;" all the way down to the end of the function - but before this whole abstraction it used to be before those steps^ (glLinkProgram and detach)
15:12 Note that this should be std::ios::in | std::ios::binary. (Instead of the comma). You are unintentionally passing third parameter to the constructor.
It looks like a Microsoft extension to support file sharing modes. When you open a file, you can have exclusive access to it, or allow other processes to read or write or delete (or some combo of these) the file while you have it open. In this case, it looks like they take the same 'shflag' values of "_fsopen()", which only controls share access to reading and writing the file.
For some reason when i build, I'm getting syntax errors in my Texture.glsl file, why is it trying to compile that? It still compiles successfully, but I have a bunch of errors.
Heyy cherno a big fan of yours im watching your c++ series for quite a long time and in one video you said that you're gonna come with a project in c++ very soon.when are you going to come with that project its been a long long time please upload it as soon as possible it will be extremely helpful. Thank youu
@Cherno - you haven't configured your updates correctly if you're getting prompted for restart while you work. That's not Windows' fault, that's your own fault. I never see that prompt and my desktop is always up to date. Go into Settings / Update and Security and set Active hours if you want to. Go into advanced options and turn off 'Restart this device as soon as possible ...' That changes the behavior of updates so that you never get prompted. Instead, Windows does its updates when the computer is turned off.
I uninstalled windows 10 from my computer very fast, so I can't check, but from what I heard you can only set up to 8 working hours a day. So that's for very lazy people Not an thing in gamedev.
28:31 You can use structured bindings in your range-based for loops:
for (auto&& [key, value] : shaderSources)
Here's also a regex approach for the shader file parsing:
std::string shaders_str; // The shader file's contents as a string
std::regex r("#type (.*)(?:
|
)((?:(?!#type )(?:.|
|
))*)");
while(!shaders_str.empty()) {
std::smatch match;
std::regex_search(shaders_str, match, r);
std::string shader_type = match[1];
std::string shader_source = match[2];
// Do whatever with the type and source
shaders_str = match.suffix();
}
Because everyone loves regex.
Should probably also use regex for parsing XML while you're at it, because regex always "works." 😏
Since OpenGL 4.5 (I think) binary Spir-V is supported. So yes, you can load binary shaders in OpenGL
I have different approach to storing few shaders in 1 file, and I belive that my way is better.
Instead of reading shader data you can add 1 line of code with some definition.
So for example for pixel shader you add "#define _TYPE_PIXEL_SHADER
", and for vertex shader you add "#define _TYPE_VERTEX_SHADER
".
And since glShaderSource accept array of strings, you can do it without modificating or copying orginal source.
Then shader programmer will do #ifdef _TYPE_PIXEL_SHADER ... #endif.
The biggest adventage from this method is reading shader errors.
In your method once shader programmer get shader compile error with line number, that line is far off from actual line with error.
In my method it's off by 1 line, witch is fine.
That make shader programming much much easier.
Other advantage is possibility to create common functions, definitions, variables, etc.
This is also very huge thing.
Your method can be easily changed to support common code at the top.
But in my method it can be in any order, and sometimes we want it in different order.
Or shader programmer can even use #ifdef inside function if he/she want to.
Another thing is that in my method content of file is 100% correct GLSL code. So we can use generic GLSL minifier, or other tool for GLSL.
The only advantage of your method over mine is that in your method you automaticly have information about witch shaders are in file, and witch arent.
While I have to either enforce usage of certain shader types, or assume that shaders with certain compilation error doesn't exists, or detect type by search in shader source for string _TYPE_PIXEL_SHADER, _TYPE_VERTEX_SHADER, etc.
I liked your approach because now I can use .glsl extension without getting '#type' highlighted as a syntax error. Though I had to remove '#version' directive from shader files and put it into the define string like this:
if (type == GL_VERTEX_SHADER) {
shaderDefine = "#version 450 core
#define VERTEX
";
}
else if (type == GL_FRAGMENT_SHADER) {
shaderDefine = "#version 450 core
#define FRAGMENT
";
}
Might be better to get correct version from opengl context but who cares
+TheChernoProject I think its time to show us how to create Unit Tests for a game engine. I believe they are very helpful and most of the people including me probably are struggling how to make tests specifically for a game engine. I know you have a video about tests but when it comes to a game engine things are more complicated...
Unit tests are for testing small units of your program and should work the same whether you're developing a game engine or anything else. The things you cannot test however is:
1. Code that is run asynchronously. You can only test single-threaded code (with exceptions of course). All OpenGL calls are actually just messages sent to the GPU, which the GPU will execute whenever it wants. Testing any code that includes an OpenGL call is thus not a good candidate for unit tests.
2. Non-deterministic code. If your code has behaviour that is random (or just can vary based on the state of an uncontrolled variable, like time), then it's not a good candidate for unit tests.
3. Visuals, audio, and other I/O operations.
You could create other tests than unit tests. If you want to make sure your game engine is working correctly, you could set up larger scenes which you run from time to time to check if it looks correct.
@@naxaes7889 But you can make integrated test. Like unit tests, only that they run from inside the system (for example the game engine) instead of running in a different executable. Imagine tests which get called from the main loop of your game engine. Something like scripts, but the difference is that they get compile in Debug mode only and behave like unit tests. You can do a lot of crazy debugging with those.
@@nikoszervo Yes, there are many other good testing/debugging techniques than unit tests. What I meant was that if unit tests feel more difficult for a game engine than any other application, then you might be testing the wrong things. Unit tests should be small, easy and hardcoded. They are meant to test things like _"If I pass parameter X to function F, do I get Y?"_ or _"If I create object X and call method M, does its attribute change to Y?"._ Any more complicated than that, than maybe unit tests isn't the right tool (as usual, there are always exceptions).
@@nikoszervo That being said, it would be nice with a testing/debugging tutorial for game engines.
Just wanted to add that in the case you were talking about reusing a vertex shader with multiple fragment shaders, we did that a lot for Cloudbuilt and Super Cloudbuilt. Almost all materials in the whole game uses one of 3 vertex shaders. There are those with very special vertex shaders of course, but one vertex shader can calculate more than it actually will use in the end. It will be automatically removed when liking the program. So we rather focused on the behavior and input we wanted from the vertex shader, and then calculated all kinds of outputs we wanted from that, as long as it didn't conflict with each other and didn't make the shader too complex to work with and so on. It was really useful for us. We had a button in our editor to derive a material, where we could select what to change and what to keep the same. So we could just select a material, drive, and only change the pixel shader. Then if we later patched or changed parts of how the vertex shader worked it applied to all that used it. Also, as we had a button to open the shader files in our preferred text editor from within the asset/level editor, it was no problem having them in multiple files.
Now we are working on a new pipeline with SpirV though, as working with GLSL and HLSL conversions and optimizations for SCB was a bit bothersome (sometimes files getting out dated and so on). But we will probably look to keep the separate files and re-usability in our new pipeline if possible.
Anyway, always interesting to hear other's perspective and experiences. Keep up the great work and this nice series.
Would btw be nice if you had some C++ podcasts or something where you could talk over different perspectives and such with other devs now and then. I think that would help a lot, as a lot of beginners might find this channel and take all things that might be opinion based and subjective at times as facts. So it would be really nice to show them there are always more way to look at things that can be just as valid. And for experienced programmers it's also always interesting to see more perspectives and arguments in my opinion. And there are just too few good programming podcasts in general :D and judging from your production quality of the videos, I think you could pull it of well.
4:51
TheCherno: "No one really ships games for windows on OpenGL"
Minecraft: "Am I a joke to you?"
Doom 2016 as well.
I think Doom is build on Vulkan, or does it support both?
Not a joke, just an anecdote.
@@gavinw77 The anecdote being the most selled game still 😄
Is it still now that Microsoft took over though? I can not picture them not doing a full overhaul to support DirectX instead to show off their stuff.
You could make the methods in rendercommand that should not be used by users private and a friend to renderer (for example rendercommand::init) so only the methods that should be used are accessible
It is indeed clear to have vertex and fragment shaders written in one file. However, plugins such as "GLSL language integration", which provides assistance for writing opengl shaders, seems to only support separate files for different shaders.
Will this series cover multithreading sometime in the future?
Yes
What?
std::thread is a thing, CreateThread is a function too :p
@@nashis5947 Sure but the problem is how to use them, how to avoid race conditions and deadlocks etc. there is a lot to learn, multithreading is one of the hardest things in all of programming.
How are you getting VS to do the syntax colouring in the .glsl files?
In VS extensions search for GLSL. Its the first result that shows up called GLSL language intergration.
@@Griimlol Thank you!
I know we covered the shader compilation and program linking some episodes ago, but I still have a question from back then:
On 32:00 Cherno is creating a variable of type GLuint called "program". He proceeds to making changes to it (compiling and attaching shaders to it), and then copies the value of "program" to m_RendererID - which supposedly means storing the value of "program" on another memory slot. So far I got it, but...
Then he carries on to use glLink on the "program" (which is a variable that should disapear when scope ends, no?) and detaches shaders from it... Shouldn't we be working on m_RendererID instead?
Thanks so much in advance for any help interpreting this!
ok my bad, at 34:18 he does move "m_RendererID = program;" all the way down to the end of the function - but before this whole abstraction it used to be before those steps^ (glLinkProgram and detach)
15:12 Note that this should be std::ios::in | std::ios::binary. (Instead of the comma). You are unintentionally passing third parameter to the constructor.
Thanks for this! The 3 parameter constructor is actually somehow not considered an error with MSVC (?) but clang & gcc complain.
@@Sf37li0 Yeah, It is weird, the third parameter is not even defined by the standard so I would really like to know what it does.
It looks like a Microsoft extension to support file sharing modes. When you open a file, you can have exclusive access to it, or allow other processes to read or write or delete (or some combo of these) the file while you have it open. In this case, it looks like they take the same 'shflag' values of "_fsopen()", which only controls share access to reading and writing the file.
@@oracleoftroy thank you for explaining
For some reason when i build, I'm getting syntax errors in my Texture.glsl file, why is it trying to compile that? It still compiles successfully, but I have a bunch of errors.
OpenGL 4.6 has SPIR-V support
is anybody else having an issue with their shaders?
I dont see them working in my viewport... any ideas?
thanks!!!
Your pullover link??
How to get that syntax highlighting?
In VS extensions search for GLSL. Its the first result that shows up called GLSL language intergration.
@@Griimlol I meant the "Visual Assist" extensions in vs2019
Heyy cherno a big fan of yours im watching your c++ series for quite a long time and in one video you said that you're gonna come with a project in c++ very soon.when are you going to come with that project its been a long long time please upload it as soon as possible it will be extremely helpful.
Thank youu
@Simon Farre why do you think so ?
@@umairalvi7382 How about a game engine project ?
There is no reason for the ReadFile function to be in OpenGLShader
Again, no proper filesystem API for now.
All the squares are filled with white color....ugh
Vertex Shader should be compiled before Fragment shader,ikr?
One header file engine challenge anyone?😂
yes
Linux
i highly recommend you to switch to linux, you will never regret
relax
For a game developer, ADDING Linux to your arsenal is they way to practice cross platform programming.
The only I regret are crappy MESA drivers, but the rest is all good
std::vector shaderIDs instead of std::vector. They both represent an unsigned int in the end, but it's confusing for humans.
71st!! #EveryViewerMatters
@@spaghetta5497 wait, did I?
@Cherno - you haven't configured your updates correctly if you're getting prompted for restart while you work. That's not Windows' fault, that's your own fault. I never see that prompt and my desktop is always up to date. Go into Settings / Update and Security and set Active hours if you want to. Go into advanced options and turn off 'Restart this device as soon as possible ...' That changes the behavior of updates so that you never get prompted. Instead, Windows does its updates when the computer is turned off.
I uninstalled windows 10 from my computer very fast, so I can't check, but from what I heard you can only set up to 8 working hours a day. So that's for very lazy people Not an thing in gamedev.
If you are getting a compile error in file reading in openGLShader just remove the last parameter: "std::ifstream in(filepath, std::ios::in);"
2nd
6th
4st
forest?
@@pako_powr This was, of course, a joke.
4st??
5rd
Could have used regex to split the file.
your video is so long