Jump to content
The Dark Mod Forums

Light frustums different in DR and TDM


Recommended Posts

I can reinstall it this evening if nobody else chimes in.

That said, the Github page:

https://github.com/id-Software/DOOM-3-BFG

under base / renderprogs appears to have all the shaders ?

https://github.com/id-Software/DOOM-3-BFG/tree/master/base/renderprogs

  • Like 1

Please visit TDM's IndieDB site and help promote the mod:

 

http://www.indiedb.com/mods/the-dark-mod

 

(Yeah, shameless promotion... but traffic is traffic folks...)

Link to comment
Share on other sites

BFG interaction shader:

/*vertex*/
//# texture 2 has one texgen
result.texcoord2 = defaultTexCoord;
result.texcoord2.x = dot4( vertex.position, rpLightFalloffS );
//# texture 3 has three texgens
result.texcoord3.x = dot4( vertex.position, rpLightProjectionS );
result.texcoord3.y = dot4( vertex.position, rpLightProjectionT );
result.texcoord3.z = 0.0f;
result.texcoord3.w = dot4( vertex.position, rpLightProjectionQ );

/*fragment*/
half4 lightFalloff = idtex2Dproj( samp1, fragment.texcoord2 );
half4 lightProj	=    idtex2Dproj( samp2, fragment.texcoord3 );

Looks the same as in Doom 3: falloff parameter is not divided by perspective divisor of light matrix.

By the way, it is divided by perspective divisor of vertex position, which is necessary to correctly support vertices with w != 1.0. I guess we don't have such values (except for shadows), so it does not matter.

It means that my analysis for BFG is wrong: the depth is linear there too.
I'll edit that post in the nearest future.

P.S. I suppose they use the same light matrix with perspective division in the other parts of BFG code, which could be the reason why they decided to change conventions. The BFG matrix yields 0 at near and 1 at (near + far) both with and without division (given that someone has scaled the matrix by far+near) and results are monotonous. It can be used as ordinary MVP matrix e.g. for shadow maps of a spotlight... which would be a good explanation if D3BFG had shadow maps 😁

Link to comment
Share on other sites

Ok, here is corrected analysis for BFG code, assuming that falloff parameter is not divided by W.
See all the formulas in PDF.

Some notes:

  1. The inputs are the same as usual: R, U, T are unit vectors from spawnargs, and W, H, D (aka width, height, and depth) are the lengths of spawnargs.
  2. Coordinate "w" is divisor for "x" and "y", but not for "z".
  3. You can see decomposition of matrix into three transforms, which produce homogeneous coordinates for "x" and "y" in range [0..1] both. Computation of the falloff coordinate "w" is totally independent of this transformation.
  4. Coordinate "z" (falloff) must be scaled down by (n+f), which is achieved by returning 1 / (n+f) from R_ComputeSpotLightProjectionMatrix, then multiplying matrix[2] row by it.
  5. "start"/"end" spawnargs define linear gradient of falloff parameter, but only their component along "target" vector matters, the two other components don't affect anything.
  6. Let n = dot(target, start), f = dot(target, end). Falloff = 0 is at distance n, and falloff = 1 at distance (f + n). Quite surprisingly, "end" vector does not define the end of falloff texture, but ("start" + "end") does.
  7. Frustum half-angles are: length(right) / length(target) and length(up) / length(target). Multiplying all three vectors by same coefficient does not change anything, only their length ratios matter.
  8. If R/U/T triple is not orthogonal, then the matrix which I called "rotation" is no longer orthogonal. Strictly speaking, it maps a parallelepiped with face normals R, U, T respectively into axis-aligned box. The vectors are not axes of the local coordinate system (I guess they are called "cobasis"), they are not normals of frustum planes.

Judging from point 8, I don't think non-orthogonal R/U/T were ever intended.
However, if there is no special tools, drawing/specifying three exactly orthogonal vectors is very hard. If mapper sets almost orthogonal vectors, then the transformation will work almost as if they were orthogonal...

I'd say the spawnargs should be set as follows:

  1. Set frustum direction into "target" vector, choose length arbitrarily.
  2. Specify X/Y coordinates on frustum "screen" by choosing orthogonal "right" and "up" vectors.
  3. Set lengths of "right" and "up" in such way that (target +/- up +/- right) vectors look through frustum corners.
  4. Choose "start" and "end" so that falloff is zero at "start", and unit at "start" + "end".

