Closed Lecrapouille closed 5 years ago
And here the backward trace backward.txt
Yeah I remember running into the GLM default matrix != identity problem. I wrote my own wrapper that called identity() in the constructor.
I have no idea why that assert fails. Have you made any code changes? Some of the errors I saw in your big diff where you added all those 'f's to the floating-point numbers could have broken it. Did you revert that set of changes? If your stat is clean, then maybe it's some compiler issue. I don't see how I can debug it if I can't reproduce it. What gcc version are you using? My only thought is to disable the "#pragma omp parallel for" around the BVH construction, or make sure do_mt_build=0 in that case. It could be a thread problem.
Try adding this line to cobj_bsp_tree.h line 134 inside obj_okay(): assert(c.is_normalized());
If that fails, some object with a denormalized bounding cube was added to the tree. Then you can print out the data for the failing object. If it doesn't fail, then something got corrupted in memory at some point. Maybe start by calling c.print_bounds(): if (!c.is_normalized()) {c.print_bounds(); assert(0);}
I haven't actually checked this code...
I made an attempt to fix this problem. There was a very strange error where a class member variable wasn't being properly initialized in the constructor, and renaming the argument (???) fixed it. I also updated a conditional to make it safer and added debug printouts into that failing case. Can you try it now to see if it still fails with that assert?
I did not change float literals. I'm using an older desktop (Ubuntu 18.10 AMD 32bits) because I'm out of my home. My g++ is 7.4.0. Now I'm passing this step but now no kidding I'm failing at the next assertion :(
3DWorld/src/csg.cpp:193: unsigned int cube_t::get_split_dim(float&, float&, unsigned int) const: Assertion `dim_sz >= 0.0' failed.
I'm going to try without openmp
Even disabling openmp I got the same error.
PS: it may be interessing to wrap #ifdef _OPENMP
functions to avoid this compilation error:
snow.o : Dans la fonction « create_snow_map(voxel_map&) » :
3DWorld/src/snow.cpp:501 : référence indéfinie vers « omp_get_thread_num »
Universe_control.o : Dans la fonction « omp_get_thread_num_3dw() » :
3DWorld/src/Universe_control.cpp:63 : référence indéfinie vers « omp_get_thread_num »
collect2: error: ld returned 1 exit status
makefile:32: recipe for target '3dworld' failed
Which scene is this failing on? The mapx scene doesn't have enough objects to enable the parallel OpenMP tree build, so I don't think it's a threading problem. The shapes aren't modified anyway. The new assert is probably the same problem, it's just failing at a different place in the tree building. Can you share the stack trace for that one? It seems like it could be uninitialized memory access. Can you run under valgrind? That's a similar OS and gcc to the one I'm using, and I don't have this problem. I don't see how it can be a 32 vs. 64 bit problem here.
I need to figure out what the bounding cube values are before I can guess at the problem. Are they reasonable numbers but just wrong? Are they all random garbage numbers with large exponents from uninitialized memory? Are they NaN values from some divide-by-zero or other such thing?
The omp_get_threadnum() calls are supposed to be guarded by #ifdef OPENMP. I must have missed some. I should change it to the way I have at work so all of those omp* functions are abstracted with a wrapper that does nothing in the case where OpenMP is disabled.
The default mapx scene . No Valgrind error found. backward.txt As an alternative investigation, you can freely (through your GitHub account) access to Coverity Scan. This tool does static code analysis. You compile against their tool. They freeze you 1 week after the first upload to check your bugs. After that, you can see bugs the tool found.
PS: and yep its NaN for d[i][1] and d[i][0]
ooooh I compiled with -O0 + backtrace + no openmp (that I did not restored) and that passed:) I got the scene but this time really running at 5 -15fps (and the ESC key looks be broken, I could escape with 'm'). I'll try again valgrind.
Here the valgrind valgrind.txt except errors from the nouveau_driver valgind found nothing
I don't think any of those valgrind uninitialized errors are related to the assert. Maybe it's because you can't load the dragon model. It could be that it still adds it but with some garbage bounding cube. I'll check tonight. If that's not it, then I have no idea what's going on. Can you send me the text written to the terminal for a failing run? I want to compare to see if the numbers are any different.
This ? I'm not sure about what you really mean by "Can you send me the text written to the terminal for a failing run". Dragon is for nothing because I have it since the beginning.
obj/3dworld
Starting 3DWorld
Using config file mapx/config_mapx.txt.
Loading.....
Loading Sounds....................................................
...GL Initialized.
loading textures done
max TIUs: 16, max combined TIUs: 48
Texture Load time = 1429
OpenGL Version: 4.5 (Compatibility Profile) Mesa 18.2.8
Renderer: AMD RS880 (DRM 2.50.0 / 4.15.0-51-generic, LLVM 7.0.0)
Vendor: X.Org
GLSL Shader Version: 4.50
mesh = 128x128, scene = 10x10
Generating Scene...
Read input mesh file 'mapx/mesh128.txt'.
Surface generation time = 6
Matrix generation time = 9
Gen Landscape Texture time = 50
Landscape Texture generation time = 59
Volume+Motion matrix generation time = 62
Scenery generation time = 62
..Error opening model3d file for read: ../models/dragon.model3d
Error reading model3d file ../models/dragon.model3d
Error reading model file data from file ../models/dragon.model3d; Model will be skipped
Reading model3d file model_data/fish/fishOBJ.model3d
Model3d File Load time = 0
loading material library fishOBJ.mtl
Model Texture Load time = 55
model stats:
bcube: -0.150792,0.774795, -1.39715,-1.27357, -3.40216,-2.42594
center: 0.312001, -1.33536, -2.91405, size: 0.925586, 0.123583, 0.976219
verts: 2213, quads: 1104, tris: 69, blocks: 2, mats: 1
Total Model3d Load time = 55
Model File Load/Process time = 55
Create and Xform Model3d Polygons time = 1
polygons: 1214, grid: 14x2x5
Model3d Polygons to Cubes time = 1
grid size: 140, cubes out: 45
.1218 => 1479
Negative Shape Processing time = 2
1479 => 1598
Cube Overlap Removal time = 2
Add Fixed Cobjs time = 5
bins = 16384, ne = 9803, cobjs = 26071, ent = 119447, per c = 4, per bin = 7
3dworld: /home/qq/3DWorld/src/csg.cpp:194: unsigned int cube_t::get_split_dim(float&, float&, unsigned int) const: Assertion `dim_sz >= 0.0' failed.
Abandon (core dumped)
Strange why this bug appears with -O3 and disappears with -O0. On another part, I do not understand why on Debian a g++-6.0 does not show bugs that the same g++ version detects on Ubuntu (I'm talking about my personal projects) !!!!
That's interesting. The number of v_coll_matrix entries here is 119,447, while it's 119,471 in the other run that doesn't fail. That shows that the code diverges before building the BVH. Is the only difference the optimization flag? I wonder if it's some sort of compiler bug? Or maybe it's some type of C++ undefined behavior that the compiler handles differently depending on optimization mode. I have no idea how to debug something like that. It should be possible to push that error check earlier into the flow and eventually track it down to where the bad value is set/inserted, if you're willing to keep running experiments. I've run on 4 different machines and haven't seen the failure.
Is there a way to force gcc to zero all variables? Maybe just memset all fields of coll_obj to zero in the constructor? The only thing I can think of is some uninitialized variable, or maybe an invalid operator<() used in a sort function. Maybe the memcmp() in obj_layer::operator<() is picking up some unused garbage bytes? But then I would think valgrind can find it. Can you run valgrind on the -O3 build?
I consistently get this line without the dragon model on Windows and gcc with -O3 and -O0: bins = 16384, ne = 9803, cobjs = 27576, ent = 121505, per c = 4, per bin = 7
You get this: bins = 16384, ne = 9803, cobjs = 26071, ent = 119447, per c = 4, per bin = 7
I'm not sure what accounts for the difference. I'm using gcc-7.4 as well. It's possibly some difference in the floating-point math. For example, I have a line: val = sqrt(1.0 - normal.x); normal.x should always be <= 1.0, but maybe there's some double vs. float problem and it comes out slightly negative? I added a max with 0.0 that should fix it. I also added various other asserts, a divide-by-zero check, and a better error message printout for that assert. Maybe you can try it again to see if anything has changed?
Ok good observations about the number of objects. I gonna redo tests with this in mind. First, compilation errors with the current Makefile:
3DWorld/src/mesh_intersect.cpp:378:38: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
assert((tree[level] - tree[0]) + ix < (int)bsp_data.size());
3DWorld/src/ship.h:1074:7: warning: ‘<anonymous>.u_ship_base::wpt_center’ may be used uninitialized in this function [-Wmaybe-uninitialized]
class u_ship_base { // Note: Can be created on the stack and copied
3DWorld/src/cars.cpp:801:13: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if (*ix != (i - cars.begin())) {check_collision(*i, cars[*ix]);}
3DWorld/src/gen_buildings.cpp:1698:64: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
float const door_ztop((!doors.empty() && (i - parts.begin()) == unsigned(door_part)) ? doors.front().pts[2].z : 0.0);
Second, I compiled with your last patch, no assertions occurred and bins = 16384, ne = 9803, cobjs = 26071, ent = 119471, per c = 4, per bin = 7
whatever with -O3 or -O0. So seems ok now :)
Why don't I see those compiler warnings? That's odd. I know I fixed a whole lot of signed vs. unsigned problems.
I'm guessing it was that sqrt() function. I've had problems like this in the past.
I still don't understand why your cobjs and entries counts are different from mine. There may be something else wrong. Are you using the GLM from inside the GLI directory rather than the one directly inside dependencies? That could be the problem. GLI uses an older GLM. It may be a version from back when matrices were initialized with all zeros rather than an identity matrix by default. You shouldn't be using that version of GLM. I'll go back and see if switching to the older GLM inside of GLI changes my numbers.
PS: reverting to the previous commit: in -O3 or -O2:
bins = 16384, ne = 9803, cobjs = 26071, ent = 119447, per c = 4, per bin = 7
In -O0:
bins = 16384, ne = 9803, cobjs = 26071, ent = 119471, per c = 4, per bin = 7
I'm not a big fan of -O3 or with -Os (especially with -g) but I'm not an expert. You can compile with -O2 -g and rename your application 3dworld-debug. Then call strip -R .comment -R .note -R .note.ABI-tag 3dworld
for the release (a binary without debugging symbols).
For the computations, 1.0 is double while 1.0f is float. I'm not expert too but I think when mixing double and float, computations are made in double then cast to float. So it's ok except maybe more CPU computational. For computations with Nan: I guess than max(0.0f, NaN) will return 0.0f. Some people preferer that retun NaN.
The error is generating NaN in the first place. What do you expect this to do: float f = 1.0; float v = sqrt(1.0 - f); I would think this would result in v=0 because 1.0 can be represented exactly in a floating-point number. But if the compiler does some strange optimization, maybe this can return NaN because the value going into sqrt() is some tiny negative number? Can you test this code on your compiler with different optimizations and print the results? If you do get NaN, does replacing the math with "v = sqrt(1.0f - f)" fix it?
Why don't I see those compiler warnings? That's odd. I know I fixed a whole lot of signed vs. unsigned problems.
Like I said, I have Debian and Ubuntu with the same version of g++ and got different warnings (or worse errors). So yep, more archi you can compile, better it is. Else, use docker :)
Are you using the GLM from inside the GLI directory rather than the one directly inside dependencies?
In my Makefile I made -I$(GLI)/external
for fixing the initial error of this post
In file included from dependencies/gli/gli/format.hpp:6:0,
from dependencies/gli/gli/gli.hpp:39,
from src/image_io.cpp:25:
dependencies/gli/gli/type.hpp:42:24: error: ‘qualifier’ has not been declared
template <typename T, qualifier P>
So this is the nested GLM. What about your Visual Studio Makefile are you using the external one ?
It may be a version from back when matrices were initialized with all zeros rather than an identity matrix by default.
Is this is just that that means you missed a -DGLM_FORCE_CTOR_INIT in your Linux Makefile.
You shouldn't be using that version of GLM. I'll go back and see if switching to the older GLM inside of GLI changes my numbers.
Compare with your VS Makefile.
Your VS makefile seems to use the external GLM:
<AdditionalIncludeDirectories>jpeg-9a;glew-2.0.0\include;Targa;C:\Program Files %28x86%29\OpenAL 1.1 SDK\include;freealut-1.1.0-bin\include;libpng-1.2.20;zlib-1.2.1;tiff-4.0.3\libtiff;dependencies\glm;freeglut-2.8.1\include\GL;dependencies\gli;src</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;ENABLE_JPEG;ENABLE_PNG;ENABLE_TIFF;ENABLE_DDS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
With is similar of -I of gnuMakefile.
Ok I changed my Makefile to link with external GLM:
GLM=dependencies/glm
INCLUDES= ... -I$(GLM)
But got the same error :(
Looking in GLM: dependencies/glm/glm/detail/type_mat4x4.inl they totally changed the code in comparison of the nested GLM. They apply the identity matrix while the holder code init nothing.
The error is generating NaN in the first place. What do you expect this to do: float f = 1.0; float v = sqrt(1.0 - f); I would think this would result in v=0 because 1.0 can be represented exactly in a floating-point number. But if the compiler does some strange optimization, maybe this can return NaN because the value going into sqrt() is some tiny negative number? Can you test this code on your compiler with different optimizations and print the results? If you do get NaN, does replacing the math with "v = sqrt(1.0f - f)" fix it?
Do you said that about: float const ni(sqrt(max(0.0, (1.0 - norm[i]*norm[i]))));
?
sqrt(0.0) is 0.0 but sqrt(NaN) I dunno I guess it s NaN, I have to test. But now sqrt(max(0.0, NaN)) will be 0.0 because max(0.0, NaN) is 0.0. I think the problem is your norm[i] having bad values. I can check this.
Can you try the latest version of GLM on GitHub? Maybe they fixed that qualifier error. Also, can you try adding -DGLM_FORCE_CTOR_INIT in the makefile? I think GLM is the root of the problem and disagreement between our builds. I'll go back later and initialize all of my GLM vectors/matrices to try and avoid this problem in the future. None of that code is really performance critical anyway.
For that NaN question, I believe the NaN is generated by calling sqrt() on a negative number. The value of norm[i] is probably close to 1.0. If the number is properly normalized, it should never be greater than 1.0, so 1.0 - norm[i]*norm[i] should never be negative. But if the compiler did some sort of tricky floating-point optimization, I can believe it somehow gets a negative number going into the sqrt() and NaN coming out.
I tried reproducing this on gcc 5.4 at work and I can't seem to get NaN.
Ok I'll try the new GLM. But I change my mind concerning norm[i] at NaN. In I just added in cylinder_3dw::calc_bcube
a assert(std::isfinite(norm[i]) && "norme mal formee");
and even with -O2 now it passes. I think finally norms are ok but you still have memory overflow somewhere that inserting your code or my assert make change of location. I checked 1.0 - norm[i]*norm[i]
I'm not sure about the -DGLM_FORCE_CTOR_INIT
seems they changed
I definitively hate GLM: dependencies/gli/gli/core/././bc.inl:81:66: error: ‘uint_t’ has not been declared
Guys, what about continuous integration and writing unit tests instead of breaking APIs ? :-1:
norm[i] isn't the problem. It's the bounding cube that's the problem. You should test those values, in the variable "d[3][2]" from base class cube_t.
I know GLM is a pain. The reason I'm using an old version is because I don't want to have to change my code like the last time I updated it. It tends to not be backwards compatible. If you can point me to an alternative to GLM that has vectors and matrix transforms that are compatible with GLSL I can try it.
You can try this really fast API: http://arma.sourceforge.net/ I used mine really slow and inspired by https://github.com/Reedbeta/reed-util and its blog http://www.reedbeta.com/blog/on-vector-math-libraries/ The default with Armadillo is you cannot overload operators (for using Max-Plus algebra for example, but I think you do not care)
norm[i] isn't the problem. It's the bounding cube that's the problem. You should test those values, in the variable "d[3][2]" from base class cube_t.
Sorry but your code is hard to understand for me (no comments). I'm not sure to follow you. Why d[3][2] while in calc_bcube d[i][0] and d[i][0] are used. Can you write me the code that I can test.
Ok I have to work on my project but yep on cylinder_3dw::calc_bcube, just adding the max fixed the bug. I dunno why, maybe because of sqrt(0.0-epsilon) but I could catch it with assert. But I still think about memory corruption made in ther part of the code because just adding assert() fix it.
I compiled with g++-8 (+external GLM) this may interesting you: compilation.txt shadow_map.cpp seems to make some memory overflow
No the variable is d[3][2], a 2D C-style array. Adding the assert probably changed how the compiler optimized the code so that it wasn't able to do whatever optimization was giving a negative result going into the sqrt(). I've seen that before, and it makes sense.
I prefer to use -O3 rather than -O2. I can't measure a difference in 3DWorld, but it definitely makes a 5-10% runtime difference on performance tests in our product at work.
I prefer to use the GLM inside dependencies rather than the one in gli/external because it's newer and the one I'm using in Visual Studio. I've changed it in the makefile, and fixed a few warnings. Both versions of GLM seem to give me the same results.
I fixed that uint_t in GLI. I'm not sure why they have all the #ifdefs checking for architecture, etc. but apparently they're not testing all of the cases. GLI is pretty bad. GLM is better, as long as they stop changing the API and defaults.
I'll attempt to fix those gcc8 warnings. I do see the uninitialized variable usage warnings from trees with one of my makefile variants. I think those are false positives (something is computed from them but not used). I'm not going to attempt to fix all of those GLM errors because they're nontrivial.
Seems alright!
I'm back on Debian, seems ok here too. And also the old crash when leaving the earth is fixed. I can definitively closed this ticket :) PS: in the README can be nice to give the name of the config as a caption of each screenshot. For example, I don't know which is the config name for the 1st screenshot.
Thanks for testing this and providing all the feedback!
I'll see if I can figure out how to add captions in README.md. The first screenshot is from config_heightmap.txt. I started with just the heightmap with procedurally generated noise + domain warping + erosion, then added cities + buildings + cars + pedestrians on top of that. The car and pedestrian models are too large for the git repo so you won't see them.
ouch :( this config is a pain for my graphics card: 1FPS and the "nouveau" GPU driver seems to give hexa values. I had to kill 3dworld with the kill command. I tried again but this time all my Debian get frozen :( I cannot help you for this case. Reproduced twice. Here logs obtained with 3dworld > log1 2> log2
and interesting fact 3dworld seems to run at 20 FPS before get frozen (thread dead lock?) and my Debian died again.
3DWorld_logs.zip
You need a graphics card with at least 2GB graphics memory for that scene and it's overflowing to system memory. It runs okay until it loads too many tiles. I have the same problem on some machines. You can even see this in your log: nouveau: kernel rejected pushbuf: Cannot allocate memory
The biggest problem is that I have the shadow map resolution set way too high. I really should implement CSMs rather than having one shadow map per nearby tile. Try changing this: shadow_map_sz 4096 to shadow_map_sz 1024 in config_heightmap.txt
There also may be too many trees. You can open config.txt (which is included in config_heightmap.txt) and change: max_unique_trees 100 to max_unique_trees 20
For foo1.log, Mesa 13.0.6 is very old, I think about 6 years old. I'm using Mesa 18.
Ok cool I always wanted to see what was happening when consuming all GPU memory :) The message error is not very explicit. For Mesa this is the problem when using Debian :( Ok I'll give a try with other settings.
PS: Thanks for the notice concerning Mesa, I gonna try to update it https://linuxconfig.org/how-to-install-the-latest-mesa-version-on-debian-9-stretch-linux while you exaggerate a little mesa 13 is only 2017 https://www.mesa3d.org/index.html (you scared me lol)
If you enable DEBUG_TILES at the top of tiled_mesh.cpp you can see some count and memory usage printouts. For config_heightmap.txt, I see 66 tiles drawn of 341, 660 trees, and 33 shadow maps (of 4096x4096x4 = 64MB!) 2MB of memory for pine trees, 266MB for deciduous trees, 4MB for grass, and 2112MB for shadow maps! I didn't realize it was that high, and there's no reason to have 33 full resolution shadow maps. It should only need 4096x4096 shadow maps for the current tile. That's a total of 2491MB of GPU memory for just the tiles. Buildings, cars, pedestrians, textures, etc. take a few hundred MB more.
The easiest fix is to select a dynamic shadow map texture size based on the distance from the player to the tile. There's no reason to use full resolution shadow maps for distant tiles. This change reduces shadow map memory usage from 2112MB to 480MB - quite a reduction. I can't even tell the difference. I mapped a key to switch between full resolution and dynamic resolution shadow maps and it's hard to spot the occasional few pixels that differ. I can probably be even more aggressive with it, but I want to keep the highest quality shadows for my procedural city blog screenshots.
This is committed, but not well tested, and it needs more work. Adding CSMs is a better solution (though 10x the work). Is this enough to make it run on your machine with the default settings? I don't know.
Nice memory computations :) I guess tiles are the same idea than used for rendering maps (google, openstreet map) and closed to quadtrees. Do you use frustum culling for loading the necessary objects ? There are some methods used for avoiding shaders to compute on too many pixel/vertices. http://www.adriancourreges.com/projects/
Ok with your suggestions it works. Here again a crash report :) cras.txt
Here with valgrind (for this it was more difficult to produce this error, especially running at 1FPS :) valgrind_cars.txt
For that assert, I'm missing a check for the pedestrian model being valid before drawing it. I had the check for cars but not pedestrians because I've never tested that case. It should be fixed now. I'll get back to you on the valgrind errors later.
For the tile question, 3DWorld's tiled terrain mode uses square tiles of 128x128 mesh vertices. Tiles are generated in a radius around the player as the player walks around the scene. Tiles are populated with vegetation and other objects as the player moves toward them. Most of it is distance based, rather than using the view frustum, to avoid having to do too much work if the player suddenly turns their view. Walking speed is limitation but camera rotations are not. Some of the faster work such as sending data to the GPU is done based on the view frustum, and the actual rendering is certainly done with view frustum culling (and occlusion culling with terrain and buildings).
Seems to work
I made some changes:
Car and pedestrian model file paths are checked, and models that can't be found aren't added to the list of models to choose from. This means that it should never try to use a missing model file, and the error reporting is cleaner. You only get the default simple box/sphere models when there are no model files loaded.
I fixed tile shadow map LOD so that LOD levels are reduced even for tiles that aren't visible. That reduces GPU memory usage in cases where you walk in a straight line without ever turning around. Shadow map memory has been reduced from 2.1-2.2GB to 480MB init/704MB peak. Total GPU memory usage is now 1.2GB. You should be able to run with the default settings as long as you have a 2GB memory card. If you only have a 1GB card you probably still need to reduce max shadow map size and unique tree count.
I fixed that uninitialized memory access in building generation. I was calculating the size of a building level when only one side (left/right) was assigned. But it will recalculate that value and redo the check when the other side is assigned, so again it probably makes no observable difference. The rest of the uninitialized memory accesses are in the driver and not something I can fix.
Yep Valgrind first errors are made by the driver, I have this similar on my personal projects too. Others are fixed but I still failed in the same assertion while the commit of yesterday was ok for me. pedestr.txt
Sigh. Now it's failing for cars, in the shadow pass. It's missing the special case check for when none of the car models are valid. I fixed that in the pedestrians case but never tested it for no car models. I only tested it for some of the car models being invalid, and some/all pedestrian models being invalid. I'll fix it tonight.
Edit: I rephrase because I was in urge and I had not time to read myself the 1st time.
Compilation problem
SHA1 2f36272844a0d135308846cc0448c24a52ad02de The compilation is broken with gli. I tried the currently present folder then the git cloned version but got the same problem:
I have a Debian 9.8, amd 64, and g++-6.0. I dunno yet how to fix this. Note that glib compiles well by itself.
Some confusing points inside the README.linux:
src/
). This is not obvious. Then, I find this sad not to download these libs directly inside thedependencies/
folder. In the case of clone inside this folder, we have to update the Makefile:are not clear because we do not know if they refer to
gli
or3DWorld
. You should seperate them with sections. You also can remove these instructions because I explain just after how to simply you Makefile.Some confusing points inside the Makefile
lib/
folder? It should be created in any folder (obj/, the root project ...) execpt this one!all:
rule is not good for managingobj
andlib
directories. You should simply try the following code (explainations come just after):all: $(TARGET)
%.o : %.C $(BUILD)/%.d $(CPP) $(CPPFLAGS) -MMD -c $< -o $(abspath $(BUILD)/$@) %.o : %.cc $(BUILD)/%.d $(CPP) $(CPPFLAGS) -MMD -c $< -o $(abspath $(BUILD)/$@) %.o : %.cpp $(BUILD)/%.d $(CPP) $(CPPFLAGS) -MMD -c $< -o $(abspath $(BUILD)/$@)
$(OBJS): | $(BUILD) $(BUILD): @mkdir -p $(BUILD)
$(BUILD)/%.d: ; .PRECIOUS: $(BUILD)/%.d -include $(patsubst %,$(BUILD)/%.d,$(basename $(OBJS)))