Show Posts
|
|
Pages: 1 ... 6 7 [8] 9 10 ... 28
|
|
141
|
Developer / Technical / Re: The grumpy old programmer room
|
on: October 23, 2017, 11:20:41 PM
|
According to what I've read, testing for noexcept would indicate misuse, because the noexcept marker is supposed to be part of the signature and be a promise to the API user who might write other code that depends on that function remaining noexcept; if you can't guarantee that the function will remain noexcept through updates you shouldn't mark it as such in the first place as far as I've understood. I only use it for simple functions that by design would not make sense to change that fundamentally in the future. I may be wrong tho.  Which sounds great... until you get to templates. You can't know if a function will or will not violate the noexcept declaration without testing any functions/operators it calls. Now you could just assume all those functions throw, ignore noexcept, but that kinda defeats point right? If the best way to use noexcept is to not use noexcept... well then we are in agreement 
|
|
|
|
|
142
|
Developer / Technical / Re: The grumpy old programmer room
|
on: October 23, 2017, 10:12:23 PM
|
a rant on noexcept.... Over the past few months I've been trying to integrate noexcept into my C++ code. On the surface this sort-of new feature looked decent. For the record, even though I know C++ has its caveats I quite like it, and I am very 'pro' exceptions (pro/con pro, not pro as in I know everything). I will (and have in the past) happily defend exceptions as a superior form of run-time error handling. So noexcept should be right up my ally... But no, in short noexcept is currently a mess and should be avoided... The idea behind noexcept is that you can tag a function (member function, normal function, operator, whatever) as noexcept like: void Func() noexcept { /*... do stuff here that doesn't throw an exception...*/ } which declares to the compiler that the function does not throw an exeception. The idea being that code can later choose a more optimal algorithm if you know a function doesn't throw. You can test this with the noexcept operator: noexcept(Func()) The thing is noexcept is much like const, it gets everywhere. Once one function is tagged as noexcept, every function used in it needs to be noexcept (or nearly, as try/catch blocks can work around it), which then transitively propagates to any functions they call, and on and on... Much like you just can't add one or two const's in a program and hope all goes well, similarly with noexcept. Its either all-in or not-at-all. Except things are even worse with noexcept. const simply applies to types, classes, and their member functions. noexcept applies to all functions, not just some functions. Doing it 'right' and noexcept ends up everywhere. Its starts cluttering code and getting everywhere. I know its C++, and its not the nicest syntax, but still... But then things start to get nasty, there's two really big problems that aren't immediately obvious. The first is the noexcept operator and its interaction with templates. As soon as you add templates into the mix, you don't know exactly what functions are being called, so you can't just look at the code and know 'ya that's a noexcept function' or 'no that function clearly throws'. Maybe it throws, maybe it doesn't, depends on the template parameters. So the idea is to test them with the noexcept operator. The simple example online and in the docs are things like: void FuncC() noexcept( noexcept(FuncA()) && noexcept(FuncB()) ); The above example would declare a function FuncC() noexcept if FuncA() and FuncB() are both noexcept. So for simple function boiling down the logic to a simple state is possible if not a bit annoying. But anything remotely complex, and you're ready to throw your computer out the window. In many cases the noexcept specification becomes as complex, if not more so, than the code itself. These things start to become monstrous, and trying to keep the code and specification cohesive while fixing bugs, making changes, refactoring... to say its a nightmare is an understatement, and completely intractable in all but the simplest of situations. And of course, it'd be nice if we only had to do this on the odd function, but like I mentioned above, tag one function noexcept and it starts to spread like a plague through your code... This is itself is annoying, but the last problem is insidious. Unlike const, noexcept isn't checked at compile time. So while const is annoying at first, at the very least its trivial to debug. noexcept on the other hand is a nightmare, make a mistake and you don't get an easy to find compiler error pointing directly at the error, no you get some weird seg fault or stack corruption in some bizarre part of code... Multiply this over an entire code base... and well... /sigh Now noexcept COULD be very good, something like it is very useful. If noexcept was checked at compile time and the specifier was only used as a declaration (ie. you didn't need to specify noexcept for functions under normal circumstances, instead the compiler deduced it from static code analysis, and noexcept was only required when needed to override the default analysis, or in function pointer declarations, etc...) then it could be very useful. But of course that doesn't work on 60000 year old machines running on 93 bit architectures with 4kb of ram that have no way to perform cross translation unit analysis. So instead we get this half baked monstrosity. For anyone who's gotten this far. My suggestion would be to only use noexcept on move constructor/move operator, and otherwise forget it exists. This is a warning for those who might wish to travel down the noexcept rabbit hole, there is nothing but darkness and insanity waiting for you...
|
|
|
|
|
145
|
Developer / Technical / Re: The happy programmer room
|
on: September 21, 2017, 03:53:29 PM
|
|
Well that's where the geometry shader comes in. Since you can work with triangles (not vertices) there's no restriction on topology.
It worked well in a D3D11 context, but moving to Vulkan I couldn't justify the added complexity. In Vulkan recording command buffers is dirt cheap, and trivially multithreaded. So I can just blast out another command buffer every frame without even breaking a sweat (so to speak); and with 8, 10, 12, even 16 core behemoths on the horizon, what else are we gonna use the computational power for?
The idea was a lot of fun as a 'proof of concept', but in the end the pros didn't outweigh the cons. Also I found that as cool as using mesh's to represent glyphs (and other 2d graphics) are, that signed distance field is generally still superior. Sure meshes are resolution independent, while signed distance fields have limits, but with signed distance fields things like shadows, aura's, outlines, all sorts of cool effects are trivial; while with meshes many of these effects are very non-trivial.
I think in the future if I were to use the technique again, it'd most likely be in some sort of advanced particle effect. Using the vertex shader to do the heavy computational lifting, then using the tesselation stages to multiply/amplify that, and then use the geometry shader as sort of a 2nd stage vertex shader. The advantage being that it can be done in a single pass with no intermediary buffers needed for stream out.
I'd also like to play around a bit with micro-poly culling in the geometry shader, but as of yet I don't have that need.
|
|
|
|
|
146
|
Developer / Technical / Re: The happy programmer room
|
on: September 20, 2017, 06:18:57 PM
|
Your solution sounds cool, were the meshes predefined or did you also somehow expand them adaptively in the tessellator? You'll probably get nice quality and performance regardless. I would probably just have gone with a compute kernel for the expansion step (look up amount of required triangles per triangle, prefix scan to find proper indices for all of the vertices) but your solution is likely faster since it does the allocations locally using the tessellator. What's your upper limit for the atmount of possible geometry per glyph? The meshes were pre-generated from a TTF file and stored in a texture. I used the pixel shader to generate the quadratic curves for the glyphs, so I didn't have a need for dynamic tesselation (in the sense that you would increase or decrease triangle count on the fly). By using the pixel shader for quadratic curve rendering, you can get pixel perfect precision at any resolution, with a surprisingly small mesh (usually 20 - 50 tris/glyph, really depends on the font). I think I could get at most 8k triangles out of a single mesh. If I remember correctly (was a while back) you could tesselate each side of a planar mesh up to 64 times, which gives you 4k quads or 8k tris. I never seriously tested the performance. The idea was to use it for a UI engine where everything was a 'glyph', and almost everything executed on the GPU. You could program animations with keyframes for each glyph, have them respond to input, etc... The thing was that all the keyframe animation was relatively complex (nothing worse than say skinned animation, but still more than usual for UI). So I could do all the calculations once for each glyph in the vertex shader, then send the transformation data to the geometry shader via constants. This meant I only had to do the key frame animation computations once per glyph as opposed to once per vertex. It was working great, and then Vulkan came out...
|
|
|
|
|
147
|
Developer / Technical / Re: The happy programmer room
|
on: September 20, 2017, 02:15:23 PM
|
Btw, has anyone tried the shader-printf out? Any thoughts on how to improve the usage, either C++ or GLSL? I used the tesselation shader stage to output glyphs for text, if that's what you mean. Each glyph was a single 'vertex' in a buffer, the tesselation shader stages expanded that into 'n' triangles, 'n' being the number of tris for a particular glyph (I wasn't using a standard bitmap, rather each character was actually a 2D mesh). The geometry shader then transformed the tesselation generated tris, handled texture coords, etc... It worked pretty well. I guess you could use a compute kernel to create the initial text buffer, seems like it would be pretty straight forward. Or were you thinking of something else?
|
|
|
|
|
148
|
Developer / Technical / Re: The grumpy old programmer room
|
on: September 17, 2017, 02:08:37 PM
|
I started a reply, then got sidetracked, then forgot I didn't send it, then like 4 days later I look back and I'm way behind... So here we go... kinda  Oh yes, the ESP8266 is pretty nice, are you using it with the full toolchain from Espressif? - I've went through that hassle as well a while ago, and switched over to Lua (NodeLua) immediately afterwards, it's sooo much easier to use  . Just out of curiosity, are you controlling some underwater vehicle or flow or fish interaction in the Aquarium? Sounds interesting... I'm actually using the Arduino tool chain, as I prefer C++ but didn't want to mess with the normal Espressif toolchain. Granted their RTOS is pretty cool... Its controlling all the pumps, lights (turning them off at night, on during the day), there's an automated water change system, as well as temperature readings/control. I made a smaller controller with an Arduino (since I'm an electronics noob) which worked well, so now I thought I'd step it up a notch, control everything, have it all accessible via the web. That way I could leave it for a week or two and know its working fine. The ESP8266 seemed like a good choice as it works with the Arduino toolchain (which I'm familiar with) but also has built in wifi, etc... I gotta admit its a neat little chip and its surprising what you can do with a few kB of ram and a few MHz when you don't have an OS or a driver stack to slow you down  Not sure what the compiler will do there with the function argument (uint32_t counter) on the stack... How significant are the delay differences? Just guessing, but the difference might stem just from the fact, that it will read the passed counter argument first from the stack, store it in a register and then use it for the counter loop in CounterTest() and in the "tempalated" CounterTest2 function it puts the counter value in as a literal at compile time... Every memory read operation might cause cache issues, but I'm just guessing, you might be able to tweak it out with different compiler optimization settings. Also, what does ICACHE_RAM_ATTR really stand for? - It most often helps to generate assembly output with -S for gcc, to see the exact differences.
On the ESP8266, code can either be stored directly in ram (for fast execution) or run off the flash (a lot slower). When the function is in ram you have no cache latency and you can pretty much know exactly how many cycles each instruction will take. Or at least in theory... As far as the stack/templated variable... my understanding was the volatile after asm prevented a lot of assembly transformations, so between the counter reads, there should be no way GCC can alter that code. But apparently it is? Clearly I don't understand asm/volatile as well as I thought. Its gotta be doing something... Its weird, the 1st function (non-template) takes (5*i) - 3 cycles to execute, dead on every time. 1 iteration = 3 cycles, 10 = 47, 1000 iterations = 4997 cycles, etc... The templated one will vary all over the place with small/tiny changes to code not even related to it. Sometimes its faster at ~4 cycles per iteration, sometimes much slower. You are correct in that I should check the disassembly. At this point though I think I'm ready to move on. I didn't really need the code, it was just me playing around and trying to understand how things worked. It seems if I want nanosecond precision timing I'm going to need to hand-code the assembly myself (ie. not use inline and hope GCC doesn't mess me up). Welcome to hell! :D
By way of explanation of your situation. The people who create microcontrollers are brilliant, brilliant electrical engineers, who happen to be terrible software developers, who delegate to inexperienced interns in a critically underfunded area to write the software libraries.
Be thankful you aren't doing development on STM32 ARM code, which comes with quite possibly the worst libraries in the industry.
I take it you have past experience with it? Its funny, with ARM being as big as they are, I'm surprised they have such terrible software... I mean 1 full time C++ geek could handle it all  You're project looks pretty cool, and far beyond my noobish electronic capabilities. I wish I had more to comment apart from... wow neat  Clearly a lot of time and effort invested.
|
|
|
|
|
149
|
Developer / Technical / Re: The grumpy old programmer room
|
on: September 11, 2017, 06:38:49 AM
|
So I'm working on an aquarium controller for a home/DIY project and to play around with MCU's. So I've been busy programming away with the Esp8266, and getting things done for the most part... That said, I just got to rant. The libraries for this thing, I know they are free, but what a mess. Its like going back 30 years. Macro's everywhere, build systems so convoluted they are 10x more complex than any of the code. Constants defined in 4 different places for no apparent reason. Documentation thats near impossible to find and wrong 1/2 the time when you can actually find it. And debugging/working with it has made me really miss my VS IDE and its integrated debugger. I've also come across probably the most bizarre error I've seen in a very long time. In trying to create just a simple spin-loop delay function, I've created what I though to be two identical pieces of code... ICACHE_RAM_ATTR uint32_t CounterTest(uint32_t counter) {
uint32_t start, end;
__asm__ volatile ( "rsr.ccount %[r_start];" "loop_%=:" "addi.n %[r_counter], %[r_counter], -1;" "bnez.n %[r_counter], loop_%=;" "rsr.ccount %[r_end];" : [r_start] "=r" (start), [r_end] "=r" (end), [r_counter] "+r" (counter) : // no inputs );
return end - start; }
template<uint32_t count> uint32_t CounterTest2() ICACHE_RAM_ATTR;
template<uint32_t count> uint32_t CounterTest2() {
uint32_t counter = count; uint32_t start, end;
__asm__ volatile ( "rsr.ccount %[r_start];" "loop_%=:" "addi.n %[r_counter], %[r_counter], -1;" "bnez.n %[r_counter], loop_%=;" "rsr.ccount %[r_end];" : [r_start] "=r" (start), [r_end] "=r" (end), [r_counter] "+r" (counter) : // no inputs );
return end - start; }
void AsmTest() { static bool run_once = false; if (run_once) return; else run_once = true; uint32_t count0 = CounterTest(1); uint32_t count1 = CounterTest(10); uint32_t count2 = CounterTest(100); uint32_t count3 = CounterTest(1000);
uint32_t count4 = CounterTest2<1>(); uint32_t count5 = CounterTest2<10>(); uint32_t count6 = CounterTest2<100>(); uint32_t count7 = CounterTest2<1000>(); lcd.Clear(); ReadyLn(lcd, 0); Utility::Print(lcd, count0); Utility::Print(lcd, " / "); Utility::Print(lcd, count4); Utility::Print(lcd, " / "); Utility::Print(lcd, 1);
ReadyLn(lcd, 1); Utility::Print(lcd, count1); Utility::Print(lcd, " / "); Utility::Print(lcd, count5); Utility::Print(lcd, " / "); Utility::Print(lcd, 10);
ReadyLn(lcd, 2); Utility::Print(lcd, count2); Utility::Print(lcd, " / "); Utility::Print(lcd, count6); Utility::Print(lcd, " / "); Utility::Print(lcd, 100);
ReadyLn(lcd, 3); Utility::Print(lcd, count3); Utility::Print(lcd, " / "); Utility::Print(lcd, count7); Utility::Print(lcd, " / "); Utility::Print(lcd, 1000); } Otherwise identical functions that should just spin loop... And yet they give different results. Its not crucial for anything, I was just playing around and trying stuff out at the low level; but I'd love to know why they give different results... And if anyone has played with MCUs, is there any way to embed template constants into inline assembly? Being able to encode things like pin definitions directly into assembly could really yield some interesting possibilities.
|
|
|
|
|
150
|
Developer / Technical / Re: General programming discussion
|
on: September 07, 2017, 06:34:31 PM
|
I'd say skip VAOs and attributes in GL and skip vertex buffers and input layouts in D3D. Just go with generic buffers for vertex attributes. The only vertex shader specific input you need is the vertex index. This results in the exact same operations performed by the hardware and saves a great amount of API hassle.
Actually, as much as I'd like to agree with you, there is still a lot of hardware out there where input layouts is faster than manual fetches from a vertex shader. As far as AMD/NVidia are concerned I'm not sure, but I know (know as in have read, not personally experienced...) most of the Intel chips input layouts are much faster, as well as with many mobile GPUs. As much as I'd love to see them ditch the IA and rework vertex shaders to be more of a geometry generation shader (the notion of 1:1 input vertex to output vertex is rather antiquated at this point IMHO), we sadly are not there yet.
|
|
|
|
|
151
|
Developer / Technical / Re: The grumpy old programmer room
|
on: August 27, 2017, 04:43:49 PM
|
I haven't done much language design study.  Try focusing on defining new types. Try to break down the separation between compiler-supported types and user defined types. Maybe like a data file can be read by the compiler to install a new type of data, and all type primitives (including the default ones that come packaged with the compiler) are defined in this format. It would be sweet if these data types could be defined in-code, sort of like a .c header. How do you envision that being different than say a struct with some operator overloads?
|
|
|
|
|
152
|
Developer / Technical / Re: The grumpy old programmer room
|
on: August 26, 2017, 04:24:00 PM
|
Ideally a language would provide some fundamental primitives that can be used to express whatever the user wants. Users really like encapsulation, but not necessary on an object-object basis. Users really like polymorphism, but not necessarily on a class to class basis, and not necessarily tied into C++-style inheritence. Users really like code-generation for compile-time polymorphism, but not necessarily in the form of C++ templates, which pollute translation units with codegen spam. Ideally users can express strict memory patterns across their statements and expressions (like the above examples), but not necessarily in the form of C++ deep copies or other OOP-ey messes.
What primitives would you propose?
|
|
|
|
|
153
|
Developer / Technical / Re: The grumpy old programmer room
|
on: August 26, 2017, 07:58:33 AM
|
I'm curious as to what others think would be a better alternative. For example, if you could replace move semantics, how would you change them? How would you replicate or work around what they propose to solve?
The better alternative is not to tie ownership of data to objects. Have all your types either be value types or pass them around by pointer and forget about the rule of three, five, seven, nine or whatever. Doesn't that sort of negate encapsulation and the single responsibility principle? And the data has to live somewhere, do you advocate not using containers? I'm not trying to be negative/call you out, I am just really curious and playing a bit of devils advocate here to further my own understanding (tone is hard to convey online, I want to emphasize this is not an attack or criticism). In my spare time I've been working on my own language, and while I have a lot nailed down, move and rvalues have left me with a bit of an uneasy feeling. I understand why they are in C++, and are pretty much essential to containers, but there are still things about them that feel a bit... iky?
|
|
|
|
|
154
|
Developer / Technical / Re: The grumpy old programmer room
|
on: August 26, 2017, 06:07:49 AM
|
|
I'm curious as to what others think would be a better alternative. For example, if you could replace move semantics, how would you change them? How would you replicate or work around what they propose to solve?
|
|
|
|
|
156
|
Developer / Technical / Re: Voxel engines (not minecraft-like blocks)
|
on: August 16, 2017, 05:11:33 AM
|
Nice stuff Schrompf  It may be 'self promotion' but its nice to see what others are working on. Personally, I find that trying to render voxels as triangles, just works against you. Voxels are easy and very fast to ray-trace. The problem with voxels IMHO is storage and dynamic creation of the acceleration/tracing structures. If you can fit it in memory, and it doesn't change frame to frame, modern hardware can ray trace a nearly infinite amount of scene complexity with voxels. I'd be open to revisiting the project, I had fun writing the engine. I just don't want to write all the associated tools (voxel editor, level designer, animation, etc...). All the tools already exist for polygon engines, almost none of them for voxel engines. That said, a 'hand drawn', top down, voxel, diablo-esque, game would look amazing.
|
|
|
|
|
157
|
Developer / Technical / Re: The grumpy old programmer room
|
on: August 13, 2017, 05:30:08 PM
|
Of course there will always be coupling, the question is where does the coupling happen. Normally it happens at the 'top'/'deep' (called high level in most books) part of your dependency chain, with inversion it happens at the 'bottom'/'shallow' (called low level) end of the chain. Dependency inversion often fixes the singleton spam problem. For example, imagine you have some sort of file manager. Normal dependency gives you something like: OS specific file routines <- file manager <- game engine <- main game loop <- OS specific entry/initialization likewise for a graphics manager: OS specific windowing routines <- graphics API(s) <- graphics engine <- game engine <- main game loop <- OS specific entry/initialization You'll find that quite often with normal dependency chains the OS/platform specific stuff will start to pop up in all your systems (as well as other high level interdependencies). Now if its something that is always there (like the standard library) its a non-issue. But as soon as its not a guarantee, you have tons of branches, checks, or worse #define's littering your code. With dependency inversion you end up with something like: file manager <- game engine <- main game loop <- OS specific entry/file wrapper graphics API(s) <- graphics engine <- game engine <- main game loop <- OS specific entry/initialization/window wrapper The key difference is that your OS/platform specific code is in one place, not scattered through out. Your interface to the OS is then implemented through fixed/known interfaces. Moving to new platform, adding a system, or porting a system to a whole new program/game now become rather trivial tasks. https://en.wikipedia.org/wiki/Dependency_inversion_principle
|
|
|
|
|
158
|
Developer / Technical / Re: The grumpy old programmer room
|
on: August 13, 2017, 03:08:59 AM
|
I heavily disagree with everything else in there but I don't want a derailing discussion so I'll just say that in my experience you want your systems to be LESS coupled the deeper down in the stack you go and connect them with HIGHER level systems, and I found that to be perfectly working without any need for singletons. Actually better than back when I was using singletons.
I'm going to agree with JWki here. I've found that as you go deeper, you want less coupling. Its a little strange at first, but it makes code reuse more than just a pipe-dream. For example, my VulkanLib wrapper/engine I'm working on has almost no external dependencies, and none that are tied to any particular OS, platform, system, or game engine. Its mainly just C++ code interfacing with Vulkan. In a Windows library (which handles all the Windows OS stuff) I provide the OS specific tie-ins to VulkanLib (dll loading, surface creation, etc...). There's no Windows specific stuff in VulkanLib, rather there's Vulkan specific stuff is in WindowsLib. That way if I were to port to Linux or Android, the entire Vulkan 'back-end' wouldn't need anything changed. As far as the delete on exit, I agree with Ordnas here. Your classes shouldn't worry about the context they are used in. They should generally be context agnostic to allow as much re-use as possible. If at the end of the program you want to use std::terminate() or something similar to avoid unwinding the stack, then by all means; but destructors should always free any data they allocate. RIAA is fundamental to good C++ programming.
|
|
|
|
|
159
|
Developer / Technical / Re: General thread for quick questions
|
on: August 13, 2017, 02:47:46 AM
|
What are the pros and cons to embedding resources directly in code versus loading them from file at runtime? It does seem very handy to embed everything from a portability standpoint, as there is no need to mess with complicated differences in filesystems (especially on things like mobile and consoles), and the size footprint is going to be there anyway, but what are some negatives about it?
The obvious cons are: - It bloats the exe size, this is rarely a problem on modern systems but can be an issue if you start heading into the gigabyte+ sizes. Could be an issue on mobile and consoles, depending on how their virtual memory/paging system works. Worst case scenario you could end up loading everything into memory twice, first when you run the executable, the second when you load that asset into the GPU or whatever, since the original isn't necessarily unloaded. - Updates/patches can become quite large, a small change could require essentially the entire program and all its resource to be re-sent. - Nearly impossible to mod. - Debugging can easily become a nightmare if you're not very careful about your build system. - Can significantly increase build times (in particular link times). I like to embed my shaders into the exe, but the rest of my assets I leave in archives grouped according to use.
|
|
|
|
|
160
|
Developer / Technical / Re: General programming discussion
|
on: August 12, 2017, 04:45:30 AM
|
The link I posted explains everything quite well. Some highlights are: - Uses JSON so that datatypes don't have to be decoded manually.
- Can link to external binary files that can pretty much be sent straight to the GPU.
- Standardised units such as metres, and that y is up, so that everyone is on the same page, from one modelling software to another, to an engine.
Appreciate the info. IMHO we don't need another interchange format, we already have way too many of those. If this was a binary/GPU orientated format then that would be pretty cool. Oh well, I have my own formats 
|
|
|
|
|