SharpLife - Dot Net Core based modding p Created 5 years ago2018-05-06 19:15:02 UTC by Solokiller Solokiller

Created 5 years ago2018-05-06 19:15:02 UTC by Solokiller Solokiller

Posted 5 years ago2018-07-20 20:17:04 UTC Post #340217
Code conversion from one language into another happens very often.
Besides, the ASM is only the sped-up 256 color software-renderering code so that's really irrelevant.
My point is that you'd have to heavily modify the code just to make it work and not be the ugly mess that Quake code is. I won't be doing anything like that so it doesn't matter.

So far i've just been making it all from scratch since the original version is such as mess. Thanks for bringing this up before we got any Quake code in there.

I forgot one item on the list of systems: User Interface. That'll probably be a custom design, unless i can find a library for that.
Posted 5 years ago2018-07-22 10:44:35 UTC Post #340224
I'm seriously considering swapping to Veldrid for Sledge. The API is way better than the half-assed OpenTK wrapper I've rigged up, and you get Vulkan/DirectX11 support for free. I've been doing a few tests and it embeds into Winforms with multiple viewports very easily. It's a bit inconvenient to require both .NET Framework and a bunch of .NET Core DLLs at the same time, but I can wait until .NET Core 3 is out which will ship with Winforms support out of the box.

Your last screenshot inspired me to try and write my own BSP renderer, I'm running into a lot of walls at the moment with Sledge and I need something different to play with for a while.
Penguinboy PenguinboyHaha, I died again!
Posted 5 years ago2018-07-22 12:14:42 UTC Post #340225
Veldrid is .NET Standard, not .NET Core so it should work fine with .NET Framework based projects: https://docs.microsoft.com/en-us/dotnet/standard/net-standard

Veldrid does make it easy to swap backends, but you will need to account for backend specifics quirks, like for example using samplers in OpenGL requires you to add the sampler to resource sets in a specific order to make it affect rendering, and shader outputs may be flipped vertically, requiring you to change the shader to output something different. You will generally also need shaders specific for a given backend, but SPIR-V intermediate compilation may make that relatively simple.

BSP rendering isn't terribly difficult once you get the data part going, but if you're making an editor you'll need to make sure to mark buffers as dynamic. I don't know what the best approach is for that kind of stuff, it depends on the cost difference between static and dynamic buffers.

I'm trying to modularize my code as much as possible, so BSP rendering should be standalone for the most part, but it's probably still going to have some dependencies on the file format. Still, the shaders can be reused for drawing in-editor. The current LightMappedGeneric shader is pretty much what you'd use in an editor, aside from maybe simple light shading like Source does to show the difference between face normals.

I have thought about maybe having some kind of support added to Sledge to add some new stuff eventually, but that'll require a new map format. I haven't looked at its source code yet so i don't know how easy that would be, but given that it's open source and written in C# it's the best way to open up new possibilities for content creation.
Posted 5 years ago2018-07-22 14:23:07 UTC Post #340227
Yeah it works in Winforms, but pulling .NET Standard into a .NET Framework project adds about 100 extra DLLs, at that point you might as well just use .NET Core to begin with. (Though I suspect the extra DLLs may just be shims to the framework, since they're all pretty small)

Sledge will support extra formats with the plugins in the latest branch, but at the moment it's... not too great, stability wise. Which is why I'm looking into doing some side projects to polish up my 3D rendering skills.
Penguinboy PenguinboyHaha, I died again!
Posted 5 years ago2018-07-22 21:37:19 UTC Post #340229
Posted 5 years ago2018-07-23 06:28:17 UTC Post #340232
PenguinBoy said:https:/blogs.msdn.microsoft.com/dotnet/2018/05/07/net-core-3-and-support-for-windows-desktop-applications
Oh boi, I can't wait to see this in action in Sledge.
Posted 5 years ago2018-07-23 07:59:50 UTC Post #340234
I think we've established by now that nobody's using Mono or Gtk.
@Solokiller, how is your work in process? Rendering nsp and wad and can we have sources :P
I'm working on implementing the client-server architecture so there's nothing new to show.

The source code and game files can be found in these repositories:
https://github.com/SamVanheer/SharpLife-Engine
https://github.com/SamVanheer/SharpLife-Install

Put SharpLife-Install in Half-Life/sharplife_full and define the STEAMCOMMON environment variable to point to the steam/steamapps/common directory.
Posted 5 years ago2018-07-23 19:47:52 UTC Post #340240
I'll get the package stuff sorted out, right now there's a couple beta versions in use and the SDL2 library is a custom build to work around a problem in the older version used by the game.
Posted 5 years ago2018-07-24 14:40:43 UTC Post #340246
What are you trying to use SDL2.dll for? You shouldn't need to compile the native wrapper yourself, if you do you should just use the one in Half-Life. There shouldn't be any need to reference the file explicitly though.

The wrapper does only one thing and that's bootstrapping the Net Core host. You shouldn't need to touch it unless there's a problem with it or if you need some interop with the engine somehow (you probably won't).

I've removed the need to use a local NuGet package source, all assemblies are now either from the standard NuGet package source or a local assembly reference in sharplife_full/assemblies.

I've also implemented the basics for client-server stuff, up to loading game assemblies. Before i can continue i need to get the networking system done so that all works, since map loading relies on it to load the data on the client (clients always use data from the server to load content, even if it's a listen server).

This'll probably take a while so don't expect it to work from the start.

As far as using local assemblies goes, when ImageSharp is updated i'll use the NuGet version, same for Veldrid. The assemblies will still be committed to allow anybody to run it though, so it only really changes how the dependencies are managed.

I've also upgraded all projects to use Net Core 2.1.
Posted 5 years ago2018-07-24 16:11:02 UTC Post #340248
You need to install the 32 bit Net Core runtime and SDK.

If you're having problems even getting it to launch then go to sharplife_full/cfg/SharpLife-Wrapper-native.ini and set DebugLoggingEnabled to true, then look in SharpLifeWrapper-Native.log in the Half-Life directory, it might tell you some useful things.

Your problem is probably that it's trying to run as 64 bit so it fails to load 32 bit binaries.
Posted 5 years ago2018-07-24 18:05:22 UTC Post #340250
Half-Life can't be converted to 64 bit because parts of it depend on it being a 32 bit application, like for instance function lookup where pointers are cast to a 32 bit int.

SharpLife can't be ran as 64 bit since it has to be in the same process space, which forces the use of 32 bit.
Posted 5 years ago2018-07-24 21:57:55 UTC Post #340252
At the moment it's hardcoded yes, this is still early in development and i haven't gotten to the point where map loading is done like the original. Please remember that it will take months if not years for this to get to a playable state.
Posted 5 years ago2018-07-25 15:55:46 UTC Post #340257
I built an event system so UI, client and server code isn't all over the place. The engine updates the UI state directly and some server code will directly call client code when running as a listen server. To avoid this all of the code will dispatch events that can be listened to.

Events are registered before use and can have a data type registered along with them that carries that event's specific data, for instance when a map starts loading an event is dispatched containing the map name, previous map name, whether it's a changelevel or a new map, and whether it's being loaded from a saved game.

This makes the event system type safe, allowing data to be easily passed around.

It's pretty easy to register listeners for specific events, if the event has data you can register a listener that takes that specific data type and just register it, it'll figure out the event name from that.

I'm designing the engine with the possibility that systems will be separate between client and server, so for instance there can be separate command systems so the server can't just execute client commands directly when running a listen server.

Still working on networking, i've been trying to figure out how exactly the engine does things, it's slowly getting there.
Posted 5 years ago2018-07-28 11:20:29 UTC Post #340303
I've implemented a basic networking system now. Servers will set up networking, clients can connect to servers.

Message sending works up until the client receiving the message containing server info, including the map name, which it uses to load the BSP now.

In theory you could just launch with a different +map command to load any map, but i haven't tried that yet.

For those interested in seeing how much it takes to add a new message, these are the steps you have to take:
  • Make sure your dev environment is set up first by adding the path to protoc (Protobuf compiler) to your PATH environment variable. The tool is included in a NuGet package that's a dependency of the SharpLife.Networking.Shared library, so it will be located in your .nuget/packages directory. You can also download the tool from Google's Protobuf website
  • Create a .proto file in SharpLife.Networking.Shared.Messages (can be in a child namespace) containing your message definition, make sure to specify the package name
For example, here's the message that the server sends to the client to give it basic server info: https://github.com/SamVanheer/SharpLife-Engine/blob/23f235a39ca16423d35ab6007731d9df2a5c3f7f/src/SharpLife.Networking.Shared/Messages/Server/ServerInfo.proto
  • Rebuild SharpLife.Networking.Shared to automatically generate the message class. This is done using a pre-build step that executes a PowerShell script
You need to add the descriptor to the list, and adding new messages post-release should also come with a change to the protocol version constant: https://github.com/SamVanheer/SharpLife-Engine/blob/23f235a39ca16423d35ab6007731d9df2a5c3f7f/src/SharpLife.Networking.Shared/NetConstants.cs#L46

Depending on whether it's a server-to-client or client-to-server message you need to add it to either list.

The order of the messages is important, since it's used to generate the IDs used to identify incoming messages.
  • On the receiving side you'll need to register a handler:
https://github.com/SamVanheer/SharpLife-Engine/blob/23f235a39ca16423d35ab6007731d9df2a5c3f7f/src/SharpLife.Engine.Client/Host/EngineClientHost.Messages.cs#L40
https://github.com/SamVanheer/SharpLife-Engine/blob/23f235a39ca16423d35ab6007731d9df2a5c3f7f/src/SharpLife.Engine.Server/Host/EngineServerHost.Messages.cs#L38

Message handlers implement the IMessageReceiveHandler<TMessage> interface. TMessage is the class type of your message. The same class can implement multiple handlers. If a handler is not implemented, an exception will be thrown when a message for it is received.
  • That's about it. You can send messages pretty easily:
https://github.com/SamVanheer/SharpLife-Engine/blob/23f235a39ca16423d35ab6007731d9df2a5c3f7f/src/SharpLife.Engine.Client/Host/EngineClientHost.Messages.cs#L76
https://github.com/SamVanheer/SharpLife-Engine/blob/23f235a39ca16423d35ab6007731d9df2a5c3f7f/src/SharpLife.Engine.Server/Host/EngineServerHost.Messages.cs#L66

Adding a message will immediately add it to a list of outgoing messages and serializes it to a buffer. The message object is not kept after that.

Servers can send both reliable and unreliable data, to allow you to send non-critical data that can be lost or ignored without consequence (e.g. visual effect creation).

The engine will send messages automatically, but you can force the immediate sending of all pending data if you want. That's generally reserved for engine code that needs that done, and will not be exposed to game code.

Game code will also be able to register messages the same way, the only difference will be that message handlers will get an object that represents a client rather than a connection.

Note that the message IDs are internal and should never be used for any persistent uses. They can change easily and are normally handled entirely by the message transmission and dispatch systems. Unlike GoldSource, game messages won't require you to store off IDs, so you only have to make sure that your code uses the same list of message descriptors on the client and server.

There is currently no hard limit to the size of messages, but that will be needed to avoid issues later on.
The only hard limit is the number of clients, and that can easily be made a configuration variable.

SVC_BAD should not be possible with this system since message sizes are sent along, and messages are always parsed fully. Only malformed packets could cause it to happen.

Now the next things to do are:
  1. Build a simple UI to display the console and accept commands
  2. Implement a system to network arbitrary add-only string lists with binary data
The first is straightforward, i'll be implementing it in the game codebase so all UI code is there.

The second will take some doing. I'll have to look into a good way to network strings because sending them uncompressed could be a problem.
I don't want to just send whole lists at once, that could easily use a ton of memory and take a long time when there's packet drops happening.
Sending lists so each packet contains a self-contained section to process would be the best way to handle this. Whatever system i come up with should also be usable for game data networking to ensure that the client gets what it needs without having to wait to reassemble the packets containing the data.

The binary data will simply be Protobuf messages, it's the most efficient way to handle that.

Once that's done i can create a couple lists to store model, sound and generic precache data in them. Then i can implement proper resource precaching and let the client load everything up.

That's a fair amount of work, so i'll keep it at that for now.
Posted 5 years ago2018-07-29 10:52:44 UTC Post #340316
I've reworked the command system to make things a little safer and easier to use.

The old system worked mostly like the original: one command system that contains both server and client commands, where you could execute client commands when running a listen server, and where commands would be forwarded to the server as needed.

The new system separates the system into 3 pieces: the queue, contexts, and the overarching system.

The queue contains all commands to be executed. This works the same as it did before, only now you can only execute commands from the same context that your own command is being executed from.