DeriveLightData_Spot_BFG_v2.pdf

Link to comment
Share on other sites

So, I think the current code is close to BFG, except that someone has changed zScale from 1/(n+f) to 1/f (which most likely does not achieve the desired effect).
If we change it back to 1 / (n+f), then we'll have BFG behavior.

Here are the differences between D3 and BFG:

  1. D3 falloff ends in W, BFG falloff ends in Z and is multiplied by (f+n). Minor postprocessing code converts the matrix from BFG convention to D3 convention.
  2. D3 falloff changes along "end" - "start" vector, which makes it completely independent of the frustum. BFG falloff always changes along the frustum's main direction.
  3. Both falloffs are 0 at "start", but D3 falloff = 1 at "end", while BFG falloff = 1 at "start" + "end".
  4. If "target" is not orthogonal to "right"/"up" plane, then D3 internally projects it onto plane normal and uses the projected vector instead of "target" everywhere. BFG does not care and uses "target" directly.

What next?
We have to decide which of the two behaviors we want to reproduce both in TDM and in DR?
By the way, does DarkRadiant support D3BFG maps?

UPDATE: I guess I can restore both functions and allow switching between them easily...

Link to comment
Share on other sites

1 hour ago, stgatilov said:

We have to decide which of the two behaviors we want to reproduce both in TDM and in DR?

In DR terms I'd vote to keep falloff in Z and perspective divisor in W, for consistency with omni lights. Otherwise we'd need to add special handling in the shader to deal with projected lights using a different texture coordinate for the falloff texture.

Note that we don't force mappers to use start/end (they have to tick another box to enable it), so rendering code needs to handle the situation where these values are unset. I think we currently assume no start means light origin, and no end means the same as the target vector.

1 hour ago, stgatilov said:

By the way, does DarkRadiant support D3BFG maps?

We don't explicitly support it and I haven't done any testing myself. It might happen to work by accident though.

Link to comment
Share on other sites

2 hours ago, stgatilov said:

So, I think the current code is close to BFG, except that someone has changed zScale from 1/(n+f) to 1/f (which most likely does not achieve the desired effect).
If we change it back to 1 / (n+f), then we'll have BFG behavior.

Here are the differences between D3 and BFG:

  1. D3 falloff ends in W, BFG falloff ends in Z and is multiplied by (f+n). Minor postprocessing code converts the matrix from BFG convention to D3 convention.
  2. D3 falloff changes along "end" - "start" vector, which makes it completely independent of the frustum. BFG falloff always changes along the frustum's main direction.
  3. Both falloffs are 0 at "start", but D3 falloff = 1 at "end", while BFG falloff = 1 at "start" + "end".
  4. If "target" is not orthogonal to "right"/"up" plane, then D3 internally projects it onto plane normal and uses the projected vector instead of "target" everywhere. BFG does not care and uses "target" directly.

What next?
We have to decide which of the two behaviors we want to reproduce both in TDM and in DR?
By the way, does DarkRadiant support D3BFG maps?

UPDATE: I guess I can restore both functions and allow switching between them easily...

It was me trying to fix I can't remember what now. I changed to 1 / (n+f) locally but did not commit yet

Can you please look where the "end" pos projects to with the both matrices?

Quote

Both falloffs are 0 at "start", but D3 falloff = 1 at "end", while BFG falloff = 1 at "start" + "end".

Does it mean that the "end" changed its meaning in BFG? What should we do about it?

56 minutes ago, OrbWeaver said:

In DR terms I'd vote to keep falloff in Z and perspective divisor in W, for consistency with omni lights. Otherwise we'd need to add special handling in the shader to deal with projected lights using a different texture coordinate for the falloff texture.

Note that we don't force mappers to use start/end (they have to tick another box to enable it), so rendering code needs to handle the situation where these values are unset. I think we currently assume no start means light origin, and no end means the same as the target vector.

We don't explicitly support it and I haven't done any testing myself. It might happen to work by accident though.

We don't discuss changing anything in this regard (like swapping W and Z)

Please don't worry about it

The two matrix styles co-exist in TDM, they don't replace each other anywhere

Link to comment
Share on other sites

31 minutes ago, duzenko said:

Can you please look where the "end" pos projects to with the both matrices?

Theoretically, "end" should map to falloff = 1 with D3 code and to (1 - f/n) with BFG code.

Quote

Does it mean that the "end" changed its meaning in BFG? What should we do about it?

Yes, it has changed its meaning, as well as handling of non-orthogonal "target" and weirdly-oriented "start"/"end".
Given that behavior was silently changed to BFG several releases ago (I guess since 2.05), we have to decide which behavior should be left now so that number of broken FMs is minimal 😥

That's why I never understood why some people tried to copy/paste some bits of code from BFG without any reason for doing so, except "maybe it would be faster... ID did this, so it must be better" 🤬

Quote

We don't discuss changing anything in this regard (like swapping W and Z)
The two matrix styles co-exist in TDM, they don't replace each other anywhere

Yes, sort of.
Now matrix is generated in BFG convention, but then converted to D3 convention. As long as people remember what happens where, it should be OK.
 

Link to comment
Share on other sites

3 hours ago, stgatilov said:

Theoretically, "end" should map to falloff = 1 with D3 code and to (1 - f/n) with BFG code.

Yes, it has changed its meaning, as well as handling of non-orthogonal "target" and weirdly-oriented "start"/"end".
Given that behavior was silently changed to BFG several releases ago (I guess since 2.05), we have to decide which behavior should be left now so that number of broken FMs is minimal 😥

 

Do you have opinion on how those changes practically change mapping and gameplay?

Is there any upside/downside? (even if theoretical)

Right now I'm leaning towards reverting to D3 matrix, because this thing has dragged long enough, generated lots of noise and so far has lead to nothing other than examples of incompatibility

I'm talking about the BFG projection matrix build function only, not the render matrix stuff (which looks quite useful)

Link to comment
Share on other sites

8 hours ago, duzenko said:

I'm talking about the BFG projection matrix build function only, not the render matrix stuff (which looks quite useful)

It is not so easy, because in both cases light frustum which is later used in frontend depends on the matrix row which defines falloff parameter.

This code won't work properly with D3 light matrix:

	// calculate the global light bounds by inverse projecting the zero to one cube with the 'inverseBaseLightProject'
	idRenderMatrix::ProjectedBounds( light->globalLightBounds, light->inverseBaseLightProject, bounds_zeroOneCube, false );

Because it assumes that matrix is 3x3 projective transform, i.e. it relies on BFG definition with Z divided by W.

Doom 3 code used to set clipping planes directly from start/end parameters, so the resulting polyhedron does not need to be frustum:

/*
===================
R_SetLightFrustum

Creates plane equations from the light projection, positive sides
face out of the light
===================
*/
void R_SetLightFrustum( const idPlane lightProject[4], idPlane frustum[6] ) {

	// we want the planes of s=0, s=q, t=0, and t=q
	frustum[0] = -lightProject[0];
	frustum[1] = -lightProject[1];
	frustum[2] = -(lightProject[2] - lightProject[0]);
	frustum[3] = -(lightProject[2] - lightProject[1]);

	// we want the planes of s=0 and s=1 for front and rear clipping planes
	frustum[4] = -lightProject[3];
	frustum[5] = lightProject[3];
	frustum[5][3] -= 1.0f;

	frustum[5][3] /= frustum[5].Normalize();
	frustum[4][3] /= frustum[4].Normalize();
	frustum[3][3] /= frustum[3].Normalize();
	frustum[2][3] /= frustum[2].Normalize();
	frustum[1][3] /= frustum[1].Normalize();
	frustum[0][3] /= frustum[0].Normalize();
}

In order to restore D3 code for shading but retain BFG code with inverseBaseLightProject, one has to:

  1. Build D3 matrix.
  2. Compute 6 planes from D3 matrix using D3 code.
  3. Build polytope from these planes.
  4. Bound polytope vertices from forward/backward, i.e. determine minimal near/far clipping distances which are enough to cover the whole light volume.
  5. Build BFG matrix with several adjustments: a) "target" is orthogonalized like in D3, b) near/far values taken from what we obtained on step 4, c) replace "far" with "far" - "near".
  6. Use D3 matrix for shading, and BFG matrix for culling.

 

Link to comment
Share on other sites

I'm going to run analysis over all released FMs to see which atypical cases are present where.

I hope we will be able to restrict R/U/T to orthogonal and start/end to being collinear with T, in which case the only difference would be falloff = 1 at far vs (far + near), which I guess we will be able to fix...

Link to comment
Share on other sites

2 hours ago, stgatilov said:

It is not so easy, because in both cases light frustum which is later used in frontend depends on the matrix row which defines falloff parameter.

This code won't work properly with D3 light matrix:

	// calculate the global light bounds by inverse projecting the zero to one cube with the 'inverseBaseLightProject'
	idRenderMatrix::ProjectedBounds( light->globalLightBounds, light->inverseBaseLightProject, bounds_zeroOneCube, false );

Because it assumes that matrix is 3x3 projective transform, i.e. it relies on BFG definition with Z divided by W.

Doom 3 code used to set clipping planes directly from start/end parameters, so the resulting polyhedron does not need to be frustum:

/*
===================
R_SetLightFrustum

Creates plane equations from the light projection, positive sides
face out of the light
===================
*/
void R_SetLightFrustum( const idPlane lightProject[4], idPlane frustum[6] ) {

	// we want the planes of s=0, s=q, t=0, and t=q
	frustum[0] = -lightProject[0];
	frustum[1] = -lightProject[1];
	frustum[2] = -(lightProject[2] - lightProject[0]);
	frustum[3] = -(lightProject[2] - lightProject[1]);

	// we want the planes of s=0 and s=1 for front and rear clipping planes
	frustum[4] = -lightProject[3];
	frustum[5] = lightProject[3];
	frustum[5][3] -= 1.0f;

	frustum[5][3] /= frustum[5].Normalize();
	frustum[4][3] /= frustum[4].Normalize();
	frustum[3][3] /= frustum[3].Normalize();
	frustum[2][3] /= frustum[2].Normalize();
	frustum[1][3] /= frustum[1].Normalize();
	frustum[0][3] /= frustum[0].Normalize();
}

In order to restore D3 code for shading but retain BFG code with inverseBaseLightProject, one has to:

  1. Build D3 matrix.
  2. Compute 6 planes from D3 matrix using D3 code.
  3. Build polytope from these planes.
  4. Bound polytope vertices from forward/backward, i.e. determine minimal near/far clipping distances which are enough to cover the whole light volume.
  5. Build BFG matrix with several adjustments: a) "target" is orthogonalized like in D3, b) near/far values taken from what we obtained on step 4, c) replace "far" with "far" - "near".
  6. Use D3 matrix for shading, and BFG matrix for culling.

 

Oh dear

I thougt that local var localProject is not used beyond creating the light->lightProject

But I can now see it's used for light->baseLightProject

Let's start again

The conversion between the two styles is rather straightforward. Can't we use R_SetLightProject for D3 style matrix and then compute BFG style by swapping columns and normalizing Z?
 

Link to comment
Share on other sites

44 minutes ago, duzenko said:

The conversion between the two styles is rather straightforward. Can't we use R_SetLightProject for D3 style matrix and then compute BFG style by swapping columns and normalizing Z?

That would be incorrect, because BFG frustum and D3 frustum can be different geometrically. You suggest drawing D3 frustum but culling BFG frustum. In some rare cases light might disappear due to incorrect frontend culling.

Link to comment
Share on other sites

1 hour ago, stgatilov said:

That would be incorrect, because BFG frustum and D3 frustum can be different geometrically. You suggest drawing D3 frustum but culling BFG frustum. In some rare cases light might disappear due to incorrect frontend culling.

I was thinking the frustum is generated from the matrix. The both style matrices define the same frustum since one is computed from the other. Currently the D3-style matrix is computed from the BFG one. I was suggesting to reverse this so that the BFG matrix is computed from the D3 one.

This way, we will end up with the D3 frustum, and both matrices would be compatible with it

Link to comment
Share on other sites

1 hour ago, duzenko said:

The both style matrices define the same frustum since one is computed from the other.

No.

We should differentiate between convention (is falloff in Z or W? should it be scaled by some number?) and behavior (shape of frustum, falloff value).
Yes, the current code converts the matrix from BFG convention to D3 convention, but it does not & cannot convert behavior. And behaviors are different between D3 and BFG.

Quote

I was suggesting to reverse this so that the BFG matrix is computed from the D3 one.

If start/end vectors are not collinear with target vector, then you cannot convert D3 matrix in such a way that idRenderMatrix::ProjectedBounds/baseLightProject would work properly with the result. In BFG, light volume is always a frustum, but in D3 its near/far planes can be arbitrary. Maybe something can be hacked by making target vector non-orthogonal to R/U plane, but I'd better avoid that.

Link to comment
Share on other sites

Makes you wonder why the original code is using full vectors for all the parameters, given that these can be combined in ways which don't make sense.

Surely start and end would be better as scalar values, representing "distance along the target vector", rather than arbitrary vectors in 3D space which might point in completely different direction to the target vector?

  • Like 1
Link to comment
Share on other sites

On 10/20/2021 at 4:37 AM, OrbWeaver said:

I'm not sure why you think a single numerical value is somehow better than a falloff image. A falloff image can represent any falloff pattern (linear, quadratic, full brightness, whatever), not just whatever hard-coded formula is programmed in by the developers.

I suppose there would be no harm in also offering a math-based falloff as an option for mappers, e.g where "falloff <power>" represents a power of Z: 0 for no falloff, 1 for linear, 2 for quadratic, 3 for cubic etc. But this is a separate issue from making the existing approach work correctly.

 

Texture lookup operations are more expensive than math on modern GPU's. Texture lookups also require lerping to smooth the brightness transitions whereas math can be set to a perfect falloff curve with no step-wise artifacts other than conversion aliasing. If we ever decide to go to a Forward+ design it is difficult to implement two texture lookups for each light ( one is hard enough ).

  • Like 2

Please visit TDM's IndieDB site and help promote the mod:

 

http://www.indiedb.com/mods/the-dark-mod

 

(Yeah, shameless promotion... but traffic is traffic folks...)

Link to comment
Share on other sites

2 hours ago, stgatilov said:

No.

We should differentiate between convention (is falloff in Z or W? should it be scaled by some number?) and behavior (shape of frustum, falloff value).
Yes, the current code converts the matrix from BFG convention to D3 convention, but it does not & cannot convert behavior. And behaviors are different between D3 and BFG.

If start/end vectors are not collinear with target vector, then you cannot convert D3 matrix in such a way that idRenderMatrix::ProjectedBounds/baseLightProject would work properly with the result. In BFG, light volume is always a frustum, but in D3 its near/far planes can be arbitrary. Maybe something can be hacked by making target vector non-orthogonal to R/U plane, but I'd better avoid that.

Then what are we left with? It would be great to keep the render matrix stuff but you say it requires the BFG-constructed projection matrix? I suppose we should then change DR to use that method as well?

Probably the only change should be the end pos being relative to origin rather than start to improve compatibility with the existing maps?

Quote

In BFG, light volume is always a frustum, but in D3 its near/far planes can be arbitrary.

Sorry, what do you mean by that? Being a frustum means that near/far planes are parallel to XY or ???

Link to comment
Share on other sites

  • 2 weeks later...

I searched for spotlights in released FMs:

  1. Non-orthogonal T/R/U is used frequently.
  2. T not orthogonal to RxU plane is used frequently.
  3. S->E direction not collinear to T is used rarely.

I have attached log files showing all problematic entities in each category, with sine/cosine of angle printed for them. Low numbers means that directions are almost collinear/orthogonal.

So, we can supposedly leave arbitrary start/end case broken. In fact, I'd say it is better to forcibly project "start"/"end" onto "target" direction. The most important broken cases would be light_attic_yellow_window in bcd and light_7 in windowopportunity.

As for near/far planes, @duzenko has already committed the change to return to old behavior: just use "end" - "start" instead of "end", which means that far clipping plane is now at "end" and falloff parameter = 1.0 at "end" too.

But as for non-orthogonal T/R/U, I guess we'd better reproduce the old behavior.


 

spotlights_TRU_nonortho.txt spotlights_RUplane_T_nonortho.txt spotlights_SE_T_noncollinear.txt

  • Thanks 1
Link to comment
Share on other sites

3 hours ago, stgatilov said:

I'm rather clueless on how to accomplish that due to my inadequate skills in linear algebra

Can you do that while keeping the idRenderMatrix stuff in working condition?

Will it be 100% what D3 projection was, or something in-between that and BFG?

At any rate any decision is better than no decision as the beta season starts soon

Link to comment
Share on other sites

  • 2 weeks later...
On 10/30/2021 at 10:48 AM, stgatilov said:

I searched for spotlights in released FMs:

  1. Non-orthogonal T/R/U is used frequently.
  2. T not orthogonal to RxU plane is used frequently.
  3. S->E direction not collinear to T is used rarely.

So, we can supposedly leave arbitrary start/end case broken. In fact, I'd say it is better to forcibly project "start"/"end" onto "target" direction.

Not really.

In fact, my check in case 3 is wrong: the falloff is different when S->E is not collinear to normal vector (N = R x U), not the target (T) one. Basically, almost all cases from p.2 are also affected, i.e. I cannot reproduce their falloff behavior with BFG code.

That includes maps:

Spoiler
  • alchemist
  • antr
  • bcd
  • betrayal
  • braeden_church
  • goneinsmoke
  • caduceus
  • cathedral
  • cauldron_v2
  • dufford
  • elixir
  • man2
  • heart
  • hh1
  • hhtlc
  • itb
  • kotm
  • lodge
  • mother
  • nowandthen
  • city
  • pearls8
  • ravine
  • remembrance
  • requiem
  • somewhere
  • kiss
  • thiefs_den
  • ws1_north
  • windowopportunity

Basically, tons of FMs are affected.
And there is no way to reproduce their falloff behavior from TDM 2.04 with current matrix-based code.
Some of them were released after 2.05, so they should work as intended now (and will stop working so).
 

spotlights_SE_N_noncollinear.txt

Link to comment
Share on other sites

I think if we want to restore Doom 3 behavior, we should revert that change and remove BFG-style light matrices. That would make life much easier in the future.

Before the change, there was one matrix stored in light. After the change, there are three matrices, two of which are inverse to each other. Now if I try to reproduce old behavior but retain the new BFG matrices, then the two matrices would become different: one would store actual light projection data, the other one will represent some bounding frustum. I imagine that would be endless source of confusion, and will cause similar problems in the future in case someone decides to touch this code.

If we remove both BFG-style projection matrices, then everything will be simple again.

Link to comment
Share on other sites

Having a single matrix definitely sounds sensible to me, both from a Don't Repeat Yourself (DRY) principle and for easier compatibility with the DR renderer which only has a single matrix per light (although the renderable frustum is derived via different code).

Link to comment
Share on other sites

6 hours ago, duzenko said:

The d3 frustum is not air tight

There is no frustum in Doom 3, there is a set of planes.
And as a set of planes, it has no issues.
I'm pretty sure the code to compute vertices can be modified to fix the problem.

Quote

Bfg code has some nice optimizations

But it is not clear how important they are.
 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share


  • Recent Status Updates

    • freyk

      Some years earlier i created some launchers for TDM. uploaded today some new sourcecode and executables on my github repo.
       
      · 0 replies
    • Epifire

      Say, I know it's been a while since the site got overhauled from the crash. But did we ever figure out if/how to get the recent topics & replies list back? It's not a total deal breaker but it was nice for becoming a thread creeper again...


      · 2 replies
    • Epifire

      Some of you who've been on the TDM discord know I was out of work last Winter, just putting in hrs for the developer portfolio. Currently I've been working a seasonal job to pay the bills and now I'm finally in my last week before I get bumped off. Things will be tight but I'm planning a long off period to make as much content as I can. Big plans in Unreal Engine as well as my most ambitious TDM collab yet! Never been so excited to be a stuck at home to pursue my life's work. With a lotta time and maybe some luck, I'm hoping to get enough art work done that I may start applying around to studios.
      · 4 replies
    • STiFU

      I finally got around to play Prey and I truly loved it. It is an incredible homage to System Shock and Deus Ex. While the gameplay is not en par with those two titles, the game makes up for that with its well written lore and story. The whole "world" just feells so authentic and it features a ton of really god environmental story-telling. Recommended for every immersive sim fan, i.e., everyone on this forum.
      · 2 replies
    • jaxa

      Alder Lake has arrived:
      https://forums.thedarkmod.com/index.php?/topic/18055-2016-cpugpu-news/page/15/&tab=comments#comment-466190
       
      · 0 replies
×
×
  • Create New...