Skip to content

Conversation

@illwieckz
Copy link
Member

Work-in-progress attempt to implement compatibility for naive blended assets in the linear pipeline.

It includes a patch to delay the loading of textures from pre-collapsed stages to the end of the stage parsing, after the blend mode is known, because it is required to select the right compatibility code for the given stage.

Also make sure to invalidate stages with a missing colormap
that is of different kind (diffusemap…).
@illwieckz illwieckz added A-Renderer T-Feature-Request Proposed new feature labels Oct 10, 2025
@illwieckz illwieckz force-pushed the illwieckz/naive-blending-compatibility branch from 40bba05 to 5302a8b Compare October 10, 2025 03:04
@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

For now I haven't implemented a keyword to enforce blended stages to be processed the linear way.

The compatibility mode simply makes the texture and the colorGen not being converted from sRGB, making the colors painted as linear, like the naive pipeline did before the linear pipeline was implemented.

@slipher
Copy link
Member

slipher commented Oct 10, 2025

In what scenario is the code intended to be used? For example rendering legacy maps in linear blendspace? Rendering new game models in naive blendspace?

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

In what scenario is the code intended to be used?

Rendering maps recompiled for the linear pipeline but without any asset change.

You take a legacy map, you rebuild it, you modify nothing else, it would be “good enough”.

To complete the port one would have to modify the blending shaders, but this compatibility mode removes that step as a hard requirement.

Especially, maps using only the filter blend mode like forlorn can even avoid any tweak.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

Map forlorn: 3716 2523 226 123 -3

Point of interest: the background door, behind two windows in a row.

Naive blend shaders, naive build, naive renderer:

unvanquished_2025-10-10_051630_000

Naive blend shaders, linear build, linear renderer:

unvanquished_2025-10-10_051918_000

Naive blend shaders, linear build, linear renderer with compatibility blend mode:

unvanquished_2025-10-10_050758_000

Map atcshd, viewpos 720 -384 296 150 0

Point of interest: the force field.

Naive blend shaders, naive build, naive renderer:

unvanquished_2025-10-10_051746_000

Naive blend shaders, linear build, linear renderer:

unvanquished_2025-10-10_052000_000

Naive blend shaders, linear build, linear renderer with compatibility blend mode:

unvanquished_2025-10-10_051035_000

The glass in forlorn uses blend filter and it looks just right with the compatibility mode. There is nothing to edit, we just have to rebuild the map and call it a day.

The force field in atcshd uses blend add and then the compatibility is much more perfectible, but that's still better with the compatibility mode. It's better to edit the asset, but that's already a good start that doesn't look broken. Without the compatibility mode the waving grid on the force field is almost invisible, now we see the wave effect as expected. If you don't compare the maps side by side before and after the rebuild, with the compatibility mode you may believe it's what's intended, as it would be harder to notice the color isn't exactly as the one expected, while without the compatibility mode you immediately notice that something is broken without needing any comparison reference.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

One that suffers a lot is the plat23 map, that is also the map for which the lighting itself is the most affected, we may even consider to tweak the lighting, not just the blended forcefield shader.

Map plat23, viewpos 0 2688 192 -90 13

Point of interest: the force field.

Naive blend shaders, naive build, naive renderer:

unvanquished_2025-10-10_053614_000

Naive blend shaders, linear build, linear renderer:

unvanquished_2025-10-10_053851_000

Naive blend shaders, linear build, linear renderer with compatibility blend mode:

unvanquished_2025-10-10_053959_000

We notice that without the compatibility mode, we cannot see the grid of the forcefield at all, and the whole surface is too much transparent, while with the compatibility mode, we see the grid and the surface is opaque enough, that fixes an element of gameplay.

Of course the color would better be tweaked, and like with atcshd, we can make it a bit less opaque, but the gameplay is fixed by the compatibility mode.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

Alongside forlorn, a map that can be re-released just by rebuilding it once the compatibility mode is implemented is vega.

Map vega, viewpos: -181 -151 133 35 13

Points of interest: the various glass surfaces on doors and cylindrical models on the floor.

Naive blend shaders, naive build, naive renderer:

unvanquished_2025-10-10_055224_000

Naive blend shaders, linear build, linear renderer:

unvanquished_2025-10-10_055543_000

Naive blend shaders, linear build, linear renderer with compatibility blend mode:

unvanquished_2025-10-10_055644_000