A context contains a set of commands and aliases (defined with the alias command). There is a shared context whose commands are accessible from all contexts, but these commands can only execute commands that are in the context that this shared command is being executed from, so if you execute such a command from the client, it cannot execute server commands, but you can also execute the command from the server.

This helps protect against abuse by not allowing direct execution of arbitrary commands, but will not protect against things like slowhacking, which relies on servers being able to make the client insert commands into their local queue.

Command registration, queuing and execution remains the same, you won't notice any real difference in code. The big difference is that you no longer have to account for where a command comes from in the command handler, you are guaranteed that the command was either executed locally or was explicitly sent to from client to server or server to client.

This allowed me to remove the CommandSource enum, which was part of the original command system. It was a really inflexible way to detect if a command was forward to the server (needed so you can execute server commands from the client).
The new way to handle this, which isn't implemented yet is to use a helper to automatically forward these commands. You'd register a server command or variable, and register a client side forwarding command so the server can receive it.

The case where you're hosting a listen server and need to directly access server commands will probably be solved by adding an option to the console to switch to the server command context so you can directly perform server administration. Otherwise, you will need to use rcon to execute those commands.

An alternate option is to add a second input area for server specific commands.

Most cvar flags have been made obsolete with this change, of the 10 flags only half are still required at this time.
Further changes may make more of them obsolete in time.
Edit: Make that 3 required flags. The other 2 are now filters.

Edit 2: only 2 flags are part of the command system now, the third flag has been changed into a user defined flag since it's only used to save config cvars.
Commands can now have user defined flags added to them, so it can be flagged with specific boolean values.
In addition, commands can have tag objects associated with them to add arbitrary data to them. For instance, the map cycle cvar could store the map cycle manager object as a tag.
Posted 5 years ago2018-07-29 20:54:38 UTC Post #340325
User posted image
Added a console.
Posted 5 years ago2018-07-31 19:37:11 UTC Post #340349
User posted image
I improved the console a bit so text can be selected. Auto scrolling doesn't work quite the way i want it; when text is added it will scroll the frame after it's been added because ImGui doesn't know the new content size yet, so it has to be delayed. This also means that a constant flow of text will delay scrolling by quite some time.

I've also reworked networking to make sure clients are fully disconnected before allowing them to connect again, and server shutdown and restarting with the map command works properly now.

I added in network message trace logging to help debug things, as well as logging state changes.

With that all done, i can now begin to work on networking the string lists. Once that's operational, i can add in the code to precache resources on both the client and server side (ideally using the same code).
Posted 5 years ago2018-08-01 15:12:19 UTC Post #340361
I'm not going to worry about anything UI related for a while.

I've implemented network string lists. These are lists of strings that can be networked to clients. Any number of these can be created with any number of strings in them.
Once a string has been added, it cannot be modified or removed.

The purpose of these are to send large amounts of indexed strings to clients. For example, the model precache list is currently being sent this way.

New strings can be added at any time, and they will be sent to clients as well. This allows late precaching to be implemented.

One important feature is that every string can have binary data attached to it. This data comes in the form of Protobuf messages.
Every message type that you want to add to lists must first be registered so that it can be sent efficiently.

Different strings in the same list need not use the same binary data type, or any binary data at all.

Binary data can be changed at any time, which is useful when updating data. For example, you could send the map cycle and include basic information about it, such as how many players are required for the server to switch over to it. Another example is a map vote, and the current number of votes for each map.

You can add event handlers to respond to the adding of new strings on both the server and client, which can be useful to load models automatically when they're needed.

With this functionality being available, i can now implement proper resource precaching to enable the loading of the map as it should be, as well as loading other models and sprites. Adding support for sound precaching should also be simple.

On the subject of sound, i contacted Firelight Technologies about using FMOD, since their non-commercial license had some confusing wording. They gave me the green light to use FMOD in this project since it's non-commercial, which means there are no legal issues regarding the use of FMOD. I may implement a basic sound system soon to have that operational as well.

As far as networking goes, the only remaining major feature that requires implementing is networking objects. This requires a bit more work since this relies on delta encoding (sending only changes to the client), and i want to avoid hardcoding the engine's networking to an entity based system, so i'll probably come up with a generic system that can also be used to send any other list of objects to clients, whether using delta encoding or just sending the entire object.

That shouldn't be too difficult, and combined with C#'s reflection facilities it should be relatively straightforward to make classes serializable this way.

I'll begin working on object networking after i have a rudimentary entity system up and running in game code.
Posted 5 years ago2018-08-02 13:21:04 UTC Post #340381
There are source files in the FMOD studio API installation that can be used to dllimport FMOD functions. I'm sure those are better maintained than any third party wrapper, but it seems that that wrapper is a more complete wrapper. I'll see which one's best for what's needed here.

I am going to change how SharpLife loads the game libraries so that it's hardcoded to the ones used now. This change is for the following reasons:
  1. The game libraries will be built as needed, without needing a manual build before running
  2. Modders should use distribute SharpLife as a whole mod, not as a mod for SharpLife
  3. The most important reason: the FMOD non-commercial license does not allowing distribution of the library in a game engine
To keep SharpLife as a mod and not an engine, the engine and game parts must remain tied together so that it counts as one entity.

This won't really affect modders since they'll want custom engine code anyway, and it'll simplify the code that accesses game code.

Edit: i discussed this with another programmer who has some experience with both FMOD and OpenAL and it seems that using OpenAL may be the better way to go here. FMOD's definition of a game engine fits SharpLife, even if it's running as a mod. OpenAL on the other hand has a modern and free implementation that has no licensing restrictions.

I found 2 C# wrappers that could be used:
https://github.com/flibitijibibo/OpenAL-CS
https://github.com/opentk/opentk

OpenTK is the more well known and used one, and both have .NET Core support.

At this point no code has been written yet, so the biggest issue is making sure it's all legal.

If anybody has any thoughts on the matter, i'd love to hear them. Audio support won't be required for some time, so we can figure this all out.

As far as reworking access to game code is concerned, i'll change that regardless to make things easier for everybody.
Posted 5 years ago2018-08-04 11:32:23 UTC Post #340411
I recently used Alure to manage OpenAL and I found out it is a lot easier to be used than using OpenAL directly. It uses the ZLib license so it is permissive.

It is coded in C++ and currently there is no wrapper to .NET Core, but it may be worth a look. Here: https://github.com/kcat/alure
Posted 5 years ago2018-08-09 06:36:37 UTC Post #340457
I looked into Alure, i don't think it's of much use in managed code since OpenTK already wraps the native API. I've built a basic program for playing sounds, it looks like it should be straightforward to use.

The only problems are that there is no file loading provided in the library itself and that there's stuttering on shutdown if the OpenTK AudioContext is disposed immediately after a sound has stopped playing. It only seems to happen with the wav test file i'm using (scientist/c1a0_sci_crit2a.wav from Half-Life), the mp3 file i tried (media/Half-Life11.mp3) didn't have that problem.

I can leverage other libraries to load various file types, so it's not that hard to implement.

I've also got a basic object list networking system up and running. I can serialize properties from an object to a stream and de-serialize them into another copy. It supports delta encoding and networking arbitrary length lists, as well as generics. It needs more work to be fully functional, but the important parts are working now.

Once the object list system is finished, i can implement a basic entity list and implement sky name settings. That should cover loading the BSP file itself, and once i've got the entity class hierarchy going i can put brush entities where they should be.

I've also committed the change to directly reference game assemblies in engine code, and i've merged what's left of the game config file (game name for display purposes) into the engine configuration.

I'm a bit busy with college work at the moment, so it'll be a few weeks before i can wrap these things up. Things are moving though, so it should work out.
Posted 5 years ago2018-08-10 12:59:32 UTC Post #340469
I've added a table of engine components that are required and their status, it should make things a little easier to keep track of: https://github.com/SamVanheer/SharpLife-Engine/wiki/SharpLife-engine-components

I've been doing some experiments with making a good entity list design, since the one used in the SharpLife interop version wraps the original version.
I've come up with one that's pretty simple and efficient and that avoids global variables, so that part should be good to go once the networking system is done.
Posted 5 years ago2018-08-12 18:39:30 UTC Post #340502
So Solo,

tell us none coders more about this new thing of yours. The way I understood it you switched codes but it's essentially still the same Hl: Enhanced is that correct?
I understand that the new code basis provides even more possibilities later on, or at least for coders like yourself. But tell us how will us the simple mappers benefit of these changes and how did it affect your total schedule regarding a full alpha or beta release?

Will this switch won't set you back yet another few years? Or is it actually the opposite. So that you're actually managing to get things done and implemented a lot faster than with c++ before?
In either case I do hope you will get a build out that SP mappers can actually use with hl1,op4,custom npc content usage. For me it seems you're stuck and limiting yourself with MP features at the moment. A thing that's totally fine of course . Yet I still do I wonder about your ultimate goal here.

Sven Coop is an established IP by now. It is doubtful that anything can push it away these days. So naturally I fail to see the focus on MP based content not that I complain. Competition is always a good thing for business. How ever only if it makes sense.

Also keep in mind that TWHL. is mostly focused on SP scenarios and so are the remaining HL1 fans. People that do love Half-Life and it's MP usually only play counter strike global offensive nowadays and that isn't even Gold Source anymore. Instead it is Source 2 or Source 2.5 as some people call it.

Tell us about your current plans and goals... Anyway I do appreciate you and all your efforts.. So don't get this the wrong way in any aspect. :)
Posted 5 years ago2018-08-12 21:26:01 UTC Post #340506
The idea behind the project is the same, it's a different approach though.

C# is a much easier language to work in, i don't have to worry about some issues that make API design hard in C++. I've made much more progress working on this than i did working on HLE, and that has to do with there being much better third party libraries to provide functionality. The .NET framework itself also covers a lot of features that are missing in the C and C++ standard libraries, which eliminates the need to provide those myself.

The language also provides much better debugging features, if you crash you'll be able to see what happened in the log, unless the crash was so serious it never makes it to logging the error.

At the rate i'm progressing i should have the essentials for entities up and running in a few weeks, which means i'll have model loading done which should let me add the minimum required entities to make a map.

I'm not limiting myself to MP features, these things are necessary even in SP because that's how the engine works. Using the same way to send data to the client makes things a lot simpler.

Sven Co-op has nothing to do with this project. It's just another mod that lets people do stuff, i'm working on this for the same reason that i joined that team in the past, which is to make something nice for people to play around with.

Getting a working build is not just a matter of implementing the minimum required engine features, without the entities it's not going to let you do much.
Once i've got the object networking code done the networking part of things is largely finished. It will require fine-tuning and optimization, but if it works it's enough to start building on top of it.

I've actually covered a large part of the engine already, the biggest missing feature is a proper GUI, which is a fairly low priority since the console provides enough control to get things done for now.

Feature wise this engine eliminates most of the engine limits. The design i've got planned for networking allows for up to 65535 entities to exist at any one time. This limit is dictated by the size of the data type used to store entity indices, and is easy to change. The rest of the codebase should adapt automatically to increasing numbers of entities. I can probably do the same for map sizes, where i can change the networking configuration for coordinates based on the size of the map to optimize bandwidth usage.

The limits you know about the original engine are things like texture size, map size, entity count, player count, etc. All of those are either gone or configurable so it's not really a limitation anymore.

Because this engine uses shaders graphics performance is much better. That means r_speeds is no longer something to keep in mind, so you can make bigger and more detailed maps. I can add things like Source's env_fog_controller to let you control visibility (and add fog properly). Beyond that i can't say what you'll be able to do since i'm still working to implement the required components.

I'll try to explain the benefits of new features in simpler terms in the future so it's easier to understand.
Posted 5 years ago2018-08-13 12:22:54 UTC Post #340514
Are there going to be additional capabilities like a user level scripting api to allow mappers to control more of the gameplay? Like maybe better storing and altering of game data like max health/armor or max bullets amounts and custom monsters that actually work?
Rimrook RimrookSince 2003
Posted 5 years ago2018-08-13 14:09:29 UTC Post #340518
That should be possible using CSharpScript: https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples

Those examples are pretty basic, but it should be possible to build a scripting API that enables you to create your own entities and access other entities as needed.
Custom monsters should work fine, but networking custom data may not be as flexible as it would be if written in C#.

For plugins, custom assemblies can be used instead. You write C# code and tell the game to load it as a plugin, you can then provide entities, hook into other objects and do what you want to do.

Custom monsters largely need support for schedules. I'm going to use a new design for that instead of using Half-Life's approach, it should be more modular so you could do something like this:
public Barney()
{
   this.AddSchedule(new Follow());
}
Follow would be a class that implements an ISchedule interface, providing a list of tasks to complete in that schedule, as in Half-Life. The big difference is that the schedule and task code would reside in the schedule object, and be flexible enough to avoid knowing the actual NPC type most of the time.

A schedule that has an NPC attack something would instruct the NPC to attack using the preferred weapon for a range (close range: don't use weapons that deal splash damage, long range: use accurate weapons). Barney would always use his pistol, so it would look something like this:
public virtual void AttackTarget(BaseEntity entity, float range)
{
    //DefaultWeapon is a property that returns the default weapon for an NPC. NPCs can override it to provide logic to decide what the default weapon is at a given time, but complex logic belongs in this method
   var weapon = DefaultWeapon;
    //NPCs that don't have weapons don't attack
    //They shouldn't have schedules that would instruct them to attack, but if they do, it can be handled here
   if (weapon != null)
   {
      AttackTarget(entity, weapon);
   }
}

public virtual void AttackTarget(BaseEntity entity, BaseWeapon weapon)
{
    //Figure out where to aim, tell the weapon to fire, etc
}
Implemented in the base NPC class so any NPC will try to attack if they have a weapon.

Most of the hard logic would be handled by base classes, weapon classes will take care of actually firing the weapon (weapon_9mmhandgun fires a 9mm bullet fired by Barney) and handling magazine size and reloading. Other weapons, like the Alien Slave electrical attack would also be a weapon for consistency, meaning it's possible to turn it into a player usable weapon (a la Decay).

Making a basic NPC like Barney would mostly be combining existing schedules shared between NPCs, setting the correct ally settings ("i am Black Mesa personnel") and the weapons the NPC can use. This could be turned into a configuration based setup, so an NPC would be defined by loading a config containing the settings. You'd then override the config by inheriting from that and setting values:
<!-- The Base attribute tells the loader to first load the given config file, then merge the values with values loaded from this config -->
<EntityConfiguration Base="NPCs/barney">
    <!-- ConfigAction instructs the loader how to handle merging, in this case clearing and overwriting the old values. Default is merge, overwriting duplicates -->
    <Weapons ConfigAction="ClearAndOverwrite">
        <!-- This Barney uses a shotgun and it's his default weapon. Note that this requires Barney to have animations to fire shotguns and provide attachment points to put the model onto -->
        <Weapon ClassName="weapon_shotgun" IsDefault="true"/>
    </Weapons>
</EntityConfiguration>
It's called EntityConfiguration because it could be used for entity configurations in general.

I'd like to point out that Half-Life only supports up to 4 attachments. This is a limitation in the client, mostly the renderer, and there is no need to impose that limit here. The studiomdl format can store up to 512 attachments if using the default compiler, custom compilers can extend that limit to theoretically unlimited.

That makes adding attachments for weapons pretty simple, the only issue is that while attachments do have names, the default compiler doesn't let you specify the name. I'll have to modify it allow for that, otherwise attachments will need to use their index to identify them.

Unfortunately there is a backwards compatibility check in the studiomdl compiler that makes adding the name a bit complicated. I could make a separate program that sanitizes existing QC files to remove the obsolete tokens, then i can add the name as an optional last argument for $attachment. I'll have to look into that at some point in the future.

For anyone interested, the backwards compatibility check is to parse and ignore the tokens "X" and "1" or "-1". The Half-Life SDK on Steam has model sources containing these, i don't know what they were for but if i had to guess it was some kind of axis specific scale or something.

Getting back to the config files: at this point it's just an idea, until we get to the point where entities can be fully implemented this is just a dream. But if we get there, i'll definitely make it as easy and powerful as possible. I want you to be able to take SharpLife and modify only config files to produce a different looking mod, so no programming experience should be required.

For mappers, you'd just set the entity config filename to something like "maps/my_map_name/NPCs/MyBarney" to use the script for a specific Barney, it would also be possible to specify a per-entity class default using a config file for maps:
<MapConfiguration Base="maps/DefaultMapConfig">
    <DefaultEntityConfigurations ConfigAction="MergeAndOverwrite">
        <DefaultEntityConfiguration ClassName="monster_barney" ConfigFileName="maps/my_map_name/NPCs/MyBarney"/>
    </DefaultEntityConfigurations>
</MapConfiguration>
It's ambitious, but unlike the HLE version i had planned, this one doesn't require a complicated third party dependency setup, and it's much easier to load XML files due to being able to map them onto C# data structures. Text encoding is handled by C# so it's a lot simpler to work with than Xerces was.

Right now the focus is on the engine. I'm finishing up the networking code now, once i'm sure it's working properly i'll integrate it into SharpLife and see if it works well in the existing networking system (i haven't tested it with actual network packets). Then i can integrate my entity list prototype and see if a basic entity can be networked over. Then i'll make a worldspawn entity along with the logic to parse out entities easily.

Once i'm sure that's all working, i can focus on model loading. I've already got a partial prototype for studiomdl loading so it's making progress. I'll need to load the BSP file (already done), studio models and sprites. The server has to do 2 things: add the filenames to the precache list (easy) and load the models (hard). BSP loading is done and most of the rendering code is there. Sprite loading is pretty straightforward to do, but studiomdl is complicated because the format has unused features.

I need to make sure everything that's needed is loaded and converted properly to make it efficient enough, and avoid loading data that isn't needed. Texture data isn't needed on the server side, but the server and client can share data so making sure it loads correctly is important.

Once model loading is done, and the interface to get models for entities is there, it should be possible to render an entire map, including brush and point entities. Models will be precached by the client and rendering settings will be networked so clients can correctly do things like render { textures or sprites with transparent parts.

Getting studio models to render will be the hardest part, since i don't yet know how to handle the uploading of the vertex positions. I don't think re-uploading them every frame is good, so a way to do the vertex position calculations in a shader would solve that problem. I'd probably have to pass the bone positions as a uniform for that, but i don't know if that's a good way to do it. If anybody has experience doing that, i'd love to hear about how you did it.

I've covered a lot now, so i'll focus on getting this all done rather than talking about it more. Hopefully i can show this all off by the end of the month, but that depends on how much work i've got for college.
Posted 5 years ago2018-08-13 16:40:51 UTC Post #340527
Wow. Thanks for letting us know about this in such great detail @Solo.
That indeed sounds like a better deal for you - for all of us. I'm happy that this new project makes things so much easier for you. It certainly sounds like a dream come true. Sounds even better than source engine does regarding it's freedoms. As always best of luck to you. :)
Posted 5 years ago2018-08-15 09:27:51 UTC Post #340545
SourceSkyBoxer said:But please please add more shadow mapping like mod AM by SysOp.and realistic reflection for water and more.

DON'T

  1. The concerned code uses "legacy" also known as "immediate" OpenGL which is totally different from the "shaders based" OpenGL used by Veldrid (and by extension SharpLife) and modern AAA games (modern Wolfenstein games).
  2. The concerned code has a HARD dependency on PARANOIA's OpenGL renderer which uses the same "legacy/immediate" OpenGL.
  3. Even if you do waste your time porting the code as-is, you'll get a massive performance loss.
  4. It pains me to say this because I'm a developer of Arrangement but please for the love of God, don't use any of it's code from any public source code(s), the quality is so poor that rewriting the entire thing by yourself will be better.
  5. IIRC, Solokiller said that SharpLife's goal is to provide a "better vanilla GoldSrc modding/playing experience". If you want any kind of graphical enhancements, then fork the project and DIY (Do It Yourself).
Posted 5 years ago2018-08-21 23:26:36 UTC Post #340625
What kind of new tools would there have to be to meet the new requirements?
Rimrook RimrookSince 2003
Posted 5 years ago2018-08-22 12:48:09 UTC Post #340630
Right now the new limits can be used with existing tools, but there are hard-coded limits in the tools that came with the SDK. For instance, QLumpy sets the maximum lump size to 327680 bytes, or about 327 KB. Each textures is stored in a lump, so that's a limitation. Removing the limit is pretty straightforward, just replace the tool with another that can store any size texture.

The compile tools and BSP format impose limits, some of which can be raised or removed by modifying the tools, but the BSP format itself presents a hard limit on its own. A new BSP format is needed to deal with that, which also means new compile tools that can produce those BSPs, and a new .map format to handle any changes in how data is passed from the editor to compile tools.

For now the plan is just to let you use existing maps in SharpLife, and where possible the limits are removed or raised.
The maximum number of entities as well as the maximum size in bytes of the data is imposed by the compile tools as well. VHLT34 supports up to 16384 entities, and a maximum data size of 2097152 bytes, or about 2.1 MB.
This limit can be entirely removed by using dynamic allocation and not imposing any maximum limitation, since the hard limit is defined by engine devs (65534 for now), and is solely a networking and memory usage limitation.

I doubt anyone will hit the compiler limits any time soon, so you can make more entities if you want. Textures can be larger than before without hitting lump size limits, but eventually you'll get there. A way to reduce that chance is to not store mipmaps, since the engine can regenerate those, but that requires modifying the wad creation tool of your choice. I don't know if Wally has any limitations so i can't say anything about that, but i can reverse engineer it and take a look if you want to know.

When it comes to upgrading or replacing tools i'd wait until SharpLife is capable of doing everything the current engine can do before looking too much into it. It may be easier to deprecate wad files entirely rather than try to remove its limits, same for other file formats.

I think the biggest limit is going to end up being the fact that SharpLife is 32 bit, required because it's hosted by a 32 bit process. I can't get around that unless Valve helps us out here. Fortunately that doesn't apply to tools, so compile tools can be 64 bit if needed.

Does that answer your question, or did you need to know more?

EDIT: i've committed the entity list code, the world is now instantiated from the map's entity data. Other entities will be instantiated once they've been ported over.
The world is networked to clients, so they'll be able to get any networked members that it has (currently origin, angles, flags).

Entities can be networked as a base type when there is no need for a specific type (e.g. NPCs can probably use a common base type), allowing them to be networked without requiring you to create client versions for every single one.

Now i can implement model precaching and start working to implement model loading and rendering.

There is currently an issue where starting another map after one has been started will cause an exception due to duplicate network string lists, i'll fix that shortly.
Posted 5 years ago2018-08-23 12:35:33 UTC Post #340641
Wow, this all seems so ambitious. I certainly hope you have help, or at least the time to complete it. The end product sounds like the engine we all need.
Rimrook RimrookSince 2003
Posted 5 years ago2018-08-23 12:39:21 UTC Post #340642
Yeah it is quite a bit of work, but it's way easier to do this in C# than it would have been in C++. IMO C# has a more complete standard library and open source community than C++, so it's easier to get things done without having to reinvent the wheel (just take a look at how Unicode and internationalization is handled in C/C++).

I've fixed the issue i was talking about, i've also refactored the networking code to move most of it to NetworkServer and NetworkClient. I've also reworked the server's setup code for clients to centralize the logic for each stage, but i might rework that later on to use objects that can handle each stage, that's more robust and reduces the amount of variables that are only used during setup.

I've reworked networking objects to use the builder pattern to ensure that all networking data structures are immutable after they've been initialized. This should help prevent accidents that could cause unexpected errors.

Now i can implement model precaching, and model loading after that.
Posted 5 years ago2018-08-23 19:00:43 UTC Post #340645
So awesome to hear that it’s going well.

Also, drag and drop mod support? Like turning mods on and off, and playing smaller self contained ones with all sorts of new stuff. Doesn’t svencoop do something like that? It’s kinda like that on L4D2.

Edit: what if we Minecraft this bitch? What if mods could easily stack, like if I download a new weapon, it adds the weapon to the list rather than replace it? Modders and modelers would have a field day with self contained files and scripting. Same with items and such.
Rimrook RimrookSince 2003
Posted 5 years ago2018-08-23 20:07:05 UTC Post #340647
The plan is to have plugin support, that will allow you to have entities added without having to modify the mod itself.
That also includes weapons, but i'm not sure how safe it would be to allow those to be client predicted since that would require downloading the code to the client.

It should be perfectly possible to have plugins that can both replace and add entities, same with map specific scripts. I don't think we should go overboard with that though, otherwise people will spend more time downloading various scripts than actually playing.

Those entities can use the config feature i talked about before.
EDIT: i've completed the essentials to load models, the map is now loaded using a model manager. The old map manager has been removed and its features merged into the model manager, or moved elsewhere.

Right now the following features are available:
  • Models can be loaded at any time by calling IServerModels.LoadModel or IClientModels.LoadModel (these interfaces may be merged since they're currently identical)
  • There is no limit to the number of models you can load, aside from hard memory limits (RAM and GPU)
  • Model data is shared between client and server, so listen servers are guaranteed to use the same data, though graphics data is stored separately
  • If a model fails to load, the fallback model "error.mdl" (renamed can.mdl from Half-Life for now) is loaded. If the fallback fails to load the game will exit with an error, but i may change it to instead create an empty model. That will have to wait until studio model loading is complete. I'd prefer to avoid having hard exits if at all possible, so this would be nice to have
  • The client can load models without having them loaded on the server. Vanilla GoldSource will silently fail to do anything when calling gEngfuncs.CL_LoadModel and returns null in this case. If the server loads the model later on, it will simply have both a positive index (networked) and a negative index (client specific). There is almost no overhead involved with this, it's just an additional entry in a dictionary
  • The map is now loaded into graphics memory after all resources have been received, matching the original engine's behavior
  • Map information can be retrieved from the engine using IServerEngine.MapInfo or IClientEngine.MapInfo. This provides a read-only object containing the name of the current map, the name of the previous map and the map's world model
  • Game code can directly access the model data by calling IServerModels.GetModel or IClientModels.GetModel. This returns an IModel, which can be cast to the specific model type that contains model data. Type identification replaces vanilla GoldSource's modtype_t here
C#'s pattern matching makes dealing with different types of models a bit easier:
switch (model)
{
    case BSPModel bspModel:
        {
            break;
        }

    case StudioModel studioModel:
        {
            break;
        }
}
It's easy to handle specific types of models this way.

This will need to be stress tested to make sure it's all working properly, but that'll have to wait until studio & sprite loading is done. I'm going to start with sprites since those are the easiest to work with.

Once that's done i'll have to implement the uploading of late precached models to the GPU, since the current design is optimized for loading everything in one go after resources are received.
Posted 5 years ago2018-08-26 13:05:10 UTC Post #340680
I'll probably use Core 3.0's UI support or Avalonia, not GTK.

[TC]Čiαиєz asked if the HUD can be scaled. This is possible in vanilla GoldSource if you modify the HUD code, and i'll keep it in mind when designing the HUD code for SharpLife.

https://github.com/MiYanni/xBRZ.NET may be a good library to use for scaling the images while retaining a sharp appearance.
Posted 5 years ago2018-08-26 14:08:46 UTC Post #340682
That has nothing to do with this project.
Posted 5 years ago2018-08-29 13:32:56 UTC Post #340733
I've been reworking the renderer and model loader to prepare for the other formats. I've merged the server and client interfaces for models and it handles path separator characters properly so for example maps/bounce.bsp and maps\bounce.bsp are considered the same.

Graphics resources are managed separately from rendering state and rendering is now handled by the game, which means when the renderer wants to render models the game can handle the entire thing.

In addition entities can now set their model, which is networked to clients. The model is set as an IModel, rather than an index. This means you can just use the model object, and the networking system will handle conversion to and from the index for networking purposes. This simplifies things and eliminates the need to look up the model whenever it's needed.

I've removed the temporary code to render all BSP models as this is now handled by the game, rendering the world and a basic version of func_wall:
User posted image
The grates and the doors are func_wall.

No render modes yet, but it's easy to add. Due to how it's implemented it's possible to let the game issue render calls and then handle it on another thread if needed since all relevant data is passed into the renderer by the game. This should allow the renderer to run on its own thread without needing to lock for access to the game; the game can collect its render calls and then pass that along, minimizing thread sync time to a single operation. That's going to have to wait for later on though.

I fixed a bug in the BSP loader where submodels were off by one due to a misunderstanding in how the engine loads them. Even the Quake codebase says it's confusing, so it really was confusing. It's all good now though.

Pretty much everything's in place to add sprites and studio models into the mix now.
Posted 5 years ago2018-08-31 22:07:54 UTC Post #340754
Looking good
Posted 5 years ago2018-09-06 14:10:38 UTC Post #340799
I've mostly implemented sprite rendering:
User posted image
I've implemented sprite loading, image conversion to RGBA32 using the alpha channel to store transparency without modifying the original color data, rendering sprites under all render modes, including a bug that makes Color not apply a color, and apply the right angles based on sprite type.

I reported this bug: https://github.com/ValveSoftware/halflife/issues/1903

I fixed a bug with the "facing upright" sprite type: in GoldSource this uses the model origin from another entity so it will snap around depending on which entity last set it before the sprite is rendered. I could reproduce the original behavior, but since it depends on render order it would never really be the same.
Given how rarely this type is used, and the fact that the bug was fixed in Source, i figured it wasn't worth replicating the original behavior.

I reported this bug as well: https://github.com/ValveSoftware/halflife/issues/1904

Neither of these bugs will ever be fixed in vanilla because some maps may rely on it, i reported them so people can find them more easily.

All that's left as far as sprites are concerned is depth sorting to make sure transparent sections don't block other transparent sprites from rendering.
You can see this problem in the image: the green lambda icon on the right is blocked by the stop sign sprite.
Depth sorting is also required for brush and studio models that have transparent parts.

Once this is done i'll implement render modes for brushes, fix skybox rendering to make sure transparent objects don't cut holes in it and to make sure sprites always render on top of it, take a shot at lightmaps and then finally handle studio models.

Once that's done i can implement render FX, which should wrap up all things model rendering related.

I also had to implement some entity keyvalues to make this all work enough to test things out, and i found a bug in the networking system's converter for vector types where it didn't decode them correctly.

Hopefully this can be wrapped up soon so i can start implementing physics, that's needed to make things like glow sprites work (it tests if a sprite is blocked by something else).

Thanks to The303 for making tutorials and test maps to display the various sprite types and render modes.
Posted 5 years ago2018-09-06 15:57:28 UTC Post #340801
Quake's SPR and Half-Life's SPR format's spec also support variable frame-rates, which is fun and could be useful in some niche cases.
Tool developers didn't feel like exposing it, so no mod known to me takes advantage of it. I doubt stock Half-Life even cares, as GLQuake/GLQuakeWorld don't and Half-Life's sprite format is essentially the same, but with the palette baked in.
eukos eukosOriginal Cowboy
Posted 5 years ago2018-09-06 17:08:37 UTC Post #340802
The vanilla engine doesn't handle frame groups (a frame that contains a variable number of actual frames), it just complains about missing frames ("R_DrawSpriteModel: couldn't get sprite frame for <model name>"). I haven't implemented it either since there is little point in adding that kind of overhead, and no existing sprites would have it anyway. Given that Quake 2 does away with it, it likely never saw widespread use in Quake either.
Posted 5 years ago2018-09-07 07:25:42 UTC Post #340807
I can't legally use Quake code in any way, shape or form. We went over this a while back in this very thread.
Posted 5 years ago2018-09-09 13:25:20 UTC Post #340831
User posted image
Lightmaps are working, including styles. It's not terribly efficient yet, but i'm working to make it better.

I'm going to implement render modes as well to make sure BSP rendering is mostly done.

After that i'm going to take a look at figuring out what could be causing memory leaks, since the game is crashing after a few maps with a lot of memory usage. Since it's not a C# exception it must be native memory somewhere.

EDIT: looks like it is a C# exception, so something is using up memory somewhere.

EDIT2: found the cause: https://github.com/mellinoe/veldrid/issues/118

A memory leak in Veldrid's OpenGL backend causes the problem.

EDIT3: the leak has been fixed. I've upgraded to the latest Veldrid build and i've also upgraded to the latest ImageSharp beta (since Veldrid needs it). ImageSharp is now pulled from NuGet, Veldrid is still a custom build since there is no full release containing the needed fixes.
Posted 5 years ago2018-09-10 19:36:34 UTC Post #340842
I've optimized BSP rendering as much as i can for the time being. Bounce is hitting close to 1300 FPS now, singleplayer maps like c1a0c (Unforeseen Consequences), c2a5 (Hydroelectric Dam), c3a2c (Lambda Core portal jumping puzzle) run at around 600 FPS without any visibility testing.

I also tried this map: https://twhl.info/vault/view/6174
It runs at about 250 FPS.

I did this by reworking a couple things:
  • Shared BSP resources are allocated once, reducing the number of state changes
  • The light styles are passed as a single uniform buffer (only 64 actual values are needed, reducing memory requirements). Each vertex also stores the styles used for it, eliminating the need to group surfaces by which styles they use
  • A single vertex & index buffer is used for an entire model
  • Lightmaps are pooled together in larger images like vanilla GoldSource does. These pools are larger than vanilla to reduce the number of state changes required to switch lightmaps. Unlike vanilla, there is no limit applied to how many pooled lightmaps there are, and the pools are not shared between models. I can change this to reduce GPU memory requirements, but it shouldn't affect performance unless a lot of nearly full pools are reused, which would cause a lot of state changes to happen when rendering BSP submodels (brush entities)
  • All of the surfaces that use a texture are grouped together, ordered by number of surfaces descending and then added in sequence, allowing surfaces with the same texture to have their lightmaps in the same texture to reduce state changes. Ordering by number of surfaces ensures that maximum efficiency is attained in grouping lightmaps together, reducing the number of state changes required a little more
Light style changes barely affect performance now, i applied random modulation as a test and the FPS drop is manageable. Bounce suffers a drop of around 100 FPS, but c3a2c only drops by about 60 FPS. Considering every single light style (64 styles) is changing at once every frame, that's pretty good. Most of the time only one or two change, and they usually don't change as frequently as this. In practical situations it shouldn't affect FPS that much, certainly not as much as people get with the vanilla engine.

Dynamic lighting will probably lower things a bit, but those are usually temporary effects so the FPS cost shouldn't be that bad.

EDIT: I implemented support for Vulkan, here are the results:
  • Bounce: 1950 FPS
  • c2a5: 1000 FPS
  • c3a2c: 900 FPS
  • dm_crossedwire: 360 FPS
Pretty good, i don't think there'll be any performance issues.
Posted 5 years ago2018-09-11 13:04:23 UTC Post #340846
amazing... that sounds almost unbelievable... Good job mate.
Posted 5 years ago2018-09-11 15:20:13 UTC Post #340850
Just to clarify, these are rendering the maps with no visibility testing, ie. No form of blackface culling, visleaf rendering etc?

If so, that's bloody impressive. Wonder how much of an improvement visibility testing will have if added in
Instant Mix Instant MixTitle commitment issues
Posted 5 years ago2018-09-11 15:32:51 UTC Post #340851
It is doing backface culling, no visleaf checking yet. We talked about maybe not using visleaves because it might actually lower performance. I'll have to try it out and see what the results are.
Posted 5 years ago2018-09-12 10:52:22 UTC Post #340854
User posted image
I've implemented render modes for brushes. From left to right:
  • Normal
  • Color
  • Texture
  • Alpha test (normal on top for comparison)
  • Normal and alpha test side by side
  • Additive
The 4 other brushes are a test to see if rotation works properly.

Here's alpha test up close:
User posted image
To make alpha test work i had to implement texture re-sampling and rescaling during upload. The engine actually does a fair amount of work here, first changing all palette colors by running it through a gamma correction table, then changing the color of invisible pixels so sampling doesn't try to blend black. Otherwise the texture ends up having black borders around the transparent parts.

Finally, the image is rescaled to a power of 2. It isn't necessarily upscaled, the cvar gl_round_down affects this and is used to make texture biased towards scaling down instead of up. For instance, the grate texture seen above is 48x48, but is rescaled down to 32x32.

This affects the look of the texture since the number of pixels for each bar in the grate is reduced. Without scaling, each bar is 3 pixels wide. Scaled up, it's 4, and scaled down it's 2. Combined with the transparency color changes this can drastically change the look of a texture.

Here's the original texture, without any of these changes:
User posted image
The scaling of the image is done for all textures to ensure power of 2 sizes, the alpha color balancing only applies to alpha tested and index alpha textures.

Textures used by models and sprites seem to use yet another scaling algorithm that uses a different code path. Setting fs_perf_warnings to 1 and developer to 1 will print out warnings only for those textures. This path is apparently used for textures that don't use mipmapping. I'll have to replicate that later to make textures for those load properly as well.
I've implemented gamma correction and brightness override to (almost) match the original. Since this is done in the shader the results are slightly different from the original, but you won't notice it unless you're comparing the results.

The settings use cvars, and the values are uploaded to the GPU. This means it's now possible to adjust the values in real-time:
User posted image
I've also added an FPS slider, although ImGui is bugged and doesn't display the value correctly.

As you can see the settings are all dialed up to the maximum, showing how it changes gamma.

Like the original render mode Color brushes are unaffected by gamma correction, and i fixed it using texture colors and lightmap data.
Posted 5 years ago2018-09-13 11:35:52 UTC Post #340867
User posted image
I've implemented gamma correction in sprites now as well.

I've also reworked shader compilation to use glslc instead of glslangvalidator.
This tool is built on top of the other tool, but adds support for #include. I've used this to share gamma correction functionality.
Posted 5 years ago2018-09-14 02:30:52 UTC Post #340873
Incredible work!
You must be logged in to post a response.