Something we can touch-up a bit in vega are the corridor lamp glow maps (the ones I discuss aren't visible in those screenshots), some would look better if more brighter, but they are not an obvious problem like the door glass is. This glow map tweak is only wanted because since the light attenuation curve is more physically correct, the ambiant light is brighter, and then the contrast between usual surfaces and glow maps is less obvious. Tweaking the glow maps would be just a perceptual work to improve the looking of the map, there is no bug in them. It just happens that the mapper never had the opportunity to test his glow maps with a physically correct lighting, so he couldn't know they weren't bright enough.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

Hmm, one that looks ugly with that compatibility mode for add is yocto, with the linear mode some yocto glass looks correct on some surfaces and some other kind of glass looks almost correct. The add blend mode can't have a fully automated compatibility mode anyway, while the compatibility mode will probably always look correct with filter.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

Hmm, while for filter it's always good, with add it's a hit or miss. Half of the time it's better, half the time it's worst.

That's annoying because I was hoping for a quirk that would be better in average.

If we can't be better in average for blend modes as popular as add, then it means we require a manual knob for half of them. And if we require a manual knob for half of them, it is better that this manual knob is only used to fix thing, not to cancel the fix.

And if a blend mode cannot be automated and we better default to the “non fix” for it, then for consistency we better stick to the default behavior for all blend modes.

What this experiment demonstrated though, is that switching to either linear or to naive mode is always a good way to give a better result in a very quick way, without editing the actual files.

So I plan to do that:

  • rely on the code already implemented in this branch to always know the blend options when loading a texture
  • rely on the code already implemented in this branch to be able to switch the light mode per blended stage
  • don't automate that switch
  • add a keyword like blendMode naive and blendMode linear to annotate stages
  • add a cvar to force a blend mode to easily test which blend mode better suits a given scene (for the mapper to test things)

All the maps I have tested from stock Unvanquished assets and InterStellarOasis ones were able to immediately get something usable just by selecting one mode or another. I assume we can blindly enable the linear mode in all the blend filter stages, and for the other ones, we have to check.

Selecting one mode or another per blend stage is enough to be able to re-release a map just by rebuilding it, without breaking the gameplay. Tweaking the assets to make them look better is only an improvement over that.

It's probable that only the quoted force fields may really benefit from manual asset tweaking outside of just flipping a blend mode in a shader (like actually editing an image).

@slipher
Copy link
Member

slipher commented Oct 10, 2025

  1. How will this compatibility mode be controlled? A worldspawn option to turn it on for all map surfaces? But then what if texture packs used by the map have already been optimized for linear blendspace?
  2. The heuristic-based color transformation here (compared to the default behavior, it converts some colorgen and texture colors from linear to SRGB) is basically fudging things to be brighter; there is no real connection to correct sRGB conversion. We could just as well consider a function like color *= 1.3 for making stuff brighter. So for the colorgen stuff at least, I suggest renaming pStage->convertColorFromSRGB to something more generic like adjustColorForBlendspace so that we give ourselves freedom to use different heuristic functions later.
  3. filter is the one blend function that behaves almost identically between linear and naive blending. I believe we shouldn't need to do anything to it. But that spot on Forlorn indeed comes out anomalously dark... I will try to investigate.
  4. The compatibility heuristic should only act on an approved and tested list of blend functions, since with other ones it may make things worse. With naive blending, blendfunc add systematically makes things brighter than they should be, so it may help to apply a compatibility heuristic making things brighter. But with naive blending, alpha blending systematically makes things darker than they should be, so the existing heuristic would make things worse. Looks like it has just been tested on add and filter blending so far.
  5. I've been intending to add a cvar r_forceBlendspace which would allow us to decouple the blending regime used by the renderer, and the sRGB-ness of the lightmaps. It would be useful for debugging purposes: having those two things tied together makes it hard to tell whether changes in a map are caused by different lightmaps or by different blending. It would help investigate things like #3 in this list. With the cvar you would be able to use naive blending with a map built with sRGB lightmaps or linear rending with a legacy map. I already implemented this before on a branch while evaluating other sRGB-related stuff, so I just need to port/rebase the code. Hmm, I like the term "blending regime", maybe I will call it r_forceBlendRegime instead.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

  1. How will this compatibility mode be controlled? A worldspawn option to turn it on for all map surfaces? But then what if texture packs used by the map have already been optimized for linear blendspace?

If we do a compatibility mode, it would be the default mode, and would use heuristics to somewhat fix legacy assets in the linear pipeline.

And then q3shaders fixed for the linear blending would have a special keyword to tell they're fully linear and skip the heuristics. That was my idea when I first started to work on this.

My first idea was that the blend something or blendFunc something syntax would automatically use the compatibility mode with heuristics, and then we would have something like linearBlend something for non-legacy assets to skip the heuristics.

Now, the results of my tests with blend add seem to geopardize such default compatibility mode as I now know no acceptable heuristics for blend add.

I don't see any advantage of making a compatibility mode that doesn't work out of the box, if it requires to flag something to enable the compatibility mode, then it's not a compatibility mode.

Now, what we can do is to not have such compatibility mode, but to add compatibility keywords to the shader lexicon.

Basically one would do:

blend add
blendMode naive

This requires every shader that are known to work better the naive way to be tagged this way. With legacy maps it would do nothing, it's already naive, with maps rebuilt for the naive pipeline, it would upload the image as is without transformation, the naive way, because we would have tested we prefer that look.

If we can find good heuristics that works “good enough” like 95% of the time, we can consider such automatic compatibility mode, otherwise no. Right now with blend add it's fifty-fifty so with my current knowledge such automatic compatibility mode isn't possible.

Now, one thing I need to say is that I want to guarantee mappers and map porters they wont have to edit all the blend images in an image editor to benefit from the linear pipeline. I want to reduce the map migration cost as much as possible, ideally just requiring a rebuild with the new map compilation profile, otherwise we will suffer maps rebuilt the legacy way forever, and this is not good because we have unfixable bugs with maps built the legacy way, especially when we start adding specular maps, etc.

If we cannot avoid requiring the edit of some legacy assets when rebuilding map the linear way, then the cost of such edit should be the lowest possible. If switching from linear to naive a blend shader fixes it in an acceptable way, that cost looks small enough for me. Maps usually only have one or two blend shaders, with luck, some of them will already in be rendered in a “good enough way” without doing anything. Using a trick once or twice per map by adding a keyword to a shader is fine. Also I don't mind if such trick hasn't any mathematical meaning.

The correct way to fix a blend shader is to edit the image, but if a low-cost stupid and wrong trick provides an acceptable result, we should provide it. In other words: I want the mappers to be able to port the maps first and procrastinate the perfect solution for the only one shader that doesn't work well by default. If we don't give such option, mappers may keep the whole map the legacy way while only one shader is broken the linear way, that would be a shame…

@illwieckz
Copy link
Member Author

And by mappers I include myself, I want to rebuild all the Unvanquishes stock maps the linear way as soon as possible, and I want to do the same for the InterstellarOasis maps. I don't want to edit images. I don't want to allocate time to run a trial and error dance while editing images on every step to fix a shader for the linear build. One force field to edit? Why not, but not more than that.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

It's also important to know that once an Unvanquished release is published with the stock maps built the linear way, I will make that map build profile the default in Urcheon and NetRadiant and I will re-release NetRadiant. They will provide a legacy build profile, but the linear one will become the default.

That's also why I want to reduce the cost of porting maps to the linear pipeline to zero if possible, almost zero otherwise. If heuristics can do that, let's go. If heuristics can't do, I want the port effort to only require one or two keywords added to shaders, per map, and for not all the maps.

We need that starting from a given day, very soon, every newly built map, brand new or rebuilt, will use the linear pipeline, by default.

@slipher
Copy link
Member

slipher commented Oct 10, 2025

It seems wrong to enable compatibility heuristics by default. This means that a mapper starting a new map from scratch will be burdened by compatibility cruft, unless they know about the hidden option to turn it off.

I don't see any advantage of making a compatibility mode that doesn't work out of the box, if it requires to flag something to enable the compatibility mode, then it's not a compatibility mode.

Supposed you have implemented the magical compatibility heuristic that makes everything look good 100% guaranteed. If the compatibility mode is off by default, all you have to do to re-release a map is add the sRGB flags for q3map2, enable the compatibility option, and rebuild. That's hardly more difficult than just changing the q3map2 flags and rebuilding. For artists, the more common pattern is to work on the map for a relatively brief period of time, then stop. Legacy map re-releases are likely to be done by a more technical person such as yourself. It doesn't make sense to hobble artists starting a new map just to make recompiling a legacy map 1% easier.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 10, 2025

It seems wrong to enable compatibility heuristics by default. This means that a mapper starting a new map from scratch will be burdened by compatibility cruft, unless they know about the hidden option to turn it off.

Unless newly made shaders use a special syntax that never rely on heuristics. My idea was that the historical blendFunc function would do heuristics, and newly assets would use another keyword and skip the heuristics. This way the engine would always have the knowledge of the shader being naive or not. But for that we need heuristics that work good enough, something we don't have.

So I don't consider such heuristics anymore anyway, unless someone comes with some magic by the end of the next week.

@slipher
Copy link
Member

slipher commented Oct 10, 2025

Unless newly made shaders use a special syntax that never rely on heuristics. My idea was that the historical blendFunc function would do heuristics, and newly assets would use another keyword and skip the heuristics.

Well, the Q3A shader manual still seems to be state-of-the-art for shader documentation... the likely result is that 90% of new maps would keep using the old blendFunc. Only the most intrepid mappers like Matth who reverse-engineer Unvanquished assets to find out how to make cool effects would have a chance of using it 😆

@illwieckz illwieckz force-pushed the illwieckz/naive-blending-compatibility branch from 5302a8b to bf586ee Compare October 11, 2025 02:02
@illwieckz
Copy link
Member Author

illwieckz commented Oct 11, 2025

So,

  • I rewrote a bit the delayed image loader,
  • I removed the compatibility heuristics and added the naiveBlend shader keyword.

Here is how looks the forcefield shader on plat23 when I set that naiveBlend keyword on the forcegrid stage, and only on it:

unvanquished_2025-10-11_040325_000

Here is the modified shader:

textures/plat23_custom/forcefield
{
	qer_editorImage textures/plat23_custom_src/forcefield_p
	surfaceparm trans
	surfaceparm nomarks
	surfaceparm nolightmap
	cull none
	{
		map textures/plat23_custom_src/forcefield_a
		tcMod Scroll .1 0
		blendFunc add
	}
	{
		map textures/plat23_custom_src/forcegrid_a
		tcMod Scroll -.01 0
		blendFunc add
		naiveBlend
		rgbgen wave sin .2 .2 0 .4
	}
	{
		map textures/plat23_custom_src/forcefield_heathaze
		stage heathazeMap
		deformMagnitude 1
		tcmod scale 1 1
		tcmod scroll .03 .03
	}

It's a very easy way to workaround a shader designed for the naive pipeline when rendering with the linear pipeline.

I like it.

@illwieckz illwieckz changed the title WIP: naive blending compatibility Naive blending compatibility Oct 13, 2025
@illwieckz illwieckz force-pushed the illwieckz/naive-blending-compatibility branch from bf586ee to c29689f Compare October 13, 2025 03:43
@illwieckz
Copy link
Member Author

That may be ready.

I now believe it's a bad idea to have a cvar to force a blend mode for quickly testing how shaders in a scene render, because there can be multiple blended stages in a single shader, and people may want to only tweak on of them. Such cvar would tweak all of them at once and then would be useless.

@illwieckz
Copy link
Member Author

I also implemented the delaying of animation textures.

@illwieckz
Copy link
Member Author

This also improves over existing code (even without using the feature):

  • Invalidate the stage when the colormap is missing when it's not a colormap stage (like diffuse map)
  • Do not attempt to load a colormap after an animMap.

@illwieckz
Copy link
Member Author

illwieckz commented Oct 15, 2025

The implementation looks ready to me.

What we may discuss is the name of the naiveBlend keyword, we may also name it naiveStage or just naive. Because in fact that keyword even works with non-translucent stages.

@illwieckz
Copy link
Member Author

What we may discuss is the name of the naiveBlend keyword, we may also name it naiveStage or just naive. Because in fact that keyword even works with non-translucent stages.

With the exception the lightmap is never naive.

@slipher
Copy link
Member

slipher commented Oct 15, 2025

That would be too much complex. This would require specific GLSL code, specific rgbGen color conversion functions with extra tests, etc.

Yeah, the multiplying by a constant idea that I wrote first would be easier and should be able to use existing code paths with minor tweaks. But I don't know whether it would really look good.

I still want to know the answer to this question:

Is the design goal of this to make a shader that can load differently depending on the blend regime, or is it just for one-way migrations to linear blending?

@illwieckz
Copy link
Member Author

illwieckz commented Oct 16, 2025

I still want to know the answer to this question:

Is the design goal of this to make a shader that can load differently depending on the blend regime, or is it just for one-way migrations to linear blending?

It's just to turn an existing shader that is already used with the naive pipeline to be reusable with the linear pipeline, to make it easy to migrate a map to the linear pipeline. Nothing else.

The mechanism has no effect in maps built the naive way, the old assets will render exactly the same even if we add this stage keyword in a shader they use.

There is no reason to produce a new texture set with such fallback for maps built the naive way, unless you import an existing texture set from another game, because there is no reason to create a new map and build it the naive way once the whole pipeline is implemented. The naive way is a pile of bugs, it only exists for backward compatibility with historical assets.

@illwieckz illwieckz force-pushed the illwieckz/naive-blending-compatibility branch from dcaabdd to ab0213f Compare October 16, 2025 10:30
@illwieckz
Copy link
Member Author

I renamed the keyword naiveColors, as it applies on colormap (including diffusemap, but only the colormap of it, not the light) and on rgbGen colors. That name looks to be the best one among the one I thought about.

@slipher
Copy link
Member

slipher commented Oct 16, 2025

I mean, after editing a shader in this way, do you want to run it only in the linear pipeline? Or to be able to run it in both linear and naive pipelines?

@slipher
Copy link
Member

slipher commented Oct 16, 2025

Can the rgbgen affecting part be dropped? Then the keyword would have a single responsibility of disabling colorspace conversions. We could give it a name like noImageColorspaceConversion which would directly describe what it does. I don't like the names like naiveBlending or whatever because those are misleading about what it does and when it should be used. It does not in general emulate naive blending; it just makes images somewhat brighter with a gamma correction. This sometimes works for blendfunc add but for others such as blendfunc blend it would make things worse.

If rgbgen changes are dropped, for the small number of cases where an rgbgen change is needed, the ifBlendspace thing can be used. Also in those cases maybe you could just use conditional rgb values instead and not need to change the color space.

@illwieckz
Copy link
Member Author

I mean, after editing a shader in this way, do you want to run it only in the linear pipeline? Or to be able to run it in both linear and naive pipelines?

The same shader stage is meant to run in linear and naive pipeline, that's the point: to avoid duplicating stages if possible.

@illwieckz
Copy link
Member Author

Example of how a shader would use such keyword:

@illwieckz illwieckz force-pushed the illwieckz/naive-blending-compatibility branch from ab0213f to a1c1fab Compare November 9, 2025 23:43
@illwieckz
Copy link
Member Author

This now implements linearColorMap and linearColor keywords, first one doesn't set the sRGB flag on the image, second one doesn't use the sRGB color converter on the rgbgen result.

@illwieckz
Copy link
Member Author

What's useful with the linearColor keyword is that you can actually set a color to be actual medium grey by setting 0.5 0.5 0.5, and other numbers that would actually be what you have in mind: 0.25 for being actually 1/4 of the brightness, etc.

When copy-pasting colors from an image editor color picker, the keyword should not be used since the value would be in sRGB.

The linearColorMap feature is purposed as a workaround for quickly porting some legacy blended shaders, which is the original motivation for this PR.

| GLS_BLUEMASK_FALSE
| GLS_ALPHAMASK_FALSE,

GLS_LINEAR_COLORMAP = ( 1 << 30 ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't belong here since it doesn't affect the OpenGL state while rendering.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now moved to a linearBits thing.

depthMaskBits = 0;
}
}
else if ( !Q_stricmp( token, "linearColorMap" ) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike the "linear X" naming. It suggests that the input is in a linear color space and so it should be converted to whatever colorspace is used for further operations. But the point is rather that the resulting color is deliberately made different depending on the blend regime by not converting. So I suggest a name that describes it more as disabling color conversion, like noTextureColorspaceConversion.

Also for the case of the color uniform, maybe we could make it more obvious what the modifier is used for by making it part of the rgbgen syntax. Something like rgbgen noconvert const (0.5 0.5 0.5)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It suggests that the input is in a linear color space

That suggestion is correct. That keyword says the image data should be considered linear.

so it should be converted to whatever colorspace is used for further operations

It's just that we have no reason to convert to another colorspace here, but if we had a reason to, it would do it, yes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's intentional that this keyword states what is the format of the data, but to not state the action to do. That's the engine that decides what to do with that data in such format.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like rgbgen noconvert const (0.5 0.5 0.5)

That idea of syntax isn't bad, but then I would implement it this way:

rgbgen linear const (0.5 0.5 0.5)

And possibly:

colorMap linear textures/something

But anyway the purpose is like the normalFormat X Y Z syntax: to state the format, not to state the action.

We can also do:

rgbGenSpace linear
colorMapSpace linear

To be in the same spirit of normalFormat.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, currently the rgbgen values are always understood as sRGB values. So when rendering in the sRGB-ish naive regime, the values are used as-is; in the linear blending regime, they must be converted to linear space. Conversely, I expect that if some rgbgen values were to be understood as linear, then they would be used as-is in linear mode, but need conversion with naive blending. Instead, the feature proposed here is to treat the values as being whatever colorspace the renderer is currently running with, as a dirty hack to make some colors brighter in linear mode. With the proposed code, colors are NOT treated as linear when using the naive blending regime, so using a keyword named linear is wrong/misleading.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect the keyword to describe the color space the numbers are interpreted as. The current engine I would describe as implementing a 'sRGB rgbgen space' or 'sRGB colormap space' since the input is understood as sRGB and converted to an appropriate colorspace for the blending regime being used. For a "rgbGenSpace linear" I would expect a similar behavior: the color is used as-is with the linear blend regime and is converted to sRGB for naive blending. That's why I suggest something like "noconvert" for the naming of the proposed mode that interprets the colors as linear when in linear blending mode but interprets them as sRGB when in naive mode.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect the keyword to describe the color space the numbers are interpreted as.

That's what it does.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also important to notice that this feature can be used with newly created assets targeting the linear pipeline, when the image input is expected to be linear. For example one may create an image with {0.5, 0.5, 0.5} pixels and expect it to be mid grey.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It suggests that the input is in a linear color space and so it should be converted to whatever colorspace is used for further operations.

It suggests correctly that the input is in linear color space.

Further operations should always be done in linear space, so there is no conversion to any colorspace to be done for it being used for further operations.

Copy link
Member Author

@illwieckz illwieckz Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only processing being done in sRGB space is the game UI. It's not an engine thing. We can simply declare that those new keywords are not meant to be used in UI assets. They're not meant for it. Also there is no UI asset to port.

@sweet235
Copy link
Contributor

The screenshots described as "Naive blend shaders, naive build, naive renderer" look best. Please use these settings!

@illwieckz
Copy link
Member Author

It's up to the mapper. We're adding new features, we don't remove the existing ones.

@illwieckz
Copy link
Member Author

illwieckz commented Nov 26, 2025

It should also be mentioned that the said screenshots are the one specially selected to show incompatibilities, so yes, they were purposely selected for exposing visual regressions when enabling a new feature on a file specially crafted to workaround the bugs of the missing feature itself.

@sweet235
Copy link
Contributor

It's up to the mapper. We're adding new features, we don't remove the existing ones.

You have been changing the default settings, and now hundreds of maps look weird. Please be more careful!

@illwieckz
Copy link
Member Author

You have been changing the default settings,

Create an issue for that. You're talking about something else than what is discussed in that thread.

@sweet235
Copy link
Contributor

Create an issue for that.

Do it yourself!

@illwieckz
Copy link
Member Author

You haven't named the regression.

@sweet235
Copy link
Contributor

You haven't named the regression.

Maps look bad.

@illwieckz
Copy link
Member Author

and now hundreds of maps look weird. Please be more careful!

We are rendering legacy maps as much as they were rendering on Tremulous.

We do know some rare missing features: https://wiki.unvanquished.net/wiki/Formats/Known_regressions_since_Tremulous
The only known visual regression is the missing of alphaGen lightingSpecular which was never implemented in our renderer and then, wasn't modified in a way it would break it, it never been there yet.

It can happen that when implementing missing features or fixing bugs we inherited from the engine we forked (XreaL), it may take some time to stabilize the feature. A good example of that was the overbright. Our overbright inherited by XreaL was broken. We fixed it but it took some time to stabilize, our first fix wasn't complete so it fixed like 90% of the issues but there was still 10% of regressions. We fixed the 10% of regressions later. Now we should be on par with the original Quake 3 engine.

If “hundreds of maps look weird”, then you talk about hundreds of edited maps (even if that edition is just a rebuild). This repository has nothing to do with map edition or map build.

@illwieckz illwieckz force-pushed the illwieckz/naive-blending-compatibility branch from a1c1fab to 90e28dd Compare January 24, 2026 14:40
@illwieckz
Copy link
Member Author

I fixed the last technical issue.

The proposed naming fits the need and fits all our hypothetical usages.

If there is no comment anymore on the implementation itself I will merge it.

@slipher
Copy link
Member

slipher commented Jan 25, 2026

Let's look at the journey of a single pixel color channel with the value of 128 in the input image, in the simplest case that we render a (3D) color map with no blending of any kind. Input Colorspace: srgb describes what our current implementation does: always treat game textures as sRGB. Input Colorspace: linear describes a not-yet-existing implementation for handling linear-space textures, which would treat the texture as linear regardless of blend regime. Input Colorspace: noconvert describes the behavior introduced in this PR, where the image is copied as-is to the 3D rendering buffer heedless of blend regime. The 3D buffer value is the value rendered to the "main FBO", i.e. the buffer used for accumulating the results of 3D rendering. This value should be understood as sRGB in the naive blend regime, and should be understood as a linear color space in the linear blend regime. Output is the final, sRGB pixel value that would be sent to the display. We assume tone mapping, etc. is off. I used the simplified gamma 2.4 approximation for ease of calculation.

Input Colorspace Blend Regime Input Pixel 3D Buffer Value Output
srgb naive 128 0.50 128
srgb linear 128 0.19 128
linear naive 128 0.75 191
linear linear 128 0.50 191
noconvert naive 128 0.50 128
noconvert linear 128 0.50 191

Observe that a noconvert input texture results in different colors being drawn on the screen, depending on which blend regime is used. From previous comments I have understood that this an intended behavior, which can be used as a hack to make dual-blend regime textures where the different image color happens to cancel out other naive vs. linear blending differences in certain blended shaders. But this behavior does not deserve the name "linear". It is a special-purpose hack with the unexpected behavior of making the texture appear a totally different color depending on which blend regime is used. It is a trap for users to use a normal and straightforward-sounding name for a feature with unexpected effects that they probably should not be using.

I expect the keyword to describe the color space the numbers are interpreted as.

That's what it does.

Wrong. Using the new feature, the input is treated like sRGB in the naive blend regime, but linear in the linear blend regime.

It's also important to notice that this feature can be used with newly created assets targeting the linear pipeline, when the image input is expected to be linear.

That would be fine for now for linear-only assets, since a true linear input colorspace keyword, like Input Colorspace: linear, is not implemented yet. But we should reserve linear<Blah> keywords for something that implements that.

@illwieckz
Copy link
Member Author

illwieckz commented Jan 25, 2026

About that line:

Input Colorspace Blend Regime Input Pixel 3D Buffer Value Output
linear naive 128 0.75 191

This will never be implemented. You're wasting my time over something that doesn't exist, will never exist, and that should not exist.

The only thing to care about is that:

Input Colorspace Blend Regime Input Pixel 3D Buffer Value Output
srgb naive 128 0.50 128
srgb linear 128 0.19 128
linear linear 128 0.50 191

First line is the legacy naive pipeline using incorrect equations. We only care about it for compatibility with old assets.

Second and third lines are about the de facto industrial standard of linear computation pipeline with pixmap storage and presentation in sRGB space.

Nothing else exist and we will not care about hypothetical compatibility with hypothetical bugs added to bugs we can add to bugs in hypothetical buggy assets no one ever made.

@illwieckz
Copy link
Member Author

illwieckz commented Jan 25, 2026

Actually your table is wrong.

You confuse the intended colorspace of the image (intended outside the game, for example in GIMP) and the actual colorspace used to load the image by the naive pipeline.

Let's redo the table:

Image Colorspace
(in editor)
Blend Regime Input Colorspace
(in engine)
Input Pixel 3D Buffer Value Output
srgb naive linear 128 0.50 128
linear naive linear 128 0.50 128
srgb linear srgb 128 0.19 128
linear linear linear 128 0.50 191

I do remind that with the naive pipeline, all images are loaded the linear way, because that pipeline doesn't know what is a colorspace. There is no sRGB input colorspace in naive pipeline. Therefore using the linearColorMap keyword in such case is superfluous and never wrong.

The naive pipeline has no knowledge of colorspace and there is no “Input colorspace” meaning, it just happens that the way it loads image is the same as an image with linear colorspace in a colorspace-aware pipeline, but there is no colorspace meaning in the naive pipeline at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants