read.cash Log in

@Metalhead33

Joined 4 April 2021 · 66 posts

Hungarian software developer and weeaboo.

120 KT

0 KT · $8.25 received · 0 KT · $24.23 given

Posts

@Metalhead33

11 years of Ways of Darkness One lesser-known fact about me, is that I am a worldbuilder.... of sorts. I am also a man with many plans, and sadly without the time - or willpower - to execute said plans, so those plans typically remain fantasies within my head, until I eventually forget about them. Most worldbuilders typically craft multiple worlds. I may have crafted several, but I forgot about all of them, except one. That one, is my passion-project, and it's 12 years old, as of 2021. I have never profited in any way, shape or form from its existence, and I would have to re-shape a lot of things if I were to do that. https://ways-of-darkness.sonck.nl/Ways_of_Darkness However, if a miracle happens - if I cease being a wageslave - then there is a chance that my passion project might actually net me some profits in the future *(in the form of a novel or a video game)*. But how did it even begin? Join me, as I explore the origins and history of my passion projecet, and lay some aspects of it to rest. Little Metalhead33's Adventures I have had an interest in both *(medieval)* fantasy and sci-fi for a very long time. Probably since I was a little child. I loved movies like Braveheart, Lord of the Rings and Star Wars. I also liked anime, such as Slayers, Inuyasha and Yu Yu Hakusho. Additionally, I played video games, like Doom, Quake, Wolfenstein 3D, Duke Nukem 3D, Heretic, Might and Magic, Heroes of Might and Magic, Stronghold, Stronghold Crusader, etc. Roughly the same time my family got an Internet connection for the first time, I also palyed The Elder Scrolls IV: Oblivion. Roughly, around the same time, I discovered a website named G-Portal. It allowed - and still allows - people to create their own.... ahem.... portal of sorts, for a small price. Most such portals featured a guestbook, a gallery, a forum, and some static HTML pages. Most G-Portal sites were fansites, revolving around bands, movie series or video games. Some were personal blogs of sorts. Some of them - including several of my poor attempts at a website - were basically glorified virtual pet simulators attempting to - and miserably failing at - competing with more sophisticated equivalents, such as TeveClub *(Camel Club)*. I could write a whole article and rise and fall of the text-based virtual pet simulators of the Hungarian Internet, **and trust me, one day, I will** *(**and I did**)***.** But long story short, many people with no programming knowledge - including me at the time - used G-Portal as a platform for creating yet another poor man's version of a turn-based RPG. https://gportal.hu/ https://read.cash/@Metalhead33/the-rise-and-fall-of-the-hungarian-text-based-pet-simulator-fe8c5ab3 Eventually, I switched from these G-Portal based virtual pet simulators, to forum-based roleplaying games. I joined several, and created several - all of which failed to attract even a single player. I no longer even remember what where they about. All I remember is that one was a loose adaptation of Doom, and another one revolved around some circus and Alice Cooper. https://en.wikipedia.org/wiki/Play-by-post_role-playing_game#Message-board_role-playing https://ways-of-darkness.sonck.nl/Doom_FRPG https://www.youtube.com/watch?v=27evSVwjzzI However, two of which I joined - Alternate World and the Age of Boulderons - had a profound influence on me and my later works, while at the same time, I was experiment with different kinds of settings. https://ways-of-darkness.sonck.nl/Alternate_World_(FRPG) https://ways-of-darkness.sonck.nl/Age_of_Boulderons_(FRPG) The two FRPGs I mentioned are also important, because that was the place where I would meet several people who would aid me in my initial efforts of building up **Ways of Darkness**. Ways of Darkness is born The Forum is born Ways of Darkness, the franchise - and the forum-based RPG - were officially born on the 19th of December, 2009, making the franchise 11 years old as of the June of 2021. https://ways-of-darkness.sonck.nl/Ways_of_Darkness https://ways-of-darkness.sonck.nl/Ways_of_Darkness_(FRPG) It all started with me and Kriszta *(Christina)* getting bored of our Doom FRPG - which was dying to begin with - and deciding to return to our roots and create a Medieval Fantasy FRPG. I got to know Kriszta on the Age of Boulderons, along with Yasami, who joined us from the start - he was mostly responsible for creating the locations. https://ways-of-darkness.sonck.nl/Kriszta https://ways-of-darkness.sonck.nl/%C3%81d%C3%A1m_Nyiri https://ways-of-darkness.sonck.nl/Category:Forum_locations Admittedly, we were off to a less than stellar start: after writing down all the necessary rules on how to RP, I started by plagiarizing the descriptions of the races *(Humans, High Elves, Wood Elves, Dark Elves, Orcs, Goblins, Ogres, Dwarves, Gnomes, Halflings)*, classes *(Warrior, Thief, Cleric, Druid, Ranger, Knight/Paladin, Inquisitor)* and the 9 alignments *(Lawful / Neutral / Chaotic + Good / Neutral / Evil)* from random wikis and Hungarian RPG sites. The description of the Goblins was basically copied from Alternate World, originally written by László Dornfeld, *"augmented"* by some parts copied from Warhammer Fantasy. This was followed by me and Kriszta coming up with the names of the states for each race: Etrand for the Humans *(as well as Dwarven, Gnomish and Halfling minorities)*, Froturn for the High Elves, Dragoc for the Wood Elves, Brutang for the Orcs, etc. Kriszta also came up with Neressa, a racially heterogenous, cosmopolitan *"pocket-empire" (city-state ruled by an "Emperor")*. Then came the cities. Then we filled out the creatures, with me taking heavy inspiration from the *(Heroes of)* Might and Magic series for the Demons, making them have a caste system that goes from Imps to Devils, with Horned Demons, Incubi, Succubi and Ifrits in between. https://ways-of-darkness.sonck.nl/L%C3%A1szl%C3%B3_Dornfeld It wasn't truly unique, but rather a mish-mash of different Medieval Fantasy, High Fantasy and Dark Fantasy influences. I also drew a horrible world map, that is now, for better or worse, lost to history. It was terrible to begin with, so Kriszta drew something better to replace it. Even though I always mistakenly insist that Ways of Darkness was born in 2009, in reality, much of the work that made it fit for roleplaying was done in early 2010. Out of the 28 stories that took place on the forum-based RPG between 2010 and 2015, two took place on the original forum, respectively between the 8th of January 2010 and the 13th of February 2010. https://ways-of-darkness.sonck.nl/Category:Legacy_stories https://ways-of-darkness.sonck.nl/Category:ProBoards_archives https://ways-of-darkness.sonck.nl/RPG:Archive_Hun_1 https://ways-of-darkness.sonck.nl/RPG:Archive_Hun_2 During late February, 2010, I began writing the history of the world, establishing a calendar, and establishing that the canonical date at the FRPG was set was 831 years after the foundation of the Kingdom of Etrand, using that kingdom's creation as the world's equivalent of Jesus Christ's birth, the epoch to which every date was compared. Arguably, the very first thing that made Ways of Darkness slightly unique was the fact, that Humans are Elves. That's right, in Ways of Darkness, the Proto-Elves split into the High Elves, Wood Elves and Humans - the Orcs and Dark Elves came into existence later. The Exodus In late February, 2010, terrible news reached my ears: ProBoards was removing non-English message boards! I originally wanted to prefix the verb *"removing"* with *"allegedly"*, but then I remembered that some French-language message boards were in fact removed. So, we began packing our bags and moving to another place - Forumotion, a phpBB2-based platform. The original ProBoards forum was actually never removed *(until I deleted it in the October of 2020)*. We completed the migration by the April of 2010. However, this was more than just a migration. We rewrote the descriptions of the races and classes to make them more detailed and less blatantly plagiarized. Kawazoe - the administrator of Alternate World - came to my help to make the descriptions of the religions. With the help of Roland Heim, the descriptions of weapons were added. https://ways-of-darkness.sonck.nl/%C3%81d%C3%A1m_Kalocsai https://ways-of-darkness.sonck.nl/Roland_Heim The lore was expanded - new races *(e.g. the* *Nereids**)* were added, the Kingdom of Etrand received a list of kings, the history was slightly revamped with a chronology added, a few new classes were added, several locations were expanded or completely revamped, etc. Basically, it was a leap comparable to the leap from *The Elder Scrolls I-II: Arena and Daggerfall* to *The Elder Scrolls III: Morrowind*, with the lore really taking shape. https://ways-of-darkness.sonck.nl/Nereids Out of the 28 legacy stories, 26 - which is to say, all of them except the First Play and Stranglehold - took part on this new phpBB2 forum. The bulk of these - almost half of them - took place in 2010. In the same year, I also created an English version of the FRPG, but it didn't attract any players at all. https://ways-of-darkness.sonck.nl/Category:Legacy_stories https://ways-of-darkness.sonck.nl/Category:PhpBB2_archives On the June of 2011, a new race was added: Winged Cobras, by Péter Pataki, whom I got to know back on Alternate World . In the very same year, the Jorge-Kshabdalim Saga began, which I might adapt into a novel one day. https://ways-of-darkness.sonck.nl/Winged_Cobras https://ways-of-darkness.sonck.nl/P%C3%A9ter_Pataki https://ways-of-darkness.sonck.nl/The_Jorge-Kshabdalim_Saga_(RP) In the Spring of 2013, Mónika Sebestyén joined the team, contributing a bit to the lore, contributing a new skill, and bringing with her a few players. Her presence brought a brief revival to the forum that was effectively dead in 2012. https://ways-of-darkness.sonck.nl/M%C3%B3nika_Sebesty%C3%A9n Eventually, in 2014, activity dropped again, and the last true legacy roleplaying sessions were in 2014 and 2015. After that, the forum simply died. In the Spring of 2020, Kriszta and I decided to have one more roleplaying session, but that did not end well... https://ways-of-darkness.sonck.nl/Dungeon-Crawlin%27_in_the_South_(RP) https://ways-of-darkness.sonck.nl/Deknighted_(RP) https://ways-of-darkness.sonck.nl/Cradar%27s_Head_(RP) In the end, in the October of 2020, I decided to lay the forum to a rest. I deleted the message board. Of course, before doing that, I archived everything, and put all the archives up to my Wiki, with the roleplaying ones being here, the non-roleplaying ones here and here. https://ways-of-darkness.sonck.nl/Category:PhpBB2_archives https://ways-of-darkness.sonck.nl/Meta:Ways_of_Darkness_(FRPG) https://ways-of-darkness.sonck.nl/Meta:Ways_of_Darkness_(FRPG)/hu However, the death of the forum was **not** the death of the franchise, not by a long shot. Attempted Expansion - Video Games and Cartoons I have had ambitions to expand the Ways of Darkness franchise from its FRPG roots into something bigger from the very start. However, I did not know any programming yet, and did not bother to learn it yet. So, what did I do? I turned to my trusty old RPG Maker and began developing Emergence of Darkness. https://ways-of-darkness.sonck.nl/Emergence_of_Darkness https://youtu.be/aYe8GW1saMk It wasn't exactly my first attempt at putting Ways of Darkness into a video game - my only finished game to date, Alternate World - a loose adaptation of the forum - had a good deal of the endgame set in Artograch, the world of Ways of Darkness. https://ways-of-darkness.sonck.nl/Alternate_World_(2011_game) Sadly, Emergence of Darkness was never finished. The story got nowhere, I had no idea how to continue, and simply lost interest in it. In the exact same year - 2012 - I tried once again, this time naming the game just Ways of Darkness. All the videos can be seen on this playlist. https://ways-of-darkness.sonck.nl/Ways_of_Darkness_(2012_game) https://www.youtube.com/playlist?list=PLRISetYAvPAd-iU_GGbE0d-lb0e9OOanz https://www.youtube.com/watch?v=MvMvshQb0xc&list=PLRISetYAvPAd-iU_GGbE0d-lb0e9OOanz&index=5 Once again, it fell flat. I had everything handed to me. I had voice actors working for free. I had a composer working for me. And yet, I failed. Why? Because I was the one writing the story, and this is the department I wasn't receiving much aid in. And alas, I simply lost my ability to continue the story, lost my interest in continuing, and it flopped. If only I was in contact with Roland - he and his friends could have came up with something. https://ways-of-darkness.sonck.nl/Zsolt_%C3%81brah%C3%A1m In 2013, I tried once again, with the results visible on this YouTube playlist. https://ways-of-darkness.sonck.nl/Ways_of_Darkness_(2013_game) https://youtube.com/playlist?list=PLFBF47B32B1A1D0B7 https://www.youtube.com/watch?v=8tjf3rHdRUc&list=PLFBF47B32B1A1D0B7&index=2 This project had an even better start than the previous: this time, I was wise enough not to go onto the voice acting forums and open my project for auditions, knowing that voice acting will have to be the very least thing the game will have. Instead, I focused on features. For aid with the story, I gathered a large group of weeaboos, and also the aforementioned Mónika Sebestyén. Just about every character from the original FRPG would have made an appearence as a playable companion / party member. https://ways-of-darkness.sonck.nl/Category:Legacy_characters I basically had a whole crew: I had Zsolt Ábrahám composing music. I had my trusty team of weebs - and Mónika - giving me ideas for the story. Yet, despite all our ambitious plans.... **nothing happened**. All that I produced were basically tech demos, if you can even call them that. Ever since, I have had an ambition to develop a 3D Ways of Darkness game instead in C++, with my own engine. The composer wanted to create a Ways of Darkness cartoon. **Of course, nothing came out of that either.** https://ways-of-darkness.sonck.nl/Ways_of_Darkness_(3D_project) https://ways-of-darkness.sonck.nl/Haruka_Engine https://ways-of-darkness.sonck.nl/Ways_of_Darkness_(cancelled_cartoon) In 2018, I also attempted to write a novel, but lost interest and inspiration after the initial spark. The Great Wiki Expansion On the 15th of July, 2015, I have decided to create a Wiki for the Ways of Darkness franchise. What made me take such a decision? https://ways-of-darkness.sonck.nl/Ways_of_Darkness This is the part where I make a confession: I'm not just a worldbuilder, but also a roleplayer. Okay, that part doesn't need much confessing, as I just showed you plenty of roleplaying logs. But what I neglected to mention, that I didn't keep Stephanus Tavilrond confined to the FRPG and its attempted adaptations. Oh no. I took him outside too, erotic roleplaying sites like F-List, to various Facebook, Google+ and MeWe roleplaying groups, etc. https://ways-of-darkness.sonck.nl/Steph%C3%A1nus_Tavilrond Aside from distorting and contorting poor Stephanus's characteristics and personality to roleplay on those platforms, there was another problem: where to store all the information? Where would I point people to, when they wanted to learn more about Stephanus and the world he inhabited? At first, I simply linked to the English translation of the FRPG. Eventually, however, at some roleplaying website, someone recommended that I create a Wiki. In hindsight, World Anvil would have sufficed for my originally intended purposes, but that didn't even exist back in 2015. Thus, the Ways of Darkness Wiki went live, originally on Wikia *(later renamed Fandom)*, but then moved to a site hosted by a friend of mine in early 2016. https://www.worldanvil.com/ The period between the July of 2015 and November of 2015 saw a great expansion of the lore, mostly thanks to László Dornfeld, who breathed new life into the history of the world and the characterization of several historical figures, such as King Bryant I of Etrand - before Dornfeld's intervention, all we knew about said king was that he was the husband of the previous king's only daughter, but Dornfeld gave it a whole new spin, by creating an exciting story about an impoverished nobleman climbing up the ladder in the royal court and winning a civil war on queen's behalf. Even though Dornfeld left the team acrimoniously in November - due to the way I mistreated him, much to my regret - his presence was a huge inspiration for both me and Kriszta to expand upon the lore. And that we did. https://ways-of-darkness.sonck.nl/L%C3%A1szl%C3%B3_Dornfeld https://ways-of-darkness.sonck.nl/King_Bryant_I_of_Etrand The leap forward may have been more gradual than sudden *(taking place in 5 real-life years instead of just three months)*, but it was nevertheless a great leap comparable to the one we took during the transition from Proboards to phpBB2. Languages were given phonologies. Religions were given even more detail, e.g. their attitudes towards sexuality. Non-playable historical characters were fleshed out. The histories of kingdoms were fleshed out. Individual battles were given attention. The world had come alive. For one.... the world itself got expanded heavily in all its dimensions. At first, this expansion was adding just one new continent: the eastern one, with Dornfeld planning to fill it with the *"Ling Empire" (inspired by China)*, the Argyrian Empire and some kind of Greco-Persian Empire. Because he left, I took up the mantle, replacing the Lings with the Shár, but also adding the Týrýng Empire, Pepetoka city-states, etc.. https://ways-of-darkness.sonck.nl/Sh%C3%A1r_Empire https://ways-of-darkness.sonck.nl/Empire_of_T%C3%BDr%C3%BDng https://ways-of-darkness.sonck.nl/Pepetoka_people Ever since 2016ish, I have been the de facto sole creator of the world. Kriszta is now semi-retired and only occasionally gives me advice, Roland Heim and Zsolt Ábrahám still occasionally give me advice, Mónika Sebestyén is long gone, Richárd Képes de facto quit due to my feud with him, etc. It would be great to have Kriszta and László Dornfeld back, but the earlier is sadly struggling with work just as much as I am, while the latter is probably still unwilling to forgive me for mistreating him, and even if he did, the best I can hope for is the burial of the hatchet, not getting back a contributor. https://ways-of-darkness.sonck.nl/Rich%C3%A1rd_K%C3%A9pes Still, ever since that, Ways of Darkness has went through a transformation. It got further and further away from its roots as a generic Medieval Fantasy world, and got more and more unique quirks and little touches that made it feel like a crafted world, it also got Steampunk elements, elements of speculative fiction, etc. Ways of Darkness was no longer a generic fantasy world - it was a worldbuilding project that merely had that appearance on the surface, but underneath, more and more things were changing. A New (Discord) Hope? Some time around 2017, I established the Ways of Darkness Discord group. I don't remember much activity that went on it, but I do remember, that in late 2020, I merged it with two of my other Discord groups, to create a new group that combines them all: Dēmiourgoí Kósmōn. https://discord.gg/BUFPauXcax Around the same time - 2017-2020 - I also frequented Worldbuilding channels on Discord, got influenced by YouTube channels like Lindybeige, Metatron, Skallagrim, Shadiversity, Snapjelly, Artifexian, Worldbuilding Notes, Stoneworks, Hello Future Me, Biblaridion, etc. https://www.youtube.com/user/lindybeige https://www.youtube.com/channel/UCIjGKyrdT4Gja0VLO40RlOw https://www.youtube.com/user/SkallagrimNilsson https://www.youtube.com/user/shadmbrooks https://www.youtube.com/channel/UCHTZ3HbTYTeGY8rii6KfMHw https://www.youtube.com/user/Artifexian https://www.youtube.com/channel/UCncTjqw75krp9j_wRRh5Gvw https://www.youtube.com/channel/UClnWLqdyrQ-hcDYW5kQQ6vQ https://www.youtube.com/channel/UCFQMO-YL87u-6Rt8hIVsRjA https://www.youtube.com/channel/UCMjTcpv56G_W0FRIdPHBn4A Out of these, Snapjelly even reviewed the swords I modelled for Ways of Darkness! https://www.youtube.com/watch?v=_yBWSBkCqDA Due to their influence, focus shifted towards realism, but this came with problems: I had to tip-toe and make sure that in my quest for realism and believability, I didn't complete trip over and uproot the pre-existing lore - after all, by that time, Ways of Darkness was a 7-10 years old franchise, and lore is like a house of cards - you can't just take out something and replace it, it will simply collapse. So ever since that time, I have been trying to delicately balance realism and compatibility with the pre-existing lore, carefully and conservatively modifying the latter. My Demiourgoi Kósmón is a semi-active Discord server. I have at least one friend who is giving me ideas for the setting. I have another who encouraged me to adapt the Jorge-Kshabdalim Saga to a novel. https://ways-of-darkness.sonck.nl/The_Jorge-Kshabdalim_Saga_(RP) While sadly, I still lack the time it takes to truly dedicate myself to my passion-project, this Discord group has given me a new hope, and I occasionally edit the lore here and there, gradually shaping it. Perhaps on this Discord, I shall find the replacement for the contributors I lost. Perhaps Ways of Darkness will rise again. Final Words? A lot of people - usually childless women - call their pets their *"children"*. Daenerys Targaryen from Game of Thrones called her dragons her children. As cliché'd as it sounds, Ways of Darkness is my child. It never earned me a single penny, but I have nurtured it since 2009, and I simply cannot let go of it - it is my child, and I will continue to cherish it until the day I die, swearing to one day establish it as a franchise. https://www.youtube.com/watch?v=1Gjl8FnzAcY&list=PLRISetYAvPAeZ5x0BQxYojhzijbv4qkYj&index=10 Currently, due to being a full-time worker working 40-hour workweeks, I have no ambitions to revive the forum, no time to write a novel or work on any video game, and only limited time to truly work my setting. I even retired from roleplaying, more or less, and will likely remain retired until the nightmare ends *(which is to say: until I win the lottery, make it big with crypto, make it big with read.cash, the Hungarian government decides to implement Universal Basic Income for some reason, or something else that frees me from the chains of wageslavery)*. Not sure if I truly have what it takes to lay Stephanus Tavilrond to rest, but I'll try to lay a tribute to all the fond memories I had, both on the forum and outside of it. **Sayonara**, Stranglehold, Jorge-Kshabdalim Saga, Brother-Brothel Showdown, Dungeon Crawlin', Cradar's Head, B&M Chronicles, A Knight and a Maid, From Over the Water, Knight and Knightess, Fox and Knight. These stories shall forever be fond memories for me. Also sayonara to all the other unlisted roleplayers I have fond memories of. https://ways-of-darkness.sonck.nl/Stranglehold_(RP) https://ways-of-darkness.sonck.nl/The_Jorge-Kshabdalim_Saga_(RP) https://ways-of-darkness.sonck.nl/Brother-Brothel_Showdown_(RP) https://ways-of-darkness.sonck.nl/Dungeon-Crawlin%27_in_the_South_(RP) https://ways-of-darkness.sonck.nl/Cradar%27s_Head_(RP) https://ways-of-darkness.sonck.nl/B%26M_Chronicles https://ways-of-darkness.sonck.nl/A_Knight_and_a_Maid_(RP) https://ways-of-darkness.sonck.nl/From_Over_The_Water_(RP) https://ways-of-darkness.sonck.nl/Knight_and_Knightess_(RP) https://ways-of-darkness.sonck.nl/Fox_and_Knight_(RP) One day, when the nightmare ends, these stories will be given the justice they deserve. Now, if I wasn't so lazy, I'd create a video, a Ways of Darkness montage of some sorts, and add a song to it. Probably the song I am about to link. But because I'm lazy, I'm just linking the song: https://www.youtube.com/watch?v=2VbODnX0dVs Ways of Darkness shall forever be my cherished passion-project. No doubt, it will go through even more evolution, before anything of note gets produced. But that's just how it goes. It evolves. So there it goes. This is all I can say right now. Maybe one day, I will write another article about my passion project. Or write about some of the other things I promised to write about.

@Metalhead33

Lessons from my story One month ago, I wrote a rather whiny article where I basically shared my sob story with the world. As much as it made me feel better - and made me feel like throwing off some weight from my shoulders - in hindsight, I realized that there was one important thing that I missed from the article: **the moral of the story.** https://read.cash/@Metalhead33/sunday-evening-rant-my-miserable-existence-dba2d582 Make no mistake: when people like me share their sob stories, it is beyond any reasonable doubt, that their goal is to make you feel sorry for them, so they can feel better about themselves. I don't claim to the contrary. However, even such sob-stories should ideally have a moral, a lesson within them. So, what are the lessons to be learned from my story? What were my biggest mistakes that you should avoid? Lesson 1: Discipline over Talent Is there such a thing as *"too smart for your own good"*? I reckon there is. There are people out there pointing out that beauty can be a curse - and I say the same about intelligence, talent and luck. https://tvtropes.org/pmwiki/pmwiki.php/Main/SoBeautifulItsACurse I don't mean to tout my own horn or pat myself on the back for my intelligence, but I dare to say, that I am - or was - too smart for my own good. Or maybe just too lucky for my own good. How can luck be a bad thing? Easily. When I was a little schoolkid, I was called a *"gifted child"*. I got good grades without having to study at home. My one and only weakness was poems, and sometimes year numbers in history. I was rather good at remembering abstract ideas and re-phrasing them in my own way. Then, as time went on, my luck started to run dry. In college, just having good memory and being a good listener were no longer enough to pass the exams: you had to actually face the book and cram to avoid failure, and boy was I bad at it. I couldn't - and still have serious trouble with this - force myself to face the book for more than half an hour. And therein lies the crux of the problem. If I was born just slightly less intelligent or less lucky, I would have had gotten some harsh lessons early enough in life to get me to learn how to study, to learn how to be disciplined, to learn how to face the book. But alas, it came way too late, when I was too old and stubborn to change my habits and spend my free time with anything other than procrastination. And now I'm a miserable software developer who earns $700 per month whose only ambition - after less than a year of work - is to retire as soon as possible. **Let that be an important lesson, kids:** smart people don't necessarily prosper - disciplined people do. Lesson 2: Missed opportunities A rather recurring theme of my life is missing good opportunities. For example, I spent seven years in college, when I should have finished it in three and a half years. As if that wasn't bad enough, during college, I had several opportunities to learn languages, to gain experience that would help me in my career, to kickstart a better future. I could have spent my summers on some projects instead of procrastination. If I was just slightly less lazy - and slightly more disciplined - I would have finished college instead of failing it, and I'd be someone of the caliber of my computer architecture teacher. I could have been a contender, instead of the bum I now. https://tvtropes.org/pmwiki/pmwiki.php/Main/ICouldaBeenAContender I constantly daydream about winning the lottery, but I never even bother trying lottery to begin with. You can't win something you're not even trying. Hell, arguably, the biggest missed opportunity of my life - which, in hindsight, would have required me to see the future - was not getting into crypto back in the days before 2014, back when bitcoin was being given out for free from faucets. Remember kids - get into crypto while you can. But really, whenever there's an opportunity for you to gain tremendous advantage, don't waste it. Don't be like me, someone who was always too lazy and afraid. https://read.cash/@Metalhead33/get-into-crypto-while-you-can-282cd3be I was always too lazy and afraid to take action and actually live with all the opportunities that life was offering me on a silver platter, couldn't be offered to lift my arm to put what was on the silver plate into my mouth. And look at me now. **What I am now? A passive victim of my circumstances. Learn from my mistake, and become a man of action.** Don't be a passive victim of your circumstances like I am. Become a man of action who is not content with just riding the waves. Lesson 3: Enjoy it while you can This is going to directly contradict Lesson 2, but here we go: whenever you have an opportunity to have fun, don't waste it. Live with it. Invited to a party? Go for it. Surrounded by hot chicks? Ask them out on dates. Have an opportunity to go to the beach? Go for it. Have an opportunity to go to a live show? Go for it. Don't waste any of those, because in your adult life, your free time will be much more limited. Unless you're wealthy enough to live a work-free life - provided that you ration your money wisely - you're living in borrowed time. Why borrowed time? Because from my point of view, having to sacrifice at least one third of each of your weekdays is not living. On paper, it's just 40 hours a week, and you're free the remaining 124 hours of the week - in reality, those 40 hours dominate the remaining 124, dictating your sleep schedule, your eating habits, etc. often straight-up spilling over and creeping into those 124. And for what? So you can have just enough not to starve to death, while the government taxes over one third of it? So that you can spend the rest of your day exhausted, your Saturday cleaning up the house, your Sunday dreading Monday? That's not living, if you ask me. So enjoy your youth, your childhood, your freedom while you can. A counterargument is that a frog that is being boiled slowly won't even notice that it's being boiled - unlike the one that you try to throw into hot water, jumping out immediately - so, arguably, if I started by doing part-time student jobs back in high school and college, I wouldn't be so miserable now, as I would have gotten broken in and used to it. But from my point of view, that would have meant that I would have never gotten any taste of freedom. Granted, I sadly didn't spend that extra free time having as much fun as I should have - instead, I spent it procrastinating, when I should have spent it on the beach hitting on cute girls. Oh well. Enjoy while it lasts. I'd give an arm and a leg for it to be 2012 again. But alas, the past is never coming back, so enjoy it while it lasts. **Carpe diem!** Lesson 4: Keep your enemies close, but your friends even closer Another theme of my life is that I'm terrible keeping my friends - both real-life and online. Back in elementary school, I had some friends. How many do I still talk to? None. I ceased contact with every single one of them over ten years ago. The fond memories I have of them are fading by day. High school friends? I gradually ceased contact after I got into college. College friends? I gradually ceased contact as they graduated while I kept falling behind. Coworkers? I absolutely refuse to contact them outside of work, and you can bet I'll cease contact with them once the nightmare ends. Now, unlike what I'm planning with my coworkers, none of these were intentional. I did not go out of my way to turn my back on these people. I just forgot to invest time and energy into maintaining those friendships. Now all of these people are people that I used to know. My online friends don't have a much better track record either. All the people that I used to know between 2004 and 2008? Gone. My oldest friend whom I still occasionally contact, I got to know in 2009. However, the overwhelming majority of my Facebook, Discord, etc. friends, I have only known since 2017. The majority of the friends I made between 2009 and 2017 are people that I used to know. Unlike the case of my real-life friends - where it was just me not putting in the time and effort to maintain those friendships -, some of these online friendships ended through no fault of my own: the people just disappeared, never to log in again to the website or platform that I used to chat with them on. In many cases, the platform in question no longer even exists. And now I am constantly haunted by fond memories of people whose names I don't even remember. **Don't be like me - put time and effort into maintaining your friendships. Keep your enemies close, but your friends even closer.** Lesson 5: Never give up Whenever you try to learn something - e.g. playing the guitar, composing music, drawing, writing, programming, etc. - never give up. Especially not after the first try. I am terrible at composing music. Don't believe me? Listen to these. https://youtube.com/playlist?list=PLRISetYAvPAeRDHIpSIdCO79AwAfwtWb6 https://www.youtube.com/watch?v=79Pj2FlO3NU&list=PLRISetYAvPAeRDHIpSIdCO79AwAfwtWb6&index=14&t=1s I still draw like a 5-year old, and I am going to prove it to you. I was born in 1992. When do you think I drew this? 1995? 1997? Nope, I drew it in 2012. Draw your own conclusions. Could I have become a decent artist if I just kept trying? I dunno. I did dabble a bit in vector art.. And somewhat in pixel art as well... Maybe in an alternate timeline, where I was more disciplined, and didn't give up, I could have been a decent composer. Or a decent artist. Or both. But the absolute worst sin that I committed was giving up on so many video game projects I was developing. Everything was handed to me. I had resources. I had an engine *(RPG Maker)*. Yet every single time, I gave up. My only game to be ever released is buggy as all hell. One of my projects even had voice actors, yet it failed. https://ways-of-darkness.sonck.nl/Alternate_World_(2011_game) https://ways-of-darkness.sonck.nl/Ways_of_Darkness_(2012_game) https://www.youtube.com/playlist?list=PLRISetYAvPAd-iU_GGbE0d-lb0e9OOanz https://www.youtube.com/watch?v=MvMvshQb0xc&list=PLRISetYAvPAd-iU_GGbE0d-lb0e9OOanz&index=5 The lesson here? **Never give up! You'll never be good at anything, if you give up after the first try!** Lesson 6: Face life with a defiant smile All of my previous lessons were lessons to be learned from my mistakes. But this one, on the other hand, is not something I forgot to do, but something I still do. **Face life with a defiant smile. When life tries to break you, refuse to be broken in.** Yes, I complain a lot. Yes, I a miserable. Yes, I'm whiny. But you know what? **At least I did not give up.** Despite all my complaints, I'm still here, living, kicking, and still holding out for hope that one day, the nightmare end. I may not see the light at the end of the tunnel yet, but I'm positive it's there somewhere. No matter what life throws out at you, it's important to always look at the positives, and keep going. It's important to always remain hopeful, and never give up. https://www.youtube.com/watch?v=RMFR9ZJZdrs **Never give up!**

+6 more

@Metalhead33

Chicken or Egg? Catch 22 Which came first? The chicken or the egg? This is a commonly asked question, and it's closely related to the logical paradox known as the Catch-22. It's essentially a feedback loop, a recursion of sort. The Case of the RNG When you take user inputs out of the equation, a computer is a deterministic machine. Given the same initial state and the same inputs or operands, a computer should - at least, when working as intended - **always** reproduce the exact same results. Given two operands *(integers or floating point numbers)*, an arithmetic operation - e.g. an addition or multiplication - should reproduce the exact same results right down to the bit *(we're ignoring endianness for the sake of simplicity)* on every computer, assuming identical implementation of the arithmetic instructions. And really, it's a no-brainier: two plus two equals four, no matter who does the counting. That's just how mathematics work, and the CPU is basically a glorified calculator where everything *(such as pixels on the screen, or sound samples you hear)* is represented by numbers that the CPU does arithmetics on. But what if you don't want deterministic operation? What if you **do** want randomness? Randomness is a necessity in video games and cryptography, so there must be a solution for this need for randomness, right? The hardware-based solution would be to have a device measuring ambient noise, or taking pictures of lava lamps and hashing them. https://www.youtube.com/watch?v=1cUUfMeOijg https://www.youtube.com/watch?v=1cUUfMeOijg But what if you don't want - or can't have - a hardware dedicated specifically to this task? Well, then I have bad news for you: true random-number generation simply cannot be implemented in software. The closest we can get is **pseudo-random number generation**, which, for non-cryptographic purposes, such as video games and image editors, is close enough. https://en.wikipedia.org/wiki/Pseudorandom_number_generator The simplest and crudest way is to simply pre-generate and cache a number of randomly generated numbers - e.g. 256, 512 or 1024 of them - and then keep iterating over them. But, aside from creating a noticeable pattern that players can exploit, it also leads us to a chicken-and-egg problem, a catch-22: we need random numbers to generate random numbers. Surely we can do better, right? Well, there are several pseudo-random number generation algorithms out there, ranging on a spectrum from fast-but-predictable to slow-but-convincing. The most well-known among them *(e.g.* *linear congruential generator**,* *multiplicative congruential generator**,* *Mersenne Twister**, etc.)* all have one thing in common: they have an initial *"seed"* and generate random numbers by modulating said seed according to an algorithm. Not only is this predictable - as you can theoretically predict the *"random"* numbers if you know the seed and know the algorithm - but also leads us to the exact same chicken-and-egg catch-22: to prevent predictability, the seed has to be an unpredictable random number, thus, once again, we need a random number to generate random numbers. https://en.wikipedia.org/wiki/Linear_congruential_generator https://en.wikipedia.org/wiki/Lehmer_random_number_generator https://en.wikipedia.org/wiki/Mersenne_Twister This catch-22 can be easily solved by simply taking a variable from the computer state - e.g. runtime, current date, etc. Or, in case of random map generators and whatnot, we can even ask the user to input text, hash it, and use said hash as the seed. However, other chicken-and-egg problems, other catch-22s are not so easily solved in life. The Great Leap Forward Sometimes, life requires you to tackle two problems at once, solve two problems simultaneously. If you try to solve only one at a time, the other will get worse and simply make it impossible to solve the one you were trying to solve. So, in that case, life requires you to make a Great Leap Forward. No, **not** that Great Leap Forward. https://en.wikipedia.org/wiki/Great_Leap_Forward On the larger scale of things, one of those problem duos is.... you guessed it, automation and UBI. My main audience is already getting really bored of me eternally chewing on the subject of UBI, so I was hoping to put it to rest, and originally intending to make this article the UBI-article to end all UBI-articles, but... we'll see. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4 You see, UBI and automation are the same catch-22 problem. Anti-automation Luddites rightfully point out that automation would cause massive unemployment, which, under the current UBI-less work-or-die system, would translate into massive starvation, even though already more than half of all food is wasted. https://www.youtube.com/watch?v=7Pq-S557XQU https://youtu.be/Gc_nLK4ji_k?t=703 Anti-UBI naysayers point out that if we implemented UBI, *"nobody would want to work"*, everybody would be just lazy, etc. But why would that be a problem though, if robots did all the work anyway? When confronting these two, I tell the Luddites about UBI, and tell the anti-UBI naysayers about automation. However, both remain unconvinced, and loop back to their original arguments, or even switch roles. **Happy wageslave:** *"We can't implement UBI, people will just stop working"* **Me:** *"What about automation? We can just automate away the most unpleasant jobs that no one wants to do, and the rest can be done by volunteers. The human desire to be contribute and be a productive member of society is criminally underrated by the likes of you."* **Happy wageslave:** transforms into a Luddite *"No, we can't automate away* ***any*** *jobs, because that would cause massive unemployment!!!"* **Me:** *"That's the whole reason why we want UBI in the first place!"* **Happy wageslave:** *"No, we can't have UBI, because then people will just stop working. Also,* *muh GDP, muh inflation rates...**"* **Me:** *"..."* https://read.cash/@Metalhead33/market-fundamentalism-6dcf6913 Or, alternatively... **Luddite:** *"We can't embrace* *automation**! It'll make everyone unemployed! We'll all starve!"* **Me:** *"What about Universal Basic Income? That sounds like a good solution to the existential challenges posed by automation."* **Luddite:** transforms into a Happy Wageslave *"No, we can't do that, because people will just stop working then! Also,* *muh GDP, muh inflation rates...* *If we implement UBI, people will just get lazy! Who will do all the work?!"* **Me:** *"Robots and AI will!"* **Luddite:** *"We can't embrace automation! It'll make everyone unemployed! We'll all starve!"* **Me:** *"..."* https://www.youtube.com/watch?v=7Pq-S557XQU https://read.cash/@Metalhead33/market-fundamentalism-6dcf6913 As much as I think that their line of thinking is heavily flawed, to say the least, I can understand them. These two problems - UBI and automation - **must** be tackled all at once, simultaneously, not separately, not one-at-a-time. We have to make a singular Great Leap Forward, otherwise we'll be forever stuck worrying about the unemployment caused by robots and AI, worrying about the loss of workforce and the amount of taxes and debt UBI would cause. If we focus on automation first without implementing UBI, millions will go not just unemployed, but outright unemployable. They'll end up on the streets, starving and destitute. If we focus on implementing UBI first without deploying the technology necessary to replace the lost labour, then millions - including me - will leave behind their day jobs. Things will get more expensive. Taxes will rise, the country will accrue foreign debt, companies will leave, etc. However, I make the counterargument, that most jobs are unnecessary jobs that don't create actual value, and that people's willingness to contribute and be productive members of society is being criminally underestimated. I also make the argument that I reckon, that the technology is already there, we're just too chicken to deploy it out of fear of unemployment. Which leads me back to the aforementioned chicken-and-egg catch-22. https://read.cash/@Metalhead33/financing-universal-basic-income-ii-social-costs-of-jobs-d508761f Clearly, these two - UBI and automation - will need to go hand-in-hand and come together. Personal chickens and eggs But the chicken-and-egg problem exists on the smaller scale too, in personal lives. **Let's start with the elephant in the room:** you need experience to gain experience. All workplaces want a 20-year old applicant with 40 years of relevant experience. They also want references, so you basically have to have a job to get a job. What if you're homeless and trying to work your way out of homelessness? Good luck, you gonna need it, because most people don't hire homeless people - gotta have a home to earn a home. But what if you already have a job, but don't like it? What if you hate your job, and think you're destined for different things? Say, you want to be an artist, a game developer or a business owner. So, you want to free yourself from the shackles of wageslavery and full-time employment? You only need two things: time and money. And herein lies the chicken-and-egg, the catch-22. Time? You need time to hone your skills as an artist or programmer, or to devise your business strategy. What about money? Obviously, you need reserve money if you want to be a freelancer in case you don't find clients for a prolonged period of time - and if you want to own a business, you need starting capital. But you don't have time if you're a wageslave working 40 hours a week. You end each workday exhausted, and the weekend is spent either on household chores, catching up on well-needed and well-deserved sleep, or just simply dreading Monday. All these things sound like excuses, but they are very valid excuses: what's the point of it all, if the sacrifice would be to give up everything that makes life worth living? If you're a typical wageslave with aspirations for different things, you probably don't have much money either, as you need to spend your earnings on food and paying the bills. You can try to save, but unless you earn a Pharaoh's salary, it won't get you far. Okay, but what if you've completely given up on the hope of being a free man in modern society, and seek to disconnect entirely, live off the grid and grow your own food? **Well, you can't**. In many places, living off the grid is illegal, producing your own power is semi-legal. Unless you already own the land needed to build your own house and grow your own food, you need to buy land, which costs money - homesteading is illegal in most countries. Only in Canada, can you simply occupy an unoccupied piece of land. In just about every other country, the government would punish you harshly for that. And even if you grew all your food and produced your own power on land that you owned, chances are, the statist mafia would still show up to force you to pay some taxes or social insurance. Ergo, even if you wanted to, you couldn't separate from society. So you're back at being a slave. The whole purpose was to free yourself from corporate wageslavery, yet you have no other options. Your best bet is to grind your teeth, try to save up enough money to live for a coupe months without work, then quit your job and try whatever you wanted to try - e.g. being an artist who does commissions for a living. But if that doesn't work out, then you'll be forced to crawl back into the world of work, begging for a job. Moral of the story? Sometimes killing two birds with one stone is not just a nice-to-have thing, but an outright necessity, because you only have one stone, and both birds are carnivorous.

+1 more

@Metalhead33

Market Fundamentalism This may be the first time I ever talk about religion on my read.cash account. But hey, I love exploring topics I haven't touched yet, so why not? A confession And so this article starts with a confession: I am a Christian. Admittedly, not a very good Christian, because I am full of vice and sin *(mostly ones of sexual nature)*, but still, I identify as a Christian, and believe that there is only one God, and that Jesus Christ is his son, who came down to Earth to redeem humanity. I'm not just any Christian - I'm a Lutheran. I believe that while humans possess free will, all humans are inherently sinful and tainted by the original sin, thus our salvation is unearned, and only granted by God's grace. As anyone who frequently reads my articles knows, I am not too fond of Statism to say the least. There is both a personal angle to it, and a religious angle. The personal one is that the more powerful the government is, the more chance it has of going full 1984, arresting people for thoughtcrimes, arresting people for victimless crimes, censoring people for innocent art and speech, etc. Yes, those victimless crimes also include my vices. I believe that freedom of speech and expressions must be protected to the absolute. The more religious angle is that I consider statism to be a form of idolatry. Statists worship the government: the government is their god; the corporations are their saints; the politicians are their preachers; and when they sin in the eyes of the state-god, they must pay indulgences or do penance in prison - the *"divine"* commandments of the state are not to be questioned. What is this, if not a religion? And God said - *"thou shall not have no other gods beside me"* - thus, statism is idolatry. In my eyes, a *"Christian"* Fascist or *"Christian"* Communist cannot be a true Christian, as they worship not God, but the government. They beg for forgiveness not from God, but from the government, from the all-powerful dictator. They might as well pray to their beloved petty dictator, who - unbeknownst to the state-worshippers - is just another mortal, like them. But Statism is not the only form of political idolatry out there: **enter Market Fundamentalism.** Market Fundamentalism No one identifies as a *"Market Fundamentalist"*. It's a pejorative term, just like any kind of Fundamentalism - a Christian or Muslim Fundamentalist isn't going to identify himself or herself as a Fundamentalist, but rather as a Faithful Christian/Muslim, a *"traditionalist"*, or even an advocate of theocracy, but never as a Fundamentalist. What does so-called *"Market Fundamentalism"* entail? Well, there are two definitions. Wikipedia defines *"Market Fundamentalism"* as *"a strong belief in the ability of unregulated laissez-faire or free-market capitalist policies to solve most economic and social problems"*. However, I contest that definition. A belief in itself doesn't make one a fundamentalist. Fundamentalism is characterized by a fanatical and irrational belief and a sense of dogmatism. I see no dogma here. https://en.wikipedia.org/wiki/Market_fundamentalism I personally define *"Market Fundamentalism"* as yet another type of idolatry, a form of numerolatry *(worship of numbers)*. I define Market Fundamentalism as the worship of mostly meaningless abstract numbers: the worship of GDP numbers, the worship of the inflation rate, the worship of the *(un)*employment rate, the worship of the stack exchange, the worship of this and that. My definition of Market Fundamentalism is the opposite of the Wikipedia definition: in my definition, Market Fundamentalists don't want to solve any social problems at all, merely want to see the numbers go up. As a matter of fact, they aren't even necessary laisez-faire - they may support government intervention in the economy in certain situations. They seek to make politics subservient to economics, and make economics subservient to nothing. They see profits as the end in itself, without any further goals. In contrast to Market Fundamentalism, pretty much every political ideology claims to advocate for policies, which from their own point of view would improve society. Every ideology seeks to make politics and economics an instrument of social improvement. Even Anarcho-Capitalists operate under the pretext, that their ideology would make the world a better place: not because of meaningless numbers changing, but because of the belief that the freer the market the freer the people, and because society is made up of individuals - individuals who value freedom - free markets benefit society. This is to be contrasted with Market Fundamentalists, who simply want productivity and the GDP to go up, even at the expense of citizens' quality of life, citizens' happiness, health, etc. Anarcho-Capitalists and Market Fundamentalists are **not** to be conflated under any circumstances, as the earlier still see the economy as a tool of achieving health, happiness and fulfillment *(under the belief that private enterprise is the superior tool to achieving those results)*, while the latter simply worship the economy and seek to placate it like a deity with sacrifices, even at the expense of more meaningful things. An Anarcho-Capitalist views economical liberalization as a gateway towards individual liberties - a Market Fundamentalist simply wants those meaningless numbers to look nice, and I guess for the establishment elites to live in luxury. While a hypothetical honest politician - a rare breed, if not extinct - seeks to implement policies that he or she sees as beneficial to the health, happiness and fulfillment of the country's citizens, the Market Fundamentalist is worried about the numbers: the GDP, the inflation rate, the *(un)*employment rate, the this and the that. We can't reduce work hours, because then the GDP would drop *(the actual negative effects of which are never explained)*. We can't switch to more efficient and automated methods of production, because then unemployment will rise *(which would necessitate the implementation of UBI)*. We can't implement Universal Basic Income, because it'll either cause inflation or require putting taxes on big corporations who will surely leave the country as a response. I'm not saying that those worries are illegitimate - especially the last one, that is a very legitimate worry -, but I'm merely pointing out that these numbers don't mean everything. If Market Fundamentalists weren't typically 60+ year old boomers, I'd be telling them to stop viewing life as a video game. There's more to life than abstract numbers. In fact, GDP pretty much means nothing. Hungary has a comparable GDP per capita to Uruguay, yet Hungarians on average are depressed and suicidal, while the people of Uruguay are not. The economy is neither a god to be placated, nor a video game where you just look at numbers changing. **The lives of real human beings are at stake. Who cares about national Gross Domestic Product, when everyone is tired, depressed, and unhealthy?!** https://read.cash/@Metalhead33/is-economical-prosperity-hurting-you-and-your-causes-949fe1ac If religious fundamentalists typically identify as faithful adherents of their religions, traditionalists and theocrats, what do the Market Fundamentalists identify themselves as? Typically *"Conservatives"*, *"(Neo)-Liberals"* and *"Centrists"*. People conveniently located inside the Overton-window. https://en.wikipedia.org/wiki/Overton_window The Centrist Scourge Centrism is arguably the most commonly misunderstood political ideology. In fact, one could argue, that it's not even a political ideology: Centrism is whatever is at the very centre of the Overton-window at the moment. You could argue, that centrism is synonymous with the status quo, with the establishment - and all its negative traits. A Centrist is basically a politician who wants to maintain the current status quo, with all its flaws. The politicians who are ordering the bombing of children's hospitals in the Middle East? They're Centrists. The politicians who keep taxing and regulating your business out of existence? Centrists. The politicians who pocket all the taxpayer money and spend it on yachts instead of spending it on medicare for the common people? Centrists. The politicians responsible for gun control? Centrists. They're all Centrists. The worst offenders guilty of Market Fundamentalism are without doubt Centrists. People on the Libertarian Right and Left alike oppose imperialist wars as a waste of taxpayer money *(and as the main cause of the humanitarian crisis in the Middle East the triggered the refugee crisis of 2011 in Europe)*, people on the Auth-Right *(Neo-Nazis, White Nationalists)* peddle their conspiracy theories about US intervention in the Middle East being *"wars for Israel"*, while people on the Auth-Left... I don't even know. People from all four corners of the political compass have reasons to oppose Western military intervention in the Middle-East, all four have reasons to oppose gun control, all four have flirted with the idea of Universal Basic Income one way or another, and all four cheered when r/WallStreetBets was ruining hedge funds. If you're a Capitalist who believes that taxation is theft - your real enemies are the Centrists. If you're a Socialist, who believes that you are entitled to free housing - your real enemies are the Centrists. Perhaps the idea of uniting with fellow political extremists in order to overthrow the Neo-Liberal Establishment wouldn't be such a bad idea? We have this misconception that Centrists are innocent moderates who believe in nothing extreme while the people in the four corners are the radicals full of unacceptable ideas, but ultimately, it's the Centrists who are causing the suffering of millions of people as they're playing chess with the universe, while the *"extremists"* typically just want to be left alone, desiring to have their own self-governed commun*(iti)*es.. And therein lies an uncomfortable truth: there is not one Overton Window, but two. The Centrists governments accross the world may be democratic on paper, in reality, they answer to their corporate overlords and typically ignore the democratic will of the people. Lessons? Normally, my articles end with some kind of lesson to be learned - most of my articles tell a story, a story with a moral. Is there a moral to this story? I believe there are several morals to this story: fundamental human values *(such as health, happiness, connections, leisure time)* are more important than abstract numbers *(like GDP, inflation rates, unemployment rates)*; money's worth is in the goods and services it can be exchanged for; Having more money is worthless, if it's at the expense of your health and happiness.... and I guess most important of all: **our governments are ran by people completely out of touch with reality.**

+2 more

@Metalhead33

The Greatest Strength and Weakness of the PC About a month ago, I wrote an article about why hardware acceleration was a mistake. Admittedly, it was a rather lousy article: short, just a three-minute read, about why increasing the amount of components of a PC that go obsolete every five years was a terrible idea. https://read.cash/@Metalhead33/hardware-acceleration-was-a-mistake-dade7306 So, even since that, I felt that I needed to elaborate on the subject further and explore it in higher details, explaining more things about why the venerable PC's greatest strength is also its Achilles heel. So, without further ado, let's get started. Instruction Sets, Architectures and Clock Cycles ISA, RISC vs CISC What is even the x86? Well, it's a computer Instruction Set Architecture. Unless you're reading this article from an Android phone, an iPhone or a Macintosh, then most likely, you are reading it from an IBM PC compatible, which contains a CPU based off the x86 architecture. https://en.wikipedia.org/wiki/IBM_PC_compatible An Instruction Set Architecture is basically a set of commands - or machine code *"instructions"* - that the CPU can natively understand and execute. It's basically the CPU's native language. The best way to explain it would be using the Little Man Computer, where each instruction is made up of a single-digit opcode and a two-digit operand. https://en.wikipedia.org/wiki/Little_man_computer#Commands For the Little Man Computer, an example program - written in machine code - would be: 510 114 310 000 And in Assembly - which is basically a more human-readable form of machine code - it would be: LDA 10 ; Load from memory address 10 into the accumulator ADD 14 ; Add the number stored at memory address 14 to the accumulator STA 10 ; Store the content of the accumulator at memory address 10, overwriting its existing content COB ; End of our little program Assuming that initially, the memory addresses 10 and 14 contain the numbers 20 and 30 respectively, by the end of the program, 10 will instead store 50. Obviously, real computers work in binary rather than decimal, so instead of X digits for opcodes and operands, we reserve X amount of bits - X usually being a power of two, like 8, 16, 32, 64, etc. Instruction Set Architectures can be neatly divided into two major camps: **RISC** *(Reduced Instruction Set)* and **CISC** *(Complex Instruction Set)*. The earlier has fixed-size instructions, with the same amount of bits reserved for each instruction's opcode and operands, while the latter typically has variable-length instructions. The earlier has instructions that do just the basics *(with more complex tasks having to be implemented from multiple instructions)*, while the latter has more complex instructions that do the heavy lifting for you, e.g. calculating the inverse square root of a number - something that has to be implemented in software on a RISC architecture. What is a clock cycle? A clock cycle is the basic unit of time at which a computer operates. On a RISC architecture, each instruction theoretically takes up the same amount of clock cycles as every other instruction (not necessarily one!). On CISC, there is no guarantee for it, as an instruction on a CISC machine often combines what needs to be done with multiple instructions on a RISC machine. In fact, very often, the same instruction takes a variable amount of clock cycles *(e.g. integer division, integer modulo, floating-point multiplication, etc.)*. We measure the speed of a CPU in terms of **clock cycles per second**, whose unit is **Hz** or **Hertz**. 1 Hetz = 1 clock cycle per second 1 KHz *(kilohertz)* = 1 000 Hz = 1000 clock cycles per second 1 MHz *(megahertz)* = 1 000 KHz = 1 000 000 Hz = 1 000 000 clock cycles per second 1 GHz *(gigahertz)* = 1 000 MHz = 1 000 000 KHz = 1 000 000 000 Hz, or clock cycles per second Keep this information in the back of the head, as it'll be important later in the article. The x86 architecture The x86 is a CISC architecture, where each instruction's length varies from 1 byte *(8 bits)* to 15 bytes *(120 bits)*, introduced in 1978. That's right, the processor of your personal computer is running on a 43-year old architecture *(not really, but I'll get into that later)*. Today, as of 2021, the x86's main rival is the ARM architecture, which is RISC. In the 1980s and 190s, the x86's known rivals were the Z80, MOS Technology 6502 *(used in the Commodore 64, Nintendo Entertainment System,Atari 2600, Apple II)* and the Motorola 68000 *(used in the Sega MegaDrive, Apple Macintosh, Atari ST and Commodore Amiga)*, most of which were CISC. It's important to note, that just because two platforms - e.g. the NES and C64, or the IBM PC and the PlayStation 4 - have a CPU based on the same architecture (*e.g. x86 for the PC and PS4)*, does not mean that software between the two platforms are going to be compatible, because they most definitely run two vastly different operating systems, and even on the hardware level, memory-mappings *(the way the CPU / operating system / software communicates with hardware peripheries, such as the sound card, video card, keyboard input, etc.)* and various other things are going to be very different. If you have a phone that runs Android, there is a 99.9% chance, that it has a CPU which is based off the ARM architecture. However, this segue is getting a bit too long, so I won't bother to explain the differences between x86 and ARM. The IBM PC's Greatest Strength and Weakness The IBM PC - at least, the typical desktop computer we are all used to seeing - follows a very **modular design**. A large number of its components can be replaced will, if they malfunction or become obsolete. Yay, that sure saves money, right? Back in the 1980s, when sound cards were a new thing, they weren't built into the computer like they often are today: you had to buy an Adlib, SoundBlaster or Gravis Ultrasound, and plop it into one of the ISA slots of your motherboard. It wasn until the arrival of the AC97 standard, that integrated, built-in soundcards became the norm. https://en.wikipedia.org/wiki/Industry_Standard_Architecture https://en.wikipedia.org/wiki/AC%2797 A similar thing can be said for GPUs - yes, PCs have always had video cards conforming to various standards *(e.g.* *MDA**,* *CGA**,* *EGA**,* *VGA**, etc.)*, but dedicted GPUs that offered 3D-acceleration didn't arrive until 1995, with the Creative Labs 3D Blaster and the S3 Virge. You can read more about them on Geri's article. https://en.wikipedia.org/wiki/IBM_Monochrome_Display_Adapter https://en.wikipedia.org/wiki/Color_Graphics_Adapter https://en.wikipedia.org/wiki/Enhanced_Graphics_Adapter https://en.wikipedia.org/wiki/Video_Graphics_Array https://read.cash/@Geri/the-first-3d-cards-ad5c643e It's important to note, that while a lot of people think that the x86 architecture and the IBM PC are practically synonymous, not every device that relies on the x86 architecture is a PC: the original Xbox used a Pentium processor which is x86. The PlayStation 4 used an x86-based CPU too. The IBM PC itself is somewhat of a Theseus's Ship - we went from MDA/CGA/EGA/VGA to having 3D acceleration, we went from ISA slots to PCI to PCI Express, we went from PC bleepers to integrated sound cards, etc. Now we even have a concept called *"**legacy-free PC**"*, which refers to modern-day IBM PC compatibles that lack all those legacy components that characterized 1980s IBM PC compatible, such as ISA slots and floppy drives. https://en.wikipedia.org/wiki/Ship_of_Theseus https://en.wikipedia.org/wiki/Legacy-free_PC A Winning Edge The reason why the IBM PC compatible eventually won - or more precisely, outlasted - all its competition so far, is due to the aforementioned modular design. Your GPU isn't rendering those triangles fast enough for the latest AAA game? Just replace it! Your sound card isn't delivering super-crisp 10.1 audio at the highest fidelity humanely possible? Just replace it! Not enough RAM to run thirty million instances of Bonzi buddy at once? Just replace it! Your processor isn't processing all the instructions fast enough? Just replace it! The PC's biggest strength - and weakness, as we'll later find out - is in this modular design, where components can be replaced at will - upgraded, if you will. While the Commodore 64, once released, was forever stuck with its 64 kilobytes of RAM, a CPU clocked 1.023 MHz, a 16-colour display and a 3-channel sound chip, the IBM PC started with was initially available with a CPU clocked at 4.77 MHz, a varying amount of RAM between 16 kB and 640 kB, a monochrome display and a single-channel sound bleeper, it eventually evolved into something much more sophisticated, with modern-day PCs often having 8-32 gigabytes of RAM, clocked at 3.70GHz, 32-bit truecolour display, above-CD-quality audio capabilities, etc. The IBM PC's 80s and early 90s rivals - the Commodore 64, Commodore Amiga, Atari ST, etc. - were un-upgradeable, while the PC, with its modular design, could evolve. The Commodore 64 is now in the attic of retro-gamers, while the IBM PC is still going strong, even 40 years after 1981. I guess you could also make arguments for this modular design from an environmentalist point of view, as you only have to replace one component at a time, instead of the entire machine - but from the point of view of economies of scale, you could make the opposite argument, as integrating every component into a single system-on-a-chip greatly reduces manufacturing costs. https://en.wikipedia.org/wiki/Economies_of_scale https://en.wikipedia.org/wiki/System_on_a_chip A Mess to Program On The biggest strength of the IBM PC is also its biggest weakness. This modular design may have allowed it to beat all its competition and last to the ripe old age of 40 as of 2021, but it always has also been - and continues to be - its Achilles's heel. Imagine being a game developer in the late 1980s and early 1990s. Let's say you were developing the game for three specific platforms: On the **Nintendo Entertainment System**, you know **exactly** what you are developing against: a CPU clocked at 1.79 MHz, a measly two kilobytes of RAM, a 4-channel chiptune synthesiser, a rather sophisticated - for the time, that is - graphics processing unit of sorts that allowed you to put 64 sprites on the screen per frame, colouring them with a palette of 48 colours. Oh, and the screen resolution was a fixed 256x240. https://www.youtube.com/watch?v=BINWqJdHy00 On the **Commodore 64**, you also knew **exactly** what you were developing against: a CPU clocekd at 1.023 MHz, 64 kilobytes of RAM, a 3-channel chiptune generator, a 16-colour palette, and a screen size of 320x200 or 160x200, depending on your choosing. https://www.youtube.com/watch?v=U9Racui9jJI On the **IBM PC**.... ho boy, things were complicated. Every PC was - and still is - an individual, a special snowflake with its own specifications. Are we even talking low-end, or high-end, or what? In **1988**, you could expect even the lowest-end PC to have an Intel 8086-compatible CPU clocked at at least 4.77 MHz, have at least 64 kilobytes of RAM, have a four-colour CGA display with a resolution of 320x200, and have a single-channel PC speaker. https://www.youtube.com/watch?v=1IOL4q5tDDQ By **1992**, this had increased to having an Intel 286 CPU clocked at 8 MHz, having at least 512 kilobytes of RAM, and an EGA display capable of rendering 16 colours at a 320x200 resolution. In **1988**, the most cutting-edge PC owned by the richest kid on the block would have an Intel 386 CPU clocked at 12 MHz, probably a couple of megabytes of RAM, a VGA video card capable of displaying 256 colours at a 320x240 resolution, and an AdLib sound card capable of producing FM synthesiszed music at 9 channels. https://www.youtube.com/watch?v=5knetge5Gs0 By **1992**, the most cutting-edge PC would have been powered by a 486 CPU clocked at 66MHz, 8 MB RAM, and probably a SoundBlaster 16 soundcard, or a Gravis Ultrasound. https://www.youtube.com/watch?v=a324ykKV-7Y https://www.youtube.com/watch?v=DoVi1__jNsI The point I'm trying to make here, is that as a software developer, you basically had no idea what kind of PCs would your customers have, so you had to specify the minimal system requirements. You basically had four choices: Write different code paths for different kinds of hardware, implementing support for CGA, EGA and VGA graphics alike, for the PC speaker and AdLib, etc. and maybe just target the lowest common denominator when it comes to CPU and RAM requirements. Just target the lowest common denominator: 4.77 MHz, 64 kilobytes of RAM, CGA, PC Speaker. Just do exactly what AAA game developers do these days: target high-end machines. That would mean 12 MHz, 512 kilobytes of RAM, AdLib, VGA, etc. Just forget about developing for the PC, pick a different platform, like the NES *(or later SNES and SegaMegaDrive)*, Amiga, etc. As impressive as the DOS game library is, most game developers obviously picked the fourth option, as the majority of of our beloved NES, SNES, Sega MegaDrive, PlayStation, etc. classics did **not** have PC ports. Before 1992, it seems that the majority of those who developed PC games went with the first option, then with the third option after 1992. However, even if you were developing games exclusively for high-end machines at the time, you still had some issues, like SoundBlaster and the Gravis UltraSound being very different beasts with different APIs and so forth. So, you either had to write two separate code paths for them, or rely on a third-party proprietary library like the Miles Sound System. https://en.wikipedia.org/wiki/Miles_Sound_System Then, in the mid-to-late 90s, 3D acceleration became a thing. At first, all these 3D GPUs supported only their own proprietary APIs: the S3 Virge had SGL, the ATI 3D Rage had the ATICIF, the 3dfx Voodoo had Glide, etc. Eventually, as DOS was replaced by Windows, these proprietary APIs were replaced by OpenGL and Direct3D, but even those two had - and continue to have - their own problems. Not to mention, even in spite of this standardization, not every GPU conforms to the standards perfectly, with a game that works fine just fine on a machine with an Nvidia GPU having glitches on a machine with an AMD GPU, and vice versa. https://read.cash/@Geri/software-rendering-is-better-than-directx-or-opengl-d94704d6 https://www.youtube.com/watch?v=3fmvxgCVzhY This situation is still so bad, that my friend Geri just decided to abandon hardware acceleration altogether, and focus on software rendering. Even by the late 90s, you would have faced a problem similar to the one I described for the DOS-era - while the PlayStation, Nintendo 64 and Sega Dreamcast all had well-known and widely documented 3D rendering capabilities, while on the PC, you may have had OpenGL and Direct3D support by the late 90s, but you also still had some video cards that lacked 3D acceleration capabilities altogether, so your best bet would have been to support Direct3D/OpenGL and also write a fallback software renderer - which is precisely what Quake 2 and Unreal did. The Myth of Compatibility So, in this article, I made the claim multiple times, that the IBM PC's greatest strength and weakness is its modular nature, the fact that you can simply replace and upgrade your CPU, your RAM, your sound card, your video card, etc. However, I have a confession to make - I wasn't being completely honest when I made that claim. Can you take an original IBM PC from 1981, replace its 8086 CPU with an Intel Core i7? **Nope!** They're not pin-compatible! You can't even put a recently manufactured memory module into it either! Hell, you can't even put a 15-year old computer's DDR2 RAM module into a modern computer either, which expects a DDR5, and the two are not compatible. Modern PCs also lack ISA slots, and instead rely on PCI. So obviously there is a limit to the forward-compatibility of PC motherboards, and you **do** have to replace the entire machine every generation or so. But wait, it gets even worse! The 86 *(5-10 MHz)*, 286 *(5-25 MHz)*, 386 *(12-40 MHz)*, 486 *(16-100 MHz)*, Pentium *(60-300 MHz)*, Celeron, Xeon, Intel Core i3/5/7, etc. all use the exactly same x86 architecture, right? Right?! **Nope!** You see, in addition to greater clock speeds *(and other improvements that reduced the amount of clock cycles needed to complete each instruction)*, these CPUs also introduced several.... ahem.... extensions to the instruction set. Each new Intel CPU implemented not just the same instructions as the previous CPU, but typically a few extensions as well, new instructions added to the x86 instruction set. If you wanted to squeeze out as much performance out of your new hardware as possible, you'd have to make use of these new features, but that would lock out those stuck with older hardware altogether. As a matter of fact, as we're quickly approaching the point where increasing clock cycles of CPUs is no longer sustainable, both hardware and software developers alike are shifting the focus on increased parallelization. However, this parallelization involves more than just increasing the number of CPU cores and hoping for the best - it also involves writing software that benefits from the increased number of CPU cores, by making use of multi-threading. As an alternative - or as a way to further squeeze out even more parallel performance - it also involves implementing new CPU instructions *(and software that makes use of said instructions)* that can perform the same operation on multiple instances of data at the same time: Single Instruction Multiple Data.... or SIMD. https://en.wikipedia.org/wiki/SIMD On the x86, this parallelization first became widespread in 1999, when the Pentium 3 came implementing the first iteration of the Streaming SIMD Extensions for the x86 architecture. Truth be told, it was preceeded by the introduction of MMX in 1997, but MMX did not have the same impact as SSE. SSE was eventually superseded by the Advanced Vector Extensions - or AVX - which first appeared in 2008. As of 2021, the latest of these instruction set extensions is the AVX-512, which first appeared in 2015 - however, CPUs actually implementing it are rather expensive, and your CPU probably doesn't support it either. Typically, PC games don't even bother to have multiple code paths for CPUs that may or may not support different iterations of SSE or AVX. They typically just went with SSE2 before between 2005 and 2016, and then AVX2 after 2016ish. Final Thoughts The PC is a mess. It's real difficult mess to develop for. But it's a beautiful mess none the less. Yes, it can be painful to program, but the beauty of it, is that it gives every consumer a unique experience. This was even more true in the DOS-era, where sequenced music reigned supreme, and MIDI music had a different sound on every sound card. However, I still think that we really went down the wrong track when we decided to add yet another replaceable part to the stack in the mid-90s with the invention of 3D-accelerated GPUs. It has been prophesied, that GPUs are going to disappear, and instead, we'll have CPUs with hundreds of cores running a software renderer, and people won't be able to tell the difference between that, and having a GPU. And you know what? **We should have went down that route from the very start.** https://blog.codinghorror.com/i-happen-to-like-heroic-coding/ Everyone would have benefited. SIMD extensions would have been adopted faster. Multithreaded programming would have been adopted faster. Programmers would have started writing programs that scale for multiple CPU cores much earlier. All that money spent on GPUs would have been spent on stronger and faster CPUs that can do more than just make video games pretty. Software rendering could have driven the need for faster CPUs with more cores and software that scales better. **I said it before, and I'll say it again:** **GPUs were a mistake****.** **It was never meant to be.** https://read.cash/@Metalhead33/hardware-acceleration-was-a-mistake-dade7306 https://read.cash/@Geri/software-rendering-is-better-than-directx-or-opengl-d94704d6#the-problem-is-the-3d-hardware-rendering-itself Or maybe the x86 itself was a mistake? It scales poorly. Maybe it's time to replace it with something different? Something fresh? Something that scales better for multithreaded programming?

+2 more

@Metalhead33

Financing Universal Basic Income II: Social Costs of Jobs Previously, I wrote about the financiability of Universal Basic Income, but I neglected to mention several variables within my calculations. https://read.cash/@Metalhead33/financing-universal-basic-income-526e9d22 So, before I introduce the difficult-to-quantify variable that could turn all the calculations within my aforementioned article upside down, I'll go on a long, but very much necessary segue. Social costs Because printing money has an ugly tendency to cause inflation *(I'll get to that later)*, the government's preferred source of revenue is from taxes, that is beyond any reasonable doubt. When the government provides you something *"for* *free**"*, it means that the government is paying for it - but because the government's main source of revenue is taxpayer money, it means that **you** are paying for it, albeit indirectly. https://read.cash/@Metalhead33/is-it-free-02a3c432 Hell, even printing money is sort of a form of taxation - when the government prints money, the total supply of money increases *(no ****, Sherlock)*, but the supply of goods and services said money can be exchanged for doesn't change. This means that the value of money decreases. Everything gets more expensive. If you found a loophole that allowed you to legally counterfeit money, you wouldn't be making yourself any wealthier by printing money - you'd be simply making everyone poorer, decreasing their buying power in relation to you. This means that inflation could be easily seen as a form of taxation. Thus, when the government is paying for something, society is paying for that thing. However, society is paying for more than just what the government is spending their taxpayer money on. These extra, non-governmental *(or indirectly governmental, as the government controls the money supply, thus it somehow trickles down from the government to all the other anyway)* expenditures are difficult to quantify, but they are the variable that could turn the whole calculation upside down. Society as a whole - that is, the individuals who make up society - directly spends money on housing, transportation *(the vehicles and fuel necessary for transportation)*, food, water, electricity, clothing, various other consumer items, and indirectly on the various tools, machinery, factories and human labour that goes into making said consumer items. Which is to say, that society as a whole spends money on human labour that creates value, either directly *(via buying said value from the private vendor)*, or indirectly via the government as a proxy *(albeit mostly only under Socialist systems)*. However, there seem to be certain costs that seem.... out of place, don't you think? On bull**** jobs When it comes to the usefulness of a job to wider society, it's not a binary value, but a continuous spectrum. On one end of the spectrum, we have so-called *"hero jobs"*, jobs that must be done to prevent society from collapsing *(or regressing to an earlier, more primitive stage)*, jobs that are essential. On the other end of the spectrum, we have bull**** jobs: jobs that don't create any value, and very often in fact destroy value. Jobs that are not only unnecessary, but sometimes even harmful to society. And in between these two extremes, we have jobs that produce nonessential goods and services that are nice to have, but we could easily live without if **** hit the fan. https://www.strike.coop/bullshit-jobs/ On the more heroic side of the spectrum, we have police officers *(gotta catch the criminals)*, military *(gotta defend the borders to prevent foreign invasions)*, farmers *(gotta produce food)*, truckers *(gotta transport said food to those who live in cities)*, construction workers *(someone's gotta build your shelter that protects you from the elements)*, doctors and nurses. On the more bull**** side of the spectrum, we have HR/PR departments who do absolutely nothing that benefits the consumer, middle-management honchos whose only job is to make the lives of their underlings as miserable as possible, marketing people whose job is to advertise already existing products *(thus adding absolutely nothing of value)*, etc. If money is supposed to represent human labour *(by proxy, as it can be exchanged for goods and services:fruits of human labour)*, then what warrants the high salaries of people who don't create value? No, I am **not** a Communist - I'm **not** talking about the CEO of the company, the man who paid for all the tools that his workers use, the man who has to shoulder the risks by being in the businesses: he's rightfully entitled to the profits of his business ventures. I'm talking about the HR/PR, marketing and middle-management guys. The ones who aren't involves in the creation of actual value - not even indirectly - yet are paid far more, than the ones that **are** involved. How are these people paid? Obviously, their salary comes from the company's profits. But where does said profit come from? From the price of the goods and services the company is selling, of course! Which is to say, that everything is more expensive than it should be, because more money has to be made in order for the company to be able to afford those useless bottom-feeders whose jobs have nothing to do with the production of said products. By the time the company starts filming the commercials or starts paying for the Internet ads, the products are already on the store shelves, thus their prices are already set. **Society itself is indirectly paying the salaries of people whose jobs don't benefit society at all.** Is this not welfare with extra steps? Bull**** jobs as welfare Under a true Capitalist system, you create value either directly *(e.g. by being a blacksmith who smiths horseshoes)* or by proxy *(by being the CEO of a company that produces horseshoes, thus paying for all the equipment required for the production of the horseshoes, hiring and paying the blacksmiths and then handling the distribution of the horseshoes)*, and you receive monetary rewards for having created said value, in the form of people buying what you produced. Under such a system, an income has the perquisite of creating value of some kind: a good or service for which there is a demand. But we don't live in a truly Capitalist system. We live in a system in which an increasing number of people are being paid to do pretend-work, to do *"work"* that doesn't create any value. At best, that work is convincing you to buy a product that was already made and already has its price set, which means that the value has been already created, and the marketing-guy added nothing to it. Because their job doesn't involve creating any real value, their salary only exists by the grace of their employer, whose own salary equals the profits of his company, the sum of money consumers are willing to pay for the products the company produces - **ergo, us consumers are indirectly paying all these useless bottom-feeders.** **Thus, a person who does a bull**** job is basically a welfare-recipient, just with less free time. Bull**** jobs are welfare with extra steps.** It reminds me of Communist Hungary, where the government essentially hid unemployment by simply making up fake jobs *(most of which involved spying on your coworkers for the government)* just to artificially reduce unemployment to zero *(it was illegal to be unemployed in Communist Hungary)* - only for this hidden unemployment to become real, visible unemployment once Communism ended. However, you can't really call it truly Socialist either, as even being alive requires you to pay up regularly, and the government's *"charity"* doesn't extend to the idle poor. If employment is to be considered a human relationship, it's a particularly coerced and non-consensual one, as you are required to be in such a relationship just to remain alive *(unless you're really lucky)*, which is not exactly my idea of consent. Unless you're already rich, you basically have two choices: the starvation or the plantation. Which is basically slavery with extra steps. The system we live under is a horrific, dystopian Frankenstein economy that combines the worst of both worlds, while having the benefits of either. What about UBI? If you read my article about UBI, then you know that the article came to the sad conclusion that in three out of the four countries I examined, UBI would be **more** expensive than the existing social security programs, not less, even under the assumption that most of the cost of social security is lost to administrative overhead. https://read.cash/@Metalhead33/financing-universal-basic-income-526e9d22 However, that article left out the variable of bull**** jobs, which might as well be counted into welfare, but aren't. What if the salary of every marketing-person, HR/PR person, middle-management boomer, etc. was calculated into the welfare expenditures of a country? Sure, the government isn't paying them, but the very same taxpayers ~~the government likes to milk~~ who fund the government's welfare programs are also indirectly paying these bottom-feeders one way or another. Sadly, the number is difficult to quantify, as it would take literal years of research to find out how much does the existence of these jobs cost to society as a whole, but I dare to say that if we found the number, and counted it in welfare costs, then suddenly, UBI would end up being the cheaper option compared to welfare. But who will do all the work? It's a common argument against Universal Basic Income that if we were all given enough money to survive without having to work for it, we'd all become lazy Okay, so bull**** jobs exist, and society would be better off without those jobs. But there re still many essential jobs that need to be done to keep society running, and also plenty of jobs that produce goods and services that may be nonessential, but are definitely nice to have, and are often taken for granted in this day and age. What about them? Well, first of all, a job's usefulness to society is independent from its resistance to automation. Truckers are absolutely necessary for the functioning of any society where the locations of production are scattered all over the place, often far away from the places where the consumers actually live. Yet truckers **can** be automated away: self-driving cars are already a thing. and **WILL** replace truckers, taxi-drivers, etc. So, first and foremost, automation is the answer. It absolutely has to come together with UBI. https://www.youtube.com/watch?v=7Pq-S557XQU The real reason why we as a society are so afraid of automating away millions of jobs, is because of our culture and economical system. On the cultural part, so many people make their jobs the focal points of their identity, and would be utterly devastated, if it was gone. On the economical side, we live in a work-or-die system, thus without UBI, automation is a no-go. Plus, replacing workers with robots - even if the robots are more effective - would reduce the amount of people whose incomes can be taxed - the government would have to tax companies instead, who would simply leave the country as a result. So it's a chicken-and-egg problem, a catch-22, where we need to make the great leap forward to bypass the problem. Second of all, if we got rid of useless jobs that don't involve creating value - David Graeber argues that those jobs make up over half of the workforce - we could do all sorts of wonderful things even without the assumption of UBI. We could effectively halve the yearly work hours people have to do, and reallocate the workforce into actually meaningful jobs. https://www.strike.coop/bullshit-jobs/ Third of all, the human desire to be a productive member of society is criminally underestimated by the anti-UBI naysayers. Humans are social creatures, who value a nice neighborhood, who value nice and trash-free streets, who value good food, etc. If suddenly liberated from the coercive need to sell one third of their entire lifespan for not starving to death, they'd probably find ways to willingly contribute to society, if not out of a desire to improve things, then out of sheer boredom. **Who knows what could people - liberated from forced labour, full of energy - could do for society?** Fourth of all, UBI could trigger the return of seasonal work. People could become temporary farm hands during planting seasons. Having a large number of unemployed people would be a godsend to any organization that relies on volunteers. Fifth of all, if we implemented UBI and automated away all the jobs that we could, those essentially jobs still done by humans would become more valued. They'd finally get the payment and social recognition they'd deserve. Conclusion In conclusion, rather than merely measuring the cost of UBI against the cost of existing officially published government expenditures, it might be smarter to measure the cost of UBI against the social cost of not just government expenditures, but everything else that we pay for, even though we don't benefit from. A lot of jobs are basically welfare with extra steps. A lot of jobs involve pretending to work, rather than actually working. **Why should these people be paid more than however much you intend UBI to be?**

@Metalhead33

Maturity *"A man learns until his death."* - old Hungarian saying. Why so immature? **In** spirit of that saying, I spent my entire life questioning everything I was taught, by parents, by school, by society, by everything. One of those things that I question the most is the concept of *"maturity"*. Why? Because to me, it seems extremely arbitrary, vague and nonsensical. My family's - or more precisely, my mother's and sister's -interpretation of the word *"maturity"* is basically a very small set of behaviors that are deemed acceptable to them, based on their own skewed perception of what society deems acceptable and what people care about. From my point of view, childhood is being obsessed with maturity, and adulthood is no longer caring, and just doing whatever you want, because you're an adult. Not so much from the point of view of my mother and my sister, to whom adulthood means refraining from a large list of completely harmless arbitrary things, like sitting down on the grass in your own garden or backyard. Apparently, sitting down onto the ground is somehow so childish that the neighbour will report you to the police for it, and you have to squat to pet your cat, or something. Sure, I admit it, I'm a rather immature person. My childhood lasted twenty-eight years *(during which I was denied agency at every turn, and constantly chastized for every little faux pas)*, and this left me with my childish desires and childish refusal to be broken in. In fact, the nine-ish months made me see their version of *"maturity"* for what it truly is: Stockholm-syndrome. Being resolved and resigned. Having accepted suffering, and no longer even having the energy to dream of something better. No imagination. No. Maturity shouldn't mean that. Maturity shouldn't be synonymous with being a zombie or an NPC, with being assimilated into a dystopian culture that seeks to completely eliminate individuality. Maturity should mean first of all, two things: Not giving a ****, because you're an adult, no longer a child who has to fear *"What will mommy think?".* You're an adult. You don't have a mommy. Your children might have a grandma, but you don't have a *"mommy"*. Being able to fend for yourself if left to your own devices. Admittedly, I am lacking in both departments, especially the latter. But my mother and my sister are lacking in the first department, especially given their highly skewed and irrational perception of what is socially unacceptable, what people will even remember, or what will make people think of you negatively. It may just be me being an autist who can't put himself into other people's shoes, but I definitely wouldn't care at all about someone sitting down on the grass in their own backyard, or showing weird sheep signals. Ideologies According to my mother, being an adult and being *"mature"* also has to include being apolitical and being an apathetic nihilist who has given up on all hope of society ever improving in any measurable way. I told her exactly what I think about work: that I hate it, and think that wageslavery ought to be abolished *(or at the very least, the 40-hour work week needs to be lowered to 24 hours)*, that I advocate for Universal Basic Income, etc. She called me delusional. Luckily, there are millions of people out there on the Internet telling me that I'm not. She'd have me believe that I am the one and only weirdo who believes in those ideas, yet here are a million people on the Internet informing me, that I'm not the only one. What does a mature person do, according to my mother? According to her, the mature person gives up all of his/her ambitions to escape wageslavery, even if alternatives to it are very much real. Freelancing? Founding your own business? Investing in crypto? Nah, don't try any of those, they only works out for one out of a million! A real mature person accepts that he/she is one of the 999 999 others, and gives up before even trying! What's up with this really weird interpretation of maturity? Is maturity supposed to be synonymous with misery, or what? Luckily, there are a million people on the Internet saying the truth: no. Social Construct? Maturity is just as much a social construct as the concept of childhood, or rather, its modern interpretation. Yes, age is very much real, and the reality of infants being unable to fend for themselves when left to their own devices is rooted in biology, but the idea of making everyone whose age *(that is, years that have passed since the day of their birth)* is below an arbitrary number like 18 a member of a precious protected class is a very modern idea. In the olden days, pretty much everyone over the age of 13 was, for all intents and purposes an adult, and in pre-modern societies, people started working at the age of 8, not 18, let alone 28 like me. The idea that some things - namely, having fun, enjoying things - should be forbidden to adults is an idea that is rooted in old puritanical culture. The even more nonsensical idea that adulthood is somehow synonymous with conformity is rooted in jealousy towards people with creativity, pre-emptive jealousy towards people who may achieve *(financial)* success and/or happiness by trying out alternative lifestyles, and the typical *"If my cow dies, the neighbour's cow must also die"* mentality. What's the lesson? The lesson is to remove toxic people from your life who twist the meaning of maturity in order to keep you down, even when it doesn't benefit them. Sadly, I lack the financial means to leave my home so I can't remove my family from my life, and arguably, I'm probably still not prepared for doing everything completely on my own. Or maybe I am, and I just don't know it yet - after all, humans are born survivors, and I'm sure that my survival instincts would kick in. The biggest lesson here, is to just do what is right for you, and remove the toxic people who want to chastize you for the smallest of arbitrary things. To be an adult is to be no longer bound by society's expectations of maturity and childishness. Real maturity is the embracing of your inner child. Fake maturity is its paranoid suppression, always resulting in misery. Don't be afraid to have passions, dreams, hobbies, ambitions and ideas. Life is not worth living in the shadows of arbitrary and outdated social norms.

@Metalhead33

Financing Universal Basic Income It's Monday evening. I had a four-day weekend, because I took Friday off *(for a college exam)*, and today was an official, government-mandated holiday in Hungary, celebrating Pentecost. Tomorrow, the nightmare *(work)* begins again, so what better way to cope with the dread, then to write about a well-known alternative to wageslavery? As everyone who reads my articles should know, I am a big advocate for Universal Basic Income. If I'm being intellectually honest, my main motivation is for supporting it is not necessarily because of economical concerns about automation *(though,* *they do play a part**)*, but a desire to return to my old life, from before I was a wageslave - or even just have a bit of a breathing room to take a break from life and re-evaluate what I want to do and what my options are. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4 Still, to put an end to that two-paragraph segue, I am here to explore how financeable Universal Basic Income is, and how financeable is one of its alternatives. So, I am going to present four countries, with three different evaluations. All of these will be made under the assumption, that only citizens with voting rights will be eligible for the Basic Income. Let's go! The United Kingdom I am starting with the UK, because this whole article was inspired by a recent discussion with a friend of mine, who is a Hungarian living in the United Kingdoms, Scotland to be specific. He doesn't support UBI, or rather, doesn't support any UBI, that is below £1500 per month. So I did some calculations. The UK has a population of 67.8 million people, but only 47 million registered voters. Thus, I'll go with the 47 million figure, since the earlier number also includes children and non-citizens, neither of which have the right to vote. To give £1000 per month to 47 million people would cost a yearly £564 billion per year. £1500 would increase it to £846 billion. £2000 would increase it to £1.128 trillion. Just for comparison, in 2020, the expenditure of the UK government was £928 billion, and with a revenue of £873 billion, this resulted in a deficit of £55 billion. Of this revenue, roughly 37% came from personal income taxes, *(roughly £323 billion)*, while out of those £928 billion... £256 billion were spent on social programs £166 billion on healthcare £103 billion on education £52 billion on military £43 billion on debt payments £37 billion on transport £35 billion on public order and safety £34 billion on personal social services £32 billion on housing and the environment £25 billion on agriculture and industry £58 billion on everything else With a deficit of £55 billion, it's obvious, that even a modest UBI of £1000/month - when just added into the budget without deducing from anything else - would increase the deficit from £55 billion to £619 billion. However, if we throw out social programs altogether, deficit would increase to just £328 billion year. If we threw out healthcare and education too, that would just be £60 billion. We can nullify the deficit altogether halving military spending *(60 billion -> 34 billion)*, halving the spending on *"everything else" (34 billion -> 5 billion)* and reducing the transport spending by 5 billion. Alternatively, the government could print money. While I could not find precisely how many British Pounds are in circulation worldwide, I know for a fact that as of the time of writing the article, each British pound can exchanged for 1.42 USD, and there are $1.2 trillion floating around the globe. Thus, my assumption is that there are around £845 billion in circulation. This means that if the aforementioned UBI was financed by printing money, it would create a hyperinflation of 66.74% per year. Sheesh! That's a far cry from the current 1.4% yearly inflation, which implies the UK government printing just £11 million per year. Thus, another problem is that we can't count on government revenue being £873 billion - out of that£323 billion comes from personal income taxes, and if UBI is implemented, people likely won't be paying those taxes anymore. WIth UBI being a thing, many people would quit their jobs, thus no longer having taxable income. Thus, it is arguably more realistic to budget with £550 billion in mind, which is a smaller number than our £564 billion per year. Even if government spending was nullified in every other area *(even military and police)*, the government would still be in a 16 billion pounds per year deficit without personal income taxes. So, as much as it pains for a *"taxation is theft"* libertarian like me to say, personal income tax would have to stay, at least for people who make at least twice as the UBI. It's likely that to make financing UBI realistic, pretty much every spending - besides police and military - would have to be nulled, turning the UK into a night-watchman state. However, the British people are too statist for that. https://en.wikipedia.org/wiki/Night-watchman_state Speaking of Night-Watchman state.... What if, instead of a UBI, we just abolished personal income taxes? Reducing the yearly budget by £323 billion means that unless we reduce spending on things, we just added £323 billion to the yearly deficit, which was already £55 billion. Reducing that £373 billion would require completely cutting social services *(£256 billion)*, personal social services *(£34 billion)*, and halving healthcare spending *(£166 billion -> £83 billion).* However, there is still one question that could change everything: corruption. How much money is lost to corruption, to embezzlement each year? How much money is actually spent on what it is intended to be spent on? Yes, 256 billion pounds are spent each year on welfare, but a lot of that cost is eaten up by administrative overhead, or the salary of people involved giving out the welfare. I reckon, that if corruption was reduced, the government was downsized and all those kleptocratic bottom-feeders were fired, financing UBI would not be an issue. The Netherlands I have two Dutch friends who advocate for UBI, so it'd be interesting to explore UBI in the Netherlands. The Netherlands have 16 171 304 people eligible to vote. In 2021, the Dutch government's income was €293 billion *(or $357.46 billion)*, while expenditures were €336.6 billion *(or $410.652 billion)*. Out it, €97.8 billion was spent on social programs and welfare, €86.7 billion was spent on healthcare, €40 billion was spent on education. If every Dutch citizen eligible to vote was given €500 per month, that would cost the Dutch government €97.02 billion per year, or $118.3644 billion. If it was €1000 per month instead, it would be €194.04 billion per year, or $236.7288 per year. What's surprising, is that this €97.02 billion per year is actually **lower** than the cost of social programs and welfare, so a UBI of €500 for every Dutch citizen eligible to vote would be **cheaper** than welfare. But if we wanted to give them €1000 instead, obviously, corners would have to be cut in other places, such as healthcare and education. Out of the Dutch budget €293 billion - it seems that only €64 billion, so if we kept the UBI at a modest €500 per month, we could also eradicate personal income tax too, and would only have to slightly cut down on healthcare and education to compensate. Overall, the Netherlands seem very promising for implement UBI. The United States Out of 328.8 million Americans, roughly 245 million have the right to vote. Andrew Yang intended to give all of them 1000$ per month. How much would that cost? Well, $245 billion per month, obviously. Per year, that would be $2940 billion, or $2.94 trillion, which is certainly no drop in the bucket compared to the $4.4 trillion federal budget expenditures of 2019 - especially with a revenue of $3.5 trillion, resulting in a deficit of $984 billion. In contrast, the sum of the budgets of all US states was just $1935.30 billion. Out of the aforementioned $4.4 trillion, roughly $1 trillion went to social security; $1.1 trillion went to a combination market subsidies, medicare and medicaid; $697 billion went to military spending, $361 went on social safety not programs; $375 billion on debt payments, and the rest *($867 billion)* on various public services. Can we squeeze UBI Into it? Well, sure, but if we don't want to increase deficit any further, then only at the expense of social security, medicare, medicaid, market subsidies, safety-net programs, halving the military spending, and cutting down a little on some public services. And that still leaves us with the same deficit as before. Okay, so how do we even finance the federal budget in the US? Well, half of it comes from personal income tax *(so around $1.75 trillion)*, 7% from corporate tax *($245 billion dollars)*, 36% from social insurance tax *($1.26 trillion)*. Under the assumption that many will just stop working and no longer have a taxable income if UBI happens, the worst-case scenario is having to account for the loss of $1.75 trillion missing from the US budget, which just wouldn't be possible even under the aforementioned cuts to the budget. And no, we can't just increase corporate tax, as that'll just make all the corporations leave the US. But what if instead of implementing UBI, we simply abolished federal income tax? Then we'd have to also cut $1.75 trillion from the US budget, which would most likely mean at least halving military spending, halving social security, halving medicare, and heavily cutting down on public services. And that would still leave us with our original 2019 federal budget deficit of $984 billion. And no, Communists, we can't just *"tax the rich"*. They'll simply leave the country, and then you'll have no one to tax. Hungary I am Hungarian, so I couldn't finish this without exploring the possibility of UBI in my own country. First of all, there 8.312 million people qualified to vote in the Hungarian elections, but those also include Hungarians living abroad, so I am limiting it only to the 7.933 million out of those who actually live in Hungary. With a UBI of 100 000 HUF *(roughly 350 USD)* per month, that would be a spending of 9519.6 billion HUF *(33.3186 billion USD)*. With a UBI of 200 000 HUF *(700 USD)* per year, that would be double that - 19039.2 billion HUF *(66.6372 billion USD)* per year. In 2017, the total expenditure of the Hungarian state was 15400 billion HUF *(53.9 USD)*, of which 20.10% *(3095.4 billion HUF = 10.8339 billion USD)* was spent on pension 11.10% *(1709.4 billion HUF = 5.9829 billion USD)* was spent on other welfare programs 10.50% *(1617 billion HUF = 5.6595 billion USD)* was spent on education 9.60% *(1478.4 billion HUF = 5.1744 billion USD)* was spent on banks and law courts 9.40% *(1447.6 billion HUF = 5.0666 billion USD)* was spent on various other economical activities 9.00% *(1386 billion HUF = 4.851 billion USD)* was spent on transportation and communication 7.10% *(1093.4 billion HUF = 3.8269 billion USD)* was spent on healthcare 6.60% *(1016.4 billion HUF = 3.5574 billion USD)* was spent on foreign debt 5.00% *(770 billion HUF = 2.695 billion USD)* was spent on law enforcement *(police)* 2.30% *(354.2 billion HUF = 1.2397 billion USD)* was spent on wildlife management 2.10% *(323.4 billion HUF = 1.1319 billion USD)* was spent on cultural activities 2.00% *(308 billion HUF = 1.078 billion USD)* was spent on military 1.40% *(215.6 billion HUF = 0.7546 billion USD)* was spent on housing 0.80% *(123.2 billion HUF = 0.4312 billion USD)* was spent on sports 0.80% *(123.2 billion HUF = 0.4312 billion USD)* was spent on environmental defense 0.80% *(123.2 billion HUF = 0.4312 billion USD)* was spent on energy. industry and mining 1.40% *(215.6 billion HUF = 0.7546 billion USD)* was spent on everything else (source) https://www.parlament.hu/irom40/10377/adatok/fejezetek/00szemleltetes.pdf Sure, a UBI of 100 000 HUF for 7.933 citizens eligible for vote - 9519.6 billion HUF *(33.3186 billion USD)* - wouldn't be a drop in the bucket. It would have to be counterbalanced by removing the removing the pension *(3095.4 billion HUF),* other welfare spending *(1709.4 billion HUF),* education *(1617 billion HUF),* transportation *(1386 billion HUF),* healthcare *(1093.4 billion HUF), cultural activities (323.4 billion HUF), sports (123.2 billion HUF)* and halving the money spent on wildlife management *(354.2 billion HUF -> 177.1 billion HUF).* However, if you ask me, it would be worth it. As a healthy and ablebodied individual who is already an adult but not old enough to be retired, I don't benefit from pension or any other welfare, education or healthcare, have no interest in sports or cultural activities, etc. So I'd be perfectly willing to make that sacrifice for no longer having to work. In 2017, the Hungarian state's income was 13400 billion HUF, which is roughly *(46.9 billion USD).* Out of that, just 13.3% was personal income tax, which is basically 1782.2 billion HUF, or 6.2377 billion USD. If, instead of having UBI, we sought to eliminate personal income tax, all we'd have to do would be to simply get rid of welfare spending and the spending on sports. Or education and sports. However, it is, once again, important to note: the Hungarian government is very corrupt and a lot of money gets lost in the cracks. Thus, UBI would still be a better idea than welfare. Final Verdict Out of all the four countries I explored, it seems that the Netherlands has the biggest potential for the implementation of Universal Basic Income, as it's one example of a country, where UBI could be potentially cheaper than welfare, and it would take minimal effort to balance the budget and eliminate deficits. In Hungary and the United States, UBI would be doable, but would have to come at the expense of existing social programs, and would only be possible under a Libertarian government *(quite ironic, isn't it?)*, and that's not even accounting for the loss of taxable income, which would have to be compensated somehow. The hardest case seems to be the United Kingdom, where UBI would almost completely throw government spending out of whack. The biggest problem is that UBI would come with the unintended side effect of depriving the state of a lot of personal income tax revenue, as many people - such as myself - would simply quit their jobs if UBI was implemented. Even if we completely erased welfare programs and cut down on spending in other areas too, this loss of tax revenue would still have to be compensated by something, whether an increase in sales taxes / value-added taxes *(making everything more expensive)*, a progressive taxation scheme targeting the rich *(which would simply make the rich leave the country)*, increased corporate tax *(corporations would simply leave)* or anything else. Alternatively, the government could simply run everything on a for-profit basis. But that would mean that we'd be working towards two conflicting goals: reducing government spending, while simultaneously growing government-controlled industry to generate profit to finance UBI. The government could also privatize everything and tax those privatized industries, but that would drive the cost of healthcare into the sky. Despite all this, I remain adamant that UBI is the way of the future. Not just because I'm lazy and hate work, but also because automation is inevitably going to drive out the workforce. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4 Final Final Verdict? Maybe the solution wouldn't be wealth redistribution, but collectively owned robots?

+2 more

@Metalhead33

Sunday Evening Musings: Education and Employment Every Sunday evening, I hold a funeral for the two days of freedom, peace and quiet that I had the privilege of possessing. I hold a funeral for the privilege I am about to lose for five days. And I think to myself - *"When will the nightmare end?"* https://read.cash/@Metalhead33/sunday-evening-rant-my-miserable-existence-dba2d582 There was that one video... But that is not the only thing I think about. I have an inner dialogue about various other subjects as well, some related to my predicament, some not. One of those thoughts are about a video I once watched. https://www.youtube.com/watch?v=Omx5KrRVkMc In that aforementioned video - in case you don't have three minutes to watch it, and would rather read me summing it up for you - we are essentially told that education comes *(or rather, historically came)* in many shapes, and school is merely one of them. As the video says, the fact that we think of *"school"* and *"education"* as synonyms - when they're really not - betrays that we have a very impoverished notion of what education is and could be. It's a great video, and I still rewatch it from time to time. When I first watched it, I was still in *(high)* school, and was absolutely miserable for some reason, so I was looking for anti-school material to prove to myself that I wasn't the only one, and that I'm not crazy. I guess that's the strength of the Internet: your family may tell you that you're crazy and delusional, but you'll easily find a community counting literal millions of members telling you that you're not. Obviously, my life has changed a lot since I first saw that video. In college, I was having fun, so I mostly forgot about the anti-school stuff. The most dramatic change took place on the 26th of June, 2020, when I ceased being a student, and got dragged into the world of work, kicking and screaming. My mindset didn't change. Just like the Frankfurt School, I was always a complainer and a critic. After growing miserable enough at work, I became a critic of the very concept of work. To prove to myself that I'm not crazy, that I'm not the only one, I dug into anti-work materials. I read The Abolition of Work by Bob Black. I read The Iron Heel by Jack London. Formerly a skeptic, I became virulently pro-UBI - not because I genuinely consider it a sustainable solution, but because I desperately need a long break from life as a whole, so I can have some breathing room, re-think my life and find out what can I really do. https://read.cash/@Metalhead33/sunday-evening-rant-my-miserable-existence-db6b6736#26th-of-june-2020-rainclouds-gather-around-my-brains https://en.wikipedia.org/wiki/The_Abolition_of_Work https://en.wikipedia.org/wiki/The_Iron_Heel https://en.wikipedia.org/wiki/Universal_basic_income As of the time of writing this article, one month ago, I registered on read.cash, deciding to publish all my anti-work thoughts, and maybe earn a little bit of *(crypto)*currency in the process. Regardless of how much do my articles make, putting those thoughts out and getting them off my chest makes me feel good. If someone gets inspired by them, all the better. But then I remembered the video I mentioned first. And I did a little bit of thinking about it. And do you know what kind of conclusion did I come to? That the video could also be used to describe the modern workplace as well. https://www.youtube.com/watch?v=Omx5KrRVkMc *"What we need to realize is that education and schooling are not the same thing. In fact, because we think that schooling is the same thing as education, we have a radically impoverished idea of what education is and could be."* - Quote from the video These two sentences struck me the most. But, what struct me now, is that if you change a couple of words, it still makes perfect sense.... *"What we need to realize is that work and (full-time) employment are not the same thing. In fact, because we think that employment is the same thing as work, we have a radically impoverished idea of what work is and could be."* Starting from next week, my country's government is no longer recommending employers to prefer home office and remote work. This means that tomorrow, I could receive a phone call, a demand to get back into the office. And do you know what will I do, if they demand my return to the office? If they won't let me to continue to work from home, I will simply resign. I'll quit. Upon hearing this, my mother immediately started screeching. *"What will you do?! What will you do?!"* - she howled at me with a heavy worry. To her, *"to work"* means *"to be (full-time) employed by a company (or the government)"*. The typical Hungarians' notion of work is rooted in Feudalism - *"Which feudal lord are you toiling the fields for?"*. But I am part of a new generation, and if I am forced to leave behind my day job, I will look for alternatives. Online work, freelancing, even just helping people for a meager payment. But that's not the main point that I'm trying to make. The main point that I'm trying to make, is that **work isn't the same as employment, especially not full-time employment**. As a matter of fact, work isn't even always monetized. People aren't getting paid for writing Wikipedia articles, yet it would be nonsensical not to consider it work. Volunteer work is exactly what it says on the tin: work, just not for profit, and definitely not for a company. Freelance work is for profit, but you're not getting tied to a company that probably considers you replaceable anyway. Owning a farm, living off the grid and producing your own food is work. Making a freeware game for donations is also work. The point that I'm trying to make, is that we need to get away from this outdated feudal idea of employment. Work comes in many shapes and forms. Full-time employment is an enslaved poor man's version of work. Schrödinger's Wageslave Anyone who has ever read my articles knows that I'm an advocate for Universal Basic Income, even though there are still a few remnants of skepticism within me. https://read.cash/@Metalhead33/how-deep-goes-the-rabbit-hole-592fe62b#funny-money Still, whenever I run into naysayers who oppose UBI, they always come at me with two inherently contradictory statements. In the very same breath, they tell me *"Lol, you are just lazy, everyone else likes working, get in line already lol"* **and** *"NOOOO, we cannot implement UBI, everyone hates working, everyone would quit their jobs, we can't do this!"*. This causes me to facepalm. Can they not see the contradiction? In the very same breath, they tell me, that everyone likes working, yet at the same time, everyone also hates working and would definitely quit their jobs if UBI was implemented. Which one is it? Then it turns out, the two statements aren't as contradictory as I thought. Remember what I said about us having a radically impoverished idea of what work is and could be? Well, I had the same radically impoverished idea in mind. However, when these naysayers refer to *"work"*, they use two separate meanings of the word in those two sentences. *"People like working"* - Well, yes, if by *"work"*, you mean engagement in productive labour, then sure. Who doesn't like making things? Granted, by this logic, playing Minecraft could be considered work, but that's not here or there... Still, this statement **is** true, albeit only under certain specific conditions. People love being engaged in productive labour, but only when it's not forced labour. *"People hate working"* - Also true, if by *"work"*, you mean full-time employment, working for the same company for years, doing the same thing over and over 8 hours straight 5 days a week, etc. The two seemingly contradictory sentences no longer contradict each other, if you clear up with the object of each sentence means: and in this case, *"work"* refers to two separate things in the two statements. People like being engaged in productive labour *(albeit only under certain specific conditions, like doing it out of their free will rather than being held at a gunpoint by the economy)*, and people hate having jobs and being full-time employees. I had a discussion briefly touching this subject with my friend Geri, and he told me - *"What you actually hate isn't work, but (wage)slavery. Making a freeware game for donations* ***is*** *work. What you hate isn't work. What you hate is employment."* Sure, these weren't his exact works, but this was the gist of what he said. Maybe he's right. Maybe what I actually hate isn't work itself, but jobs, employment, and all the things that come with them. I **do** enjoy programming - what I **don't** enjoy is being forced to do it 8 hours straight 5 days a week, communicating with people I can't stand, waking up early, not having control over my time, not having other options, etc. And I think the same thing applies to a lot of other people. There are plenty of people out there willing to volunteer to make society better. Github is home to millions of open-source projects. Sometimes, even the companies I like to rip on generously release their APIs and libraries to the public domain. Plenty of games out there downloadable for free. Plenty of mods for games that put even established game developers to shame. Not to mention, all the other examples I mentioned earlier in the article, like those who write Wikipedia articles. There are jobs that no one wants to do, for sure. Perhaps those jobs should be automated away? And if that's not possible, then those who do those kinds of jobs should be valued: paid better, and given more free time. Education What better way to finish off this article, than to connect the two divergent lines of thoughts back together? And thus, I return to the topic of **education**. As the video mentioned, education came in many ways, shapes and forms before 1806, not just Prussian-style schools as we know them. As the video said, education ought to take place not just at a certain stage of your life, but during your entire life. And sure enough, alternative forms of education have become available thanks to the Internet. Covid-19 gave a boost to these alternative forms of education. Yet in many countries - including mine - potential employers remain stubbornly obsessed with certifications put out by schools, especially diplomas. I'd be willing to say *"their loss"*, but in a world where your survival depends on your being employed - unless you're very lucky, or very unlucky *(disabled)* - that cannot be said. The employers are like feudal overlods, and they expect their employees to be loyal vassals, thankful for the opportunity to sell away 1/3 of their lives as part-time slaves for money. We live in a dystopian world that refuses to get with the times. So, as closing thoughts: People don't hate education by default, people actually love to learn - school makes them hate education and learning People don't hate work by default, people actually love to engage in productive labour - jobs, employment and forced labour make them hate working and producing. Making things compulsory makes people hate those things. People hated reading school-assigned books in high school, even though they didn't mind reading in other contexts. I wouldn't mind cleaning up the streets, picking up the trash, watering all the flowers in the graveyard and planting trees, if it was voluntary and my survival didn't depend on it. I wouldn't mind making the world a better place by contributing to open source projects either, if I had the time *(if I wasn't a wageslave)*. I'd likely would have read plenty of books on my own, if I wasn't forced to read them in school. **School isn't education - it's the enemy of education** **Employment isn't work - it's the enemy of work.**

+5 more

@Metalhead33

Society When it comes to attitudes towards the pandemic and the resulting lockdowns, there are three kinds of people. There are those who can't wait for things to *"go back to normal"*, to go back to the way things were before the pandemic, before the lockdowns. There are those who share this desire with the former, but are cynical/realistic enough to have resolved themselves to the fact, that the pre-pandemic world is gone forever, that even if the lockdowns are lifted, the damage has been already done, with millions of small businesses destroyed forever, millions unemployed and stuck in poverty through no fault of their own. *(**is that necessarily a bad thing tho?**)* https://read.cash/@Metalhead33/is-economical-prosperity-hurting-you-and-your-causes-949fe1ac#human-suffering-as-a-catalyst-for-positive-change Then there's me. People like me **don't** want the pre-pandemic world to return. We don't want to go back to the office. We don't to go back to being forced to do in person what can be easily done remotely. I like to think of myself as *"enlightened"*, but I'm probably just lazy and spoiled. Sadly, society only goes one way. If the economy re-opens, they'll likely demand my return to the office, and ignore the cries of those who would rather not. Because society is sadly one-size-fits-all. A society - we live in it ***"We live in a society."*** **-** This phrase is essentially a meme. It simultaneously carries a lot of meaning, and also no meaning at all. it effectively means whatever you want it to mean. https://www.youtube.com/watch?v=LHhbdXCzt_A To most young people- or should I say, to millenials and zoomers -, the aforementioned *quote/meme* conjures up thoughts about all the negative traits of the society we are living in, about the hypocrisy and double standards that our society holds, about the blatant injustices that everyone just pretends not to see, about the sheer absurdity of mass culture and the norms. But that wasn't always so. The word *"society"* didn't always carry such negative connotations. So what made the word "society" effectively become a swearword, a slur in the vocabulary of people my age and people younger than me? Connected, yet disconnected Human society as we know it is a*(n il)*logical paradox, because the abundance of something does not necessarily mean the absence of its lack. Abundance of food is mutually exclusive with a shortage of food, right? So, abundance of people to potentially socialize with must be mutually exclusive with a shortage of people to socialize with, right? Not really. When you ask someone about what might be the best visual representation of solitude and loneliness, people will conjure up images of recluse hermits, heychast monks and individual who are surrounded by literally nothing. But this visual image of loneliness is far from the reality of loneliness. This visual image implies, that loneliness is the absence of people to socialize with, a lack of people. In reality, the more people you are surrounded with, the lonelier you can get. A tight-knit community of villagers or cœnobite monks will have its members constantly socializing with others members of the community, whereas someone who lives in the city will be constantly feeling lonely, despite immediately being surrounded by a gazillion people as soon as they step outside the house. Technology has made the world more interconnected than ever, yet people at the same time are feeling lonelier than ever. To be fair, I can't blame them. If you're a monk or a pre-modern farmer, your brothers are always there for you - meanwhile, if you're a modern-day urban wageslave, everyone you see on the streets is in the hurry, either going to work, or going home from work, or maybe going shopping. They have no time to stop and talk to you about how nice the weather is. Everyone's in a rush to make ends meet, and once ends are met, they're too tired to really socialize - and when they aren't, thanks to the pandemic, most of that socialization is going to take place digitally, meaning no oxytocin for you - not that I'm complaining of course, since I'm ultra-introverted and welcome the fact that doing things in person have been rendered optional, but still... Another thing that plays a role is the fact that families get separated. Children go to school, which obviously gotta be 50 kilometres away from home. Parents go to ~~wageslavery~~ work, which also gotta be 50 kilometres away from home. Both parents and children go home exhausted. There is just no time or energy to spend on the more positive aspects of **society**. Once a near-everyday event *(okay, just weekly)*, festivals have been reduced to rare event that takes place maybe two or three times a year at best, and are hollow shadows of their former selves: they're shallow rituals that serve as a reminder of a much better past, a past where we used to live in a close-knit community, rather than emotionally isolated from each other, despite being in physical proximity. Rather than being beautiful events that bring society together, people nowadays think of these festivals as either quick cash-grabs by merchants, or as lame excuses to put the overzealous geeks of the school on display for every other kids' parents to see. Just like vacations, they're just short breaks from the world of work. When the festives are over, it's back to the regular grind. Thus, this leaves people - especially younger people like me - being far more aware of and fixated on the negative aspects of **society**: work, duties, compulsions, taxes, unwanted interactions with people we'd rather not exist, etc. And obviously, all the negative aspects of our norms, of our culture, all the double standards that society as a whole abides by. Return to...? Normally, my articles propose solutions to the problems I mention. Me proposing either Universal Basic Income or the replacement of the government with Anarcho-Capitalism as a magical cure-all to all of society's ills are essentially a running gag by this point. But truth be told, I don't see how these problems could be solved, at least, not without creating bigger problems. Cities are a problem. Humans were just not designed to live cramped in ugly glass cubes surrounded by a concrete jungle, and a bunch of alien strangers whose names you don't even know. This kind of existence makes people lonely and fills them with paranoia, a lack of trust for their neighbours. But the city-problem just cannot be solved. Cities, despite being soul-crushing and completely antithetical to human nature, are the most effective way to store people. If everyone moved to the countryside, and everyone lived in nice little single-story houses with gardens rather than apartments, we'd run out of space pretty quickly. Land used for farming would have to be used for human habitation. Not ideal. Work, as we know it, is also a problem, but I already wrote several articles about that, so I don't want to waste any words on that, except that maybe reducing work hours and increasing days of leave would give people more room for socialization outside work. Sure, you can still go to a café or a bar to socialize with some people *(who may or may not be complete strangers)*, but it's at the expense of time spent doing whatever you want to do at home, like watching a movie, as well as time spent with your family. As I stated in a previous article, I don't put my trust into government-sponsored initiatives. Just like how I wouldn't trust a government-sponsored *"save our villages"* initiative, I wouldn't trust a government-sponsored *"save our society"* or *"revive our festivals"* initiative either. https://read.cash/@Metalhead33/a-different-point-of-view-df73f690#is-there-a-solution In the aforementioned article, I talked about the Amish, about how they're not rejecting technology, they're just rejecting anything that will make them dependent on outsiders. **But that's just half of the story:** the Amish also reject things that will serve to divide their community. If a piece of technology serves to separate a member from his community, they won't permit it for individual ownership. As usual, there is a lesson to be learned from the Amish: they are a very close-knit community that focuses on the internal rather than the external. They eschew anything that makes them depend on outsiders or divide their community. As much as I love technology, love computers and disagree with hippies on everything politically, I often think, that it would be better to live in a hippie commune, than within mainstream society. Which is to say, that an earlier stage of development of human society is arguably far more desirable to live in, than the present state, and most likely future states as well. Maybe the solution really is to **return to monke**.

+4 more

@Metalhead33

A National Cryptocurrency Exchange? Cryptocurrency was invented specifically to be a decentralized form of currency, independent of banks and their underhanded trickery, as well as governments and their neverending desire to tax you into oblivion. So, the idea of a national cryptocurrency seems like an inherent oxymoron. Wouldn't it defeat the whole purpose of cryptocurrency? **Not necessarily.** A government-issued cryptocurrency needs not to compete with the likes of Bitcoin, Litecoin, Dogecoin or Etherum. That's a fruitless endeavour - people generally consider government-provided alternatives to anything to be *"cringe"* and *"lame"*. Instead, a government-issued cryptocurrency ought to be a stablecoin similar to USDT. Because I'm Hungarian, let's call it HunCoin, and peg it to the Hungarian forint. Assuming that you are not Hungarian, just mentally replace the Hungarian forint with your own country's national currency. https://en.wikipedia.org/wiki/Stablecoin https://en.wikipedia.org/wiki/Tether_(cryptocurrency) https://en.wikipedia.org/wiki/Hungarian_forint What kind of purpose would such a currency serve? Well, what kind of purpose does USDT serve? Enter the **Hungarian National Cryptocurrency Exchange**. A National Cryptocurrency Exchange? What kind of purpose does USDT serve? It serves basically one purpose: as a base unit of exchange on cryptocurrency exchanges. On Probit, the main unit of exchange of USDT - you can sell your cryptocurrencies for USDT, you can buy your cryptocurrencies using USDT, and the net worth of all your combined cryptocurrency wallets is measured in USDT. Likewise, this Hungarian National Cryptocurrency Exchange would function the same, just with HunCoin instead of USDT *(and obviously, would allow users to buy HunCoins using Hungarian Forints, and sell their HunCoins for Hungarian Forints)*. The main advantage of such an institution would be trustability - while I personally don't trust the government *(as I am very anti-statist)*, many people do, and would likely be far more enticed to get into cryptocurrency, if a government institution promised to protect their wallets, rather than a private company that can go bankrupt and defunct any time. The third important feature of such an institution would be **loans and debts**. Arguably, one of the major weaknesses of cryptocurrency - an inherent disadvantage of its decentralization, I would say - is the lack of in-built support loaning cryptocurrency and handling debts in cryptocurrency. If you loan someone a given amount of sh!tcoins, there is no guarantee that they'll give them back to you - and what are you going to do if they don't? Call the police? Ha! Better hire a bitcoin assassin! Thus, it would be imperative, that the Hungarian National Cryptocurrency Exchange would support this feature *(albeit only for Hungarian citizens with Hungarian residence, as we cannot enforce it on foreigners)*, and enforce debts, albeit with one major twist: when you'd loan someone, say, 100 Dogecoins *(at the time of writing this article, a single dogecoin costs 115 Hungarian Forints, and if HunCoins are pegged to Hungarian Forints, we'll assume that the rate of exchange is 1 Dogecoin to 115 HunCoins)*, what they'll actually owe you won't be 100 Dogecoins plus interest, but rather 11500 HunCoins plus interest *(albeit, you'll still be expected to pay it back in Dogecoins)*. Albeit this would make the shorting of cryptocurrency impossible, it would be straight-up ridiculous to expect people to settle their debts in currencies as volatile as the typical cryptocurrency. The fourth feature of this system would be this: at any place that accepts HunCoins as payment, the system would allow you to pay with any cryptocurrency that can be exchanged to and from HunCoins, automatically exchanging at the market price. Say, you want to buy a car that costs 10 million HunCoins, but you have no HunCoins? No problem - if you have 86 957 Dogecoins in your wallet, they'll automatically get exchanged for the 10 million HunCoins *(assuming an exchange rate of 115 HunCoins for each Dogecoin)*. Naturally, the Hungarian government would have to go out of its way to actively encourage the acceptance of HunCoins as an alternative to both cash and credit cards. But what kind of benefits would it entail to the parties? The government would obviously pocket all the transaction fees. If the transaction fee - or tax - was kept to a reasonable minimum, people would be enticed to use it. This system would provide a bridge between the regulated world of government monetary politics and the unregulated world of cryptocurrency. It would encourage the spread of cryptocurrency by encouraging its usage. This kind of system would encourage foreign investment as well, as foreigners would be able to just come here and shop in Hungary using their bitcoins, and other cryptocurrencies that can be exchanged to and from HunCoins. On a somewhat more unethical side of things *(granted, ever since I became a wageslave, I stopped caring about ethics and morals)*, if the Hungarian government took lessons from the Swiss, foreigners would be able to launder their money using this Hungarian system, which would obviously generate revenue for the Hungarian government. Last but not least, this system would strengthen the Hungarian Forint as well: if the system was succesfull, there would be a high demand for HunCoins, and since HunCoins are pegged to the Hungarian Forint, this would allow the HNC to drag the HUF up with it. But what would it take? First of all, it would require a political will. The current Hungarian government is made up of technologically illiterate boomers, so, they're never going to implement my idea. However, a hypothetical libertarian party *(which doesn't exist in Hungary)* - especially one more tolerant towards the grey market, even the black market - could definitely implement the idea. Second of all, it requires a non-greedy government, that won't put an unreasonable amount of transaction fee on the cryptocurrency. If they put a 27% tax on every single HunCoin transaction, no one is ever going to use it. Third, it requires a bit of an up-front investment: lots of Hungarian Forints need to be allocated to make sure that HNC and HUF can be exchanged for each other *(this could be mitigated by heavily encouraging the usage of HunCoins instead of Forints, and accepting them for paying taxes and paying for government services)*, and the Hungarian State Treasury will also have to buy up vast quantities of bitcoins and other cryptocurrencies to be able to have an initial stockpile to sell to citizens - either that, or invite people to move their crypto to the exchange wallets and sell some for HunCoins. However, under a wiser government - wiser than the current Hungarian government - the system would work, and turn Hungary into the Switzerland of cryptocurrency. This kind of system, would let both the government and us cryptocurrency-loving libertarians have our cake and eat it at the same time. If any government in the world implemented my idea, it could be the next step in an evolution of digital economics.

+2 more

@Metalhead33

How deep goes the rabbit hole? I have confession to make: I'm a man of conflicted views, so my ideology is not entirely coherent with itself, and contains contradictions. My very first article made solid arguments for UBI *(besides just me being lazy and spoiled)*, while in a later article, I played the devil's advocate and advocated against the UBI that I actually support. In another article, I explored alternative economical models. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4cee https://read.cash/@Metalhead33/is-it-free-2c942cee https://read.cash/@Metalhead33/a-different-point-of-view-df73f690 **But in all those three articles, I was merely scratching the surface.** You could say, that my very first article was the thesis, the later one was the antithesis, and this article will be the synthesis. https://en.wikipedia.org/wiki/Dialectic#Hegelian_dialectic Funny Money Remember when I was talking about printing money, and how it can lead to disasters? https://read.cash/@Metalhead33/is-it-free-2c942cee#money-printer-goes-brrr **To recap:** when you increase the supply of money, you decrease the value of money, at least when the amount of goods and services money can be exchanged for remains constant. If you could, hypothetically, get away with counterfeiting money, you wouldn't be making yourself any wealthier by printing money - you'd be simply making everyone else poorer *(thus, technically making you wealthier* ***in relation to them****)*, by reducing the purchasing power of the currency they hold. If you support Universal Basic Income like I do, but unlike me - who is still unsure how to go about implementing it - you are adamant to finance it by printing money... then you wouldn't be making anyone richer. At best, you'd be equalizing the amount of wealth held by people. **In other words: when you successfully counterfeit money, you're not getting any richer, everyone else is just getting poorer.** **Keep this fact in the top of your head, because the rest of this article assumes, that you are well aware of this fact and all its implications.** Money is only worth as much as the goods and services it can be exchanged to. But where did money even come from? In the beginning, there was barter This is basically elementary-level economical history at play: in the beginning, there was barter. We like to think that we are so much more sophisticated than our paleolithic caveman ancestors, but in truth, they were a lot more similar to us than we give them credit for: they engaged in trade, just like we do. Our paleolithic ancestors obviously had no concept of money, so what kind of trade did they engage in? Barter. I have something you want, you have something I want, so why not make a trade instead of resorting violence which wouldn't be beneficial to either of us? If there are only 200 kilograms of deer hide in a certain economical area, and only 10 kilograms of flint, then the rate of exchange is, in theory, 1 kilogram of flintstone to 20 kilograms of deer hide. In theory, that is - in practice, the economical actors of the time were not aware of this *(only through long centuries of habitual trade, would they have an approximation of the rate of exchange)*, so generally, they just traded a single spear's worth of flint for the hide of a deer, and so forth. But what if you have something that I want, but I don't have something that you'd like in exchange? For a long time, violence was the only solution, but fortunately, metallurgy came to the rescue - people liked useless shiny metals, so those became the main instruments of exchange. To be fair, shiny metal was not the only type of currency in the pre-modern world. Cocoa beans, bird feathers, shiny rocks, beads, ivory, even salt *(hence the word "salary")* were used as currency. As a matter of fact, it was mainly silver - not gold - that truly defined the **Age of** ***"Gold"***, but for the sake of simplicity, we'll be dealing with gold in this article. The Tale of the Goldsmith This is the tale I tell to everyone, when I wish to educate them on the evils of fractional reserve banking. https://en.wikipedia.org/wiki/Fractional-reserve_banking The tale goes like this: once upon a time, a long time ago, people used various forms of currencies, before they settled on useless shiny metals like gold *(technically, gold isn't useless today, because it's used to make rust-resistant wires for computers)*. As time passed on, these shiny metal currencies were standardized into the coins we all know and love today. But, coins had one fatal flaw: they were heavy and inconvenient to carry, and were susceptible to thievery. So, what did the goldsmith do? He came up with a brilliant idea: he opened a safehouse, where he stored people's money for them for a small fee. But how did people know how much money was there in the safe, and how could they prove themselves to the rightful owners of the money stored in the safe? The goldsmith came up with another great idea: IOUs. He handed all of his clients pieces of paper with his own signature, saying *"I owe you X* *golds**"*, and they could come back any time, to redeem their golds. That's right, the first paper money wasn't even a proper currency in its own right, but rather an IOU, a proxy for real gold. https://en.wikipedia.org/wiki/IOU https://youtu.be/3Nqgjs5wYnU?t=189 https://youtu.be/3Nqgjs5wYnU?t=329 For a while, this system worked. However, Mr. Goldsmith noticed something: people stopped using gold, and started using his IOUs as their preferred currency. It was far more convenient than using real gold, and as long as the goldsmith - who was becoming more like a banker at this point - was trusted and had a reputation for being honest and reliable, that paper was as good as gold. But greedy Goldsmith had a new idea up his sleeve: since no one ever came back to his bank to redeem their golds, there was technically nothing stopping him, from, say loaning out the golds deposited into his vault, or.... printing out fake IOUs for gold that wasn't even in his possession. https://youtu.be/3Nqgjs5wYnU?t=332 https://youtu.be/3Nqgjs5wYnU?t=335 What was the result of this? Well, first of all, inflation. Lots of it. But second of all, people got suspicious of Mr. Goldsmith's newfound great wealth, so they came back to the bank to redeem their gold. But, since Mr. Goldsmith was a big spender whose fake IOUs trickled down into the hands of many, he eventually ran out of gold to give back to the depositors. So he was lynched. But the Tale of the Goldsmith does not end with Mr. Goldsmith's death, because his legacy lived on, much to the dismay of people like me. Did the government ban this highly fraudulent practice of his? **No, they did not.** They legalized it, albeit with some regulations: for every single real golden coin, bankers were only allowed to cook up at most nine fake IOUs. And thus, fractional-reserve banking was born. https://en.wikipedia.org/wiki/Fractional-reserve_banking While the first private banks were vulnerable to bank runs - such as the demise of poor Mr. Goldsmith - after a while, the system developed some robust defense-systems to mitigate the problem, such as the government giving stimulus checks to banks at the risk of getting withdrawn into bankruptcy. https://en.wikipedia.org/wiki/Bank_run Revenge of the Government: Fiat Currency As I said before, the practice of fractional-reserve banking was legalized and regulated by the government, but money still had to be backed. Paper banknotes still had to have real gold backing them, they still had to be exchangeable to gold. But not for long. Throughout the 20th century, governments across the world abandoned the golden standard, and switched to fiat currency. Now, what is fiat currency? Fiat currency is currency that is not backed by anything, or more precisely, backed by the government's authority, which is a euphemism for being backed by gunpowder, bullets and the threat of jailtime. Fiat currency is money that's only worth something, when the government that pushed it into circulation is still around - once the government disappears, that *(paper)* money might as well be toilet paper. While the combination of the Gold Standard and fraction-reserve-banking still put a limit on the amount of new money governments could inject into the economy, fiat currency essentially gave the government full control to just print money and ask questions later. https://read.cash/@Metalhead33/is-it-free-2c942cee#money-printer-goes-brrr Under fiat currency, the government forces people to settle their debts to the government in the government's fiat currency. They demand that you accept their currency when settling debts, or else the debt that someone owes you is considered invalid by the government. They demand that you pay taxes in their currency *(tax evasion is punished with jail)*. They demand that you pay for government-provided services in their currency. But how do you acquire the government's currency? The simplest answer would be the government giving people stimulus checks, which is precisely what's happening in the USA at the time of writing this article *(Spring of 2021)*. But stimulus checks aren't everyday things, so another answer is this: when the government first prints money, the government pays its own employees with the currency, which said government employees use to buy goods and services from private vendors, who are forced to pay their debts *(taxes)* in the same currency. The government also uses this money to pay for contracts with private companies, who then use some of that money to pay other companies to do some of the work for them - the companies obviously use this money to pay their employees, who then some of it back to the government in the form of taxes. In other words, one way or another, that newly printed money eventually trickles down into the hands of private citizens.... and back into the hands of the government, reducing their need to print more, creating a perfect loop. To be honest, fiat currency is not really a new invention. It's not even a 20th century invention - Lincoln's Greenbacks in the 1860s were fiat money. While it was silently implied that the Tale of the Goldsmith took place in Late-Medieval or Early Modern Europe, it might as well have taken place in 7th century to 11th century China, because the aforementioned banks appeared in China during the 7th century, and the government took over said banks in the 11th century. By the 12th century, the Song dynasty in China ran out of gold, and simply suspended convertibility between the paper money and gold: China switched to a fully fiat currency as early as the 12th century, and the Mongols who conquered China *(Yuan dynasty)* adopted their currency, and even attempted to force it down the throats of all the other peoples - e.g. Persians, Turks and Russians - they conquered, ending in failure. https://en.wikipedia.org/wiki/Greenback_(1860s_money) Still, despite this little segue about China and the Mongols, the concept of fiat currency wouldn't conquer the world until after WW1, when governments suspend the convertibility of their banknotes to gold for private citizens, and finally to foreign governments as well in 1971. Backed by debt Earlier, I said that our fiat currency is basically backed the government's authority. However, that is only half of the story. Remember the Tale of the Goldsmith and fractional-reserve banking? I said earlier, that under fractional-reserve banking - at least, as long as the Gold Standard was active - for every real, gold-backed dollar, you could only cook up a maximum of nine fake dollars. But how do banks make money these days? If not by cooking up fake money that isn't backed by today's equivalent of gold, which by the logic of what I said before, should be the government's authority *(right?)*, then how? Banks basically have three sources of cash: government infusions, credit to the central bank *(loans)*, and deposits by depositors *(us depositors are basically* ***loaning*** *the bank money, and can withdraw at any time in theory - but if everyone wants to withdraw, that's a bank run, because of fractional-reserve banking)*. Because we have fractional-reserve banking, banks can loan out more money than they actually own, and if people try to run the bank, the central bank simply loans them money, or the government gives them stimulus checks. Because these days, people prefer using bank cards instead of cash, when you borrow money from a bank, and use said borrowed money to buy something, you are transferring debt from the bank you borrowed the money from, onto the bank that runs the bank account of the person you bought something from. Let's say, that you go the bank to borrow 10 000 dollars, to buy a car. You don't take out the cash. The bank simply says that it owes you 10 000 dollars *(even though in reality,* ***you*** *owe them... it's not complicated tho, since you're borrowing an* ***IOU****)*, and then, when you buy the car by transferring the money to the car seller's bank account, the debt is transferred. The bank no longer owes you money, but rather, it owes the other bank money *(of course, you still have to pay back what you borrowed)*. And under the previous 9:1 ration of fractional reserve banking, this second bank can keep 1 000 dollars, and loan out the remaining 9 000. When that debt gets transferred to a third bank, they can keep 900 dollars, and loan out 8 100 dollars. And every time that happens, new money is created, for all intents and purposes, like a Matryoshka doll. https://en.wikipedia.org/wiki/Matryoshka_doll Under this system, from an initial deposit of 10 000 dollars of real, government-issued cash, roughly 90 000 banknotes can be cooked up, assuming that we keep depositing the newly created debt-money into a different bank every time, instead of withdrawing the cash. Unless someone withdraws the money, it's a closed loop: bank credit created somewhere, becomes a deposited at another bank, and so on, and so on. And if you think that's crazy, here's this: the reserve to loan ratio isn't even 1:9 anymore in most places, but rather 1:20 or even 1:33. Then there are some loopholes within the banking system, that allow banks to completely bypass the fractional reserve requirements. But us, the borrowers, still have to sign a pledge to offer up certain goods, in case we fail to pay, such as our houses. Fail to pay mortgage? Your house gets taken away. Essentially, money is backed by your debt, and should you refuse to pay your debt... **But in a nutshell, banks can create as much money, as we can borrow. Money as we know it in the 21th century, is created from debt. Debt equals money. Money equals debt.** Government-created money accounts for less than 5% of the money in circulation. 95% of the money is basically bank credits. Digital. Just numbers in a computer database. In the real world, if you need a hammer at the moment, someone's promise to give you one in the future isn't much of a help. **But in the artificial world of banking, the bank's written promise to give you X amount of money, is treated as money, and we accept it as such**. They loan you that promise, which you then spend on something, but still have to pay back. **This means, that for all intents and purposes, banks create money out of thin air**. This is the real reason why everyone is in debt. Governments, corporations, private individuals, even banks. How can everyone have so much debt? Because debt equals money. Because money is backed by debt. People say that *"if there was no debt, there would be more money"*, but in reality, if we made debt disappear, there would be **no money**. People might say *"we need to pay our national debt, then we can finally invest our money into things!"*, or *"I need to pay off my student loan debt, then I can finally spend my money on other things"* - this statement might apply to a single individual, or a single organization, but if applied to the whole system, it would mean the end of money. Have you ever wondered how can there be that money to be lent out? Well, now you know - there isn't. Banks don't lend money. They create it out of thin air out of debt. Feed my Frankenstein! https://www.youtube.com/watch?v=NO2cHJmDkBg The aforementioned system requires constant growth to sustain itself. It's a closed loop, basically: the economy required renewed bank credits to continue to function, but newly created money needs to be backed by something of a value to avoid hyperinflation, which means that new things have to be produced even if no one is there to buy them. When the bank creates the money out of thin air - out of debt - and loans it to you, you have to pay it back with interest. But do you get the interest, if 95% of all money was also created from loan? Well, you work for someone who pays you, so you can pay back your interest - but that someone also needs credit to pay you, and most likely, that credit is also borrowed from a bank. And thus, debts are never repaid - everyone remains in debt at the end of the day. More debt is generated every day, and because debt equals money, more things of value have to be created to prevent hyperinflation. The economy just has to keep growing. It cannot stop. **It must not stop.** But it must stop. We cannot keep growing eternally on a finite Earth - or to paraphrase Greta Thunberg, we cannot keep believing in *"fairy tales of eternal economic growth".* And it does stop from time to time. And when that happens, the economy crashes. It's not an anomaly within the system - it is part of it by design. Since we cannot keep growing eternally on a finite Earth, the system needs to reset from time to time, and that happens in the form of great recessions, like the 1929 Great Depression, Black Monday *(1987)*, the Great Recession of 2008, or even the Covid-19-induced 2020-2021 Recession. This fatal reset mechanism is what makes this unsustainable system sustainable-ish. And every time that happens, people feel the pain. A lot of it. https://read.cash/@Metalhead33/is-economical-prosperity-hurting-you-and-your-causes-949fe1ac#damned-if-you-do-damned-if-you-don039t And this is also why I am forced to be a wageslave, even though the economy was doing fine back when I was a deadweight on it. We produce so much food, that 2/3 of it gets wasted. Robots are going to render human labour obsolete in the near future. Yet I have to keep working. I have to keep generating more debt for the company that employs me. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-bb043111 Are there ANY alternatives? Cryptocurrency seems like a viable alternative to government-issued fiat currency. It's basically Agorism in action. There are no scummy banks involved, and the government can't inflate it either. Cryptocurrency is backed by the computing power required for mining *(which is to say, for recording transactions)*, rather than debt, the government's authority, or any finite resource like gold or silver. https://en.wikipedia.org/wiki/Agorism However, I have to state: fiat currency in itself is not necessarily evil. Yes, it can be abused, but in theory, it could be used for good too: printing money when the economy is growing, burning money when the economy is contracting, effectively controlling the money supply to scale with the economy. Obviously, in reality, that never happens, and governments just keep inflating the currency even when there is zero economical growth, and the banks will refuse to let go of their power - thus, it is clear that fractional-reserve banking has to go. The idea that you can just loan someone money that isn't even in your possession *(or worse yet: doesn't even exist)* is just absurd, and needs to go. Some people advocate for a return to gold, appealing to its long history. However, gold can be debased or shaved, and not to mention, because of the finite supply, as long as loaning with interest is tolerated, we're going to eventually end up with someone owning all the gold, while everyone else will just have no money at all. Do we want that? Some people advocate for a system, in which the government would create its fiat currency not as debt, but as value *(effectively backed by whatever they are spending the newly created money on)*, and inflation would be prevented by collecting and burning the excess money - again, sounds good on paper, but the bankers would never let go of their power, and would never let go of the juicy national debts. The aforementioned cryptocurrency would take the government out of the equation, and allow self-governing and self-sustaining small communities to share a common currency. Free-market anarchists would love it. https://en.wikipedia.org/wiki/Free-market_anarchism In a sustainable system, people would only breed as much as it is necessary to sustain the population - rather than grow it endlessly - and focus on recyclable production. People wouldn't be forced to work needlessly - thus, if robots can do everything, humans would enjoy UBI. But what do you think? What should we replace fractional-reserve banking and fiat currency with?

+10 more

@Metalhead33

Is it free? ****Warning!**** **This article was written by me a very long time ago, and no longer accurately reflects my views! Reader discretion is advised.** All of my more observant readers already know, that I don't think very highly of the concept of wealth redistribution *(unless it's UBI, which is awesome)*. But why is that? Well, today, I will make my final arguments against wealth redistribution, and even act as the devil's advocate even against my own love for UBI. 1. Somebody has to pay In real life, nothing ever comes for free. This is not a law of Capitalism, this is a law of the physical universe we live in. You may do someone a favour for free, or hand them over an object for free, but doing so costs you energy to do so. It was merely your own choice not to monetize said energy. However, ultimately, you need to acquire certain resources - such as food and water - to survive, and a location to shield yourself from the elements *(namely, a house)* doesn't hurt either. However, the production of said resources and the construction of houses both consume other resources and energy - energy and resources that are, in fact, monetized. Thus, money is required to survive in this world. This is not a *"Capitalist"* thing - price tags were attached to food and water even in the Soviet Union. So, what happens when the state gives you certain things - namely, education and healthcare - for *"free"*? Well, I'd hate to break it to you, but it's not really free - the government pays for it on your behalf, the government employs people to provide those services and resources to the populace, and just like any other employer, the government has to pay these people, so that they may buy food, water, shelter and other amenities. But how does the government finance it? How does the government pay all these people to provide you various services for *"free"*? Taxes That's the simplest answer. The government has to pay for all the things it gives you for *"free"*, and how does the government finance itself? Taxes. In most countries, the lion's share of the government's income will come from **personal income tax**, which is to say, that the government will slice off part of your income every month. There are other kinds of taxes, and I'll get into them later. When the government gives you something for *"free"*, ultimately, you are still paying for it in the form of taxes. The higher income you have, the more you are paying for a service you may not even need *(or can acquire a higher-quality equivalent of via a private company)*. This is the truth even when the country has a flat tax *(you pay a certain percentage of your income)* - but some countries get extra-punitive via progressive taxation, which basically puts people into categories based on their level of income, and adjusts the percentage they have to pay accordingly. If we assume that a single doctor can provide healthcare to around 300 people, then in a country with a population of 10 million people, there have to be 33333 doctors. If doctors are given a rather high salary of 4000$ USD per month, then that's 133 332 000 USD per month. If we assume, that out of the 10 million, there are 4 million people who work for anyone other than the state, and the average monthly income is a modest 820 USD with a flat 20% tax *(thus, the actual income is 650 USD)*, then the state's monthly tax revenue is roughly 656 000 000. More than enough to pay for the doctors. But what if the state's budget is already full? What if that 656 million dollars is already spent on other things, and the state just cannot spare? Well, in that case, taxes have to be increased by 20.325% - to a whopping 40.325%! -, which reduces the average individual's monthly net salary from 650 USD to ~490 USD. Sheesh! That's almost half of your monthly income taken away by the statist mafia! *(and all of that under the very naive the assumption that all that taxpayer money is spent on good causes, rather than just corruption, and luxury goods for politicians)* That's 160 less dollars the average citizen can spend per month - and remember, the citizens still need to spend money on food, water, rent, electricity, Internet connection, clothes, maintenance *(of their car, electric appliances, etc)*, etc. And I know, Socialists are all ready to burst out screaming *"WE'LL JUST TAX THE RICH, HÖ-HÖ..."* - yeah, but.... you do realize, that the rich will simply find loopholes to avoid taxation? Jeff Bezos didn't pay **any** taxes in 2020. That's why they're rich in the first place - because they're smart. If you are smart enough to become so rich, you so are also smart enough to outsmart the government. And if by some miracle, you'll just plug those loopholes, guess what - the rich will simply leave your country for some tax havens, and show you the middle finger. If we want to just give people *"free money" (Universal Basic Income)*, well.... for 10 million people, giving each 500 USD per month would cost the government 5 000 000 000 dollars per month. How the hell are we going to finance that? Even at an unrealistic 100% taxation rate, we can only squeeze out from people as much as they make - and if the 4 million non-governmental workers each only make 820 USD per month, then we can't squeeze out any more than 3 280 000 000 *(and that still leaves them with no money at all)*, still having a deficit of 1 720 000 000. Obviously, personal income tax is not the way to go, but we're not ready to discuss the alternative means of taxation, not yet. Okay, so, you may want to provide free things to people without having to drastically reduce the income of the ordinary citizenry - but how? Money printer goes brrr! If the government literally creates the money supply, why not just print money, right? So, let's return to our doctors. We need 133 332 000 USD per month to finance our free healthcare. At an extortionate tax rate of 20%, we're already squeezing 656 000 000 USD out of all the non-governmental workers per month, and these people already feel over-taxed. You don't want to raise the taxes, because that could lead to social unrest. What to do then? Just print money, right? Add 133 332 000 USD to the money supply every month! Currently, there are roughly 1.5 trillion *(1 500 000 000 000)* USD in circulation in the world. Adding 133 332 000 increases the money supply by 0.008%. This may seem like a drop in the bucket first, but remember - we just reduced the value of the US dollar by 0.008%. Why? How? Well, think about it for a money. What is the use of money? Money in itself is just toilet paper. The real value in money is the goods and services it can exchanged to. Especially food and water. If the supply of money increases while the supply of wheat, stone or iron remains constant, then the latter three things become more expensive. So, it stands to reason, that if you simply increase the money supply by 0.008% ceteris paribus, everything is going to get 0.008% more expensive, because you increased the supply of money with increasing the supply of things it can be exchanged to - you simply did not create value. https://en.wikipedia.org/wiki/Ceteris_paribus If we go with my beloved UBI - 500$ for 10 million people per month requires a budget of 5 000 000 000 USD per month, and if we just print it, then we increase the money supply by 0.333% every month. That may seem like a drop in the bucket, but if we keep doing this, every year, we'll increase the money supply by 4%. Every year, everything will get 4% more expensive. If the UBI is 1000$ per month, then, for 10 million people, that's a 8% inflation every year. If we want a UBI of 1000$ for all 328.2 million Americans, then that's 262.56% inflation each year, which is almost hyperinflation *(21.88% monthly inflation - hyperinflation starts at 50% monthly inflation)*. https://en.wikipedia.org/wiki/Hyperinflation In Hungary, giving a monthly 100 000 HUF to every citizen who has the right to vote - every 8 025 372 of them -would cost 802 537 200 000 HUF per month. Financing it with printing money would devalue the Hungarian forint by 12.738% every month, or by 152.86% every year. Ouch! Imagine if everything got twice as expensive every year. It would drive you mad, wouldn't it? At one point, everything would become so expensive, that no one would be able to afford anything. People would resort to looting and violence. But what if you just artificially deflated the currency every now and then? Just collect money and burn it! Light a big bonfire! Simple, right? Yeah, except... how do you acquire the money to burn in order to deflate the currency? Again, taxation. In order to remove money from circulation, you have to take said money away from people *(which is coercion and extortion)* - which is taxation. That is not to say that inflation is necessarily evil - when the economy is growing, you do want to increase the money supply, but when it isn't, it 's generally a very bad idea. Alternative Taxation? Personal Income Tax is not the only kind of tax that exists. Just to name a few alternatives: **Wealth tax****:** the Ancient Romans and Greeks didn't have personal income tax - instead, state actors would go measure it the worth of your property, and collect protection money from you depending on your estimated wealth. https://en.wikipedia.org/wiki/Wealth_tax If your goal is to reduce wealth inequality, sure, this sounds good on paper, but the ultra-rich will simply bribe the inspectors or invest their wealth into things the government has no insight *(e.g. cryptocurrency)*, so the bulk of the tax burden will still fall on the poor. What if you simply have no income, but a ton of wealth? Is that state just going to chip away your wealth bite-by-bite? That's unfair. Even worse for me, who wants to become idle rich at one point. **Consumption tax****:** Instead of having your income taxed, your consumption is taxed, which is to say, you indirecetly pay taxes after everything you buy. How? Well, they'll just tax the sale of every good and service, and the sellers simply integrate this tax into their prices *(e.g. 20% sales tax -> everything gets 20% more expensive)*, and you're basically indirectly paying this tax whenever you buy these goods and services. https://en.wikipedia.org/wiki/Consumption_tax Honestly, unless you discriminate between different goods and services *(which adds administrative overhead)*, you're nowhere better off than with personal income tax. However, if you **do** discriminate between different goods, you may create a system where people aren't forced to pay for services they're not using *(e.g. paying for roads by taxing the sale of cars and gasoline)*. **Value-added tax****:** Basically consumption tax with extra steps. Next! https://en.wikipedia.org/wiki/Value-added_tax **Inheritance tax:** The worst idea ever. People's main motivation for working hard is to make it easier for their descendants. Inheritance tax - taxing inheritance - takes away that motivation. Also, it's ineffective. You're not going to finance hospitals, doctors or UBI from inheritance tax. **Property tax:** Basically, a modern version of the wealth tax. **Poll tax****:** A truly fixed-tax, even beyond the flat tax. https://en.wikipedia.org/wiki/Tax_per_head https://en.wikipedia.org/wiki/Fixed_tax How is this any better than a flat tax though? It's like reverse-UBI. Everyone pays 500$ each month, regardless of income! **Transaction tax:** Taxing every financial transaction, is basically makes it consumption tax with extra steps *(extending it not just to the selling of goods and services, but every financial transaction of any kind)*. Just... why? **Rent tax:** Taxing the income of landlords. Sounds good on paper, except landlords will simply integrate that tax into their prices, raising rent. So you are nowhere better off. **X-Tax:** Alcohol-tax, fat-tax, etc. are all just consumption tax with extra steps. Robot-tax *(**because robots are going to take our jobs**)* is basically just personal income tax with extra steps, but applied to corporations that employ robots. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4 I don't see how any of these could serve as an alternative to personal income, unless you put consumption tax on luxury products *(but who gets to define what a luxury product is?)*. The truth is, the rich will always find ways to avoid - and so will you, if you are smart. Which drives me to the second point... 2. Power corrupts I identify as a Libertarian, or even borderline-Anarchist. Unlike other Libertarians, I do so not because I am triggered by the very thought of subsidizing the healthcare of my fellow citizen, but because I don't want to be killed for *"wrongthink"*, don't want my speech censored, don't want my privacy invaded, don't want to be imprisoned for doing something that brings no harm to anyone, etc. I believe in Freedom of Speech, Freedom of Expression, and the right to own firearms *(as a defense against government tyranny)*. The biggest problem with taxation is that if the government has the power to take away your hard-earned cash and gunpoint *(yes, it's at gunpoint - if tax evasion is a crime that warrants imprisonment, then taxation* ***is*** *at gunpoint)*, then they sure also have the power to censor the hell out of my speech and any art I may like, to force me to do certain other things against my will, to curtail my liberties in various other areas. This makes leftist politics inherently authoritarian. If robots are going to take our jobs, does that make technology inherently authoritarian? https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4 Sure, authoritarianism is not necessarily censure, or even particularly intrusive. Hypothetically, you **could** have a saint-like dictator who respects people's privacy, respects Freedom of Expression, allows citizens to own guns, etc. But due to how human nature works, the chances of that are exceedingly low. When put into the position of dictator, most people would very quickly turn corrupt and tyrannical. At best, they'd get nepotistic and put their incompetent friends into positions of power - at worst, they'd basically have anyone who ever wronged them killed, and start censoring everything that offends them. And this, is ultimately why, I consider taxation to be theft. It is government-sanctioned theft, enforced by state-sponsored goons. When someone robs you, hopefully, that is the last time you've seen the robber - but when the government taxes you, you will meet the government again next *year/month*. 3. A necessary evil? I think not. It's easy to take the antisocial route and say *"I owe society nothing"*, but in that case, society owes you nothing either. Part of what makes us human is our care for the weaker members of our society, even if they are dragging us down. No one wants to see their sick and wounded kinsmen and friends die - the whole rationale behind the socialization any kind of service is the extension of this kind of familiar solidarity to the whole of the nation. However, this solidarity is artificial. As of 2021, most of us are more isolated than ever, and couldn't care less about our neighbours - at least, I personally don't care about my fellow citizens, as long as I am forced to be a wageslave and work. No one cares about me either. Maybe, if we had UBI, I would care more and voluntarily clean up the streets and do other kinds of charitable stuff. The fact of the matter is, the government is objectively inferior at providing any kind of service. Private corporations can provide the very same services - e.g. healthcare -, for a cheaper price and at a higher quality. However, I am still lazy and spoiled, and still recognize the necessity of UBI due to technology making human labour obsolete in the near future, so here be my compromise: reduce government spending to only UBI, while privatizing everything. Finance it by taxing those who employ robots, and maybe those whose income exceeds the amount of UBI by a certain multiplier, e.g. three times the amount *(in other words: no personal income tax if your income is below the UBI multiplied by 3)*. This tax would exclude investments of any kind, which would encourage citizens to invest in stocks, and democratize the ownership of means of production via peaceful, non-violent and **capitalistic** means. When the aforementioned taxes are not enough, some money can be printed, but currency must be deflated every now and then, and the money to be destroyed should be levied from the main beneficiaries of inflation. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4 Unfortunately, as long as civilization exists, and we don't intend to let the majority starve to death at any time just because technology made their labour and skills obsolete, society can only be maintained by coercion and extortion. However, it should be minimized. I consider UBI to be the lesser evil compared to financing services you may not even need - it's better to just receive the money and decide yourself what you want to spend it on. But that is not the main point. The main point is that.... **Free anything isn't free.** *"Free"* education isn't free - **YOU** are paying for it with your taxes. Many consider UBI "unfair" for forcing the workers to finance those who refuse to work - but is it any more unfair, than taxing people to finance a service they don't get to enjoy any of the benefits of? I'm a working adult, *"free"* education won't benefit me in any shape or form. I'm relatively healthy *(if we disregard my obesity and sedentary lifestyle)*, so I won't be making any visits to the hospital any time soon either. I'd rather be just given money directly, so I can save up and.... yeah, I already went there in my previous article. Read that one.

+8 more

@Metalhead33

Sunday Evening Rant: My miserable existence ***DISCLAMER:*** *This article is not like my others. This is the first - and possibly last - time I lay down the contents of my mind to all my readers. This is going to get depressing, cringeworthy, or both. Still, writing it all down and letting it out makes me feel somewhat better.* *This is my story, and it doesn't have a happy ending. Actually, it doesn't have an ending at all, since I'm still alive, and it'll only end once I die, but that's not here or there...* Every Sunday evening, before I am about to take a shower, I take a deep breath. *"The nightmare begins again"* - I think. Every Sunday evening, I am filled with existential dread, when the realization hits me on the head: I am about to experience the psychological terror of being a working man, for five days again, before I'll get another two days of brief relief, and the cycle will continue. Every night, I go to bed questioning how long will my sanity last, how long till I snap, and do the unthinkable - whatever that may entail *(I leave that up to your imagination)*. I dread the moment work has to start. Anyone who reads my articles knows, that my hatred towards the very concept of work is difficult to underestimate. I also dread the moment - that I hope may never come - when they'll ask me to come back to the office. When I first started working, I immediately resented the commutes turning my de-jure 8-hour workdays into de-facto 12-hour workdays. When I was finally allowed to have home office *- God bless Covid-19 -* I still resented the fixed working hours, literally 9 to 5. Luckily, eventually, they allowed software developers like me to do dynamic working hours. I still resent having to do a minimum of 8 hours every day, overtime sometimes being expected but never being compensated. Every workday, I clock in as early as possible, and clock out as soon as my 8-hour quota ends, just to minimize the window of time for a certain coworker of mine to contact me. I don't have any good reasons to dislike him, but whenever he messages me and says *"Hi"*, what I think is always *"**Aw ****, here we go again...**"*. Just looking at his avatar fills me with fear, dreading the potential moment he may contact me with a task. https://www.youtube.com/watch?v=-1qju6V1jLM And so it starts, every day. If I'm lucky, I already have a project to work on, and can expect the others to leave me alone. If I'm not, I can expect the aforementioned coworker to say *"Hi!"* to me at any moment, causing a heavy amount of stress to me, and increasing my resentment towards him, completely unbeknownst to him. Am I lazy? Am I spoiled? Am I depressed? Whatever the cause is, the life of a working man, who does 40 hours a week for the same company for over a year, is just not for me. I don't mind working hard, as long as it's not constant, and not repeated. I don't mind pulling an all-nighter on a single program, if I get to spend the next day sleeping and doing nothing - arguably, working 16-hours a single day while working 0 the next day, would be better than the 8 hours on both days. Yet, no matter how much I have to work a day - even if I have no active tasks, and thus my only task is to be vigilant and expect messages from my coworkers - I always end the workday tired, and dreading the next morning, wishing that the evening lasted forever, wishing that I could go back in time, constantly thinking to myself - **When will the nightmare end?** To me, the honest work is torture. I would do literally anything - if it's a one-time thing, that is - if the end result was me being liberated from the chains of wageslavery. To me, as long as I have to do 8-hour grinds every day *(save for the weekend)*, morality is just a spook, and there ain't no justice in the world. Give me Universal Basic Income, and then I'll change my mind about morality and work ethics - until then, both are just meaningless spooks to me. I hate my life, and I hate my work, even if it's the closest I can ever get to having my dream job. https://read.cash/@Metalhead33/in-defense-of-scammers-dcfd55ae And the worst part is? **I'm one of the lucky ones.** I work from home, and I work as a software developer *- without even having a diploma -* when my mother and I were expecting way worse *(factory work at an assembly line, or warehouse work)* when I got kicked out of college for failing the same subject the sixth time. My life could be so much worse. **But I don't care.** I'm not counting my blessings. I'm already miserable the way I am now, it makes no difference. My mother looks at others and slaps herself for complaining, but I am not my mother - if anything, that makes me even more adamant that the world is screwed and we need reforms as soon as possible. I shouldn't have any right to complain, yet here I am, complaining. People say that *"work makes you noble"*, but it did the complete opposite to me. It made me into an even bigger miser then I already was. It made me reject morality, reject ethics, it turned me into a selfish egoist. As long as society owes me nothing, I shall only care about myself. The Nightmare Begins But when did it all go so wrong? When did it all start? Gather around folks, because it's story time - or rather, sob story time. Prepare for a pathetic sob story! I was born in a lower-middle-class family. I was always rather eccentric and antisocial, and thus suffered from bullying ever since kindergarten. Ever since that - until college -, my life was characterized by a duality of sorts: warmth and loving at home, bullying and harassment outside *(in kindergarten, in school)*. This reinforced the introverted aspect of my personality, not wanting to take part in any social events, and just wanting to go home ASAP all the time. This made me an escapist. While others of my age were playing football *(soccer)* and all sorts of other sports, I burred myself in fantasy, in video games, in history, and all sorts of other *"nerdy"* interests. Another contrast between me and others my age was in music taste - I liked rock and metal, while everyone else preferred disco, techno, rap, etc. I make no secret out of this: I considered myself superior to my peers. In 2004, tragedy stuck the household for the first time - at least, the first time when I was alive. My maternal grandmother died. This wasn't first time I encountered the concept of death, as we have lost several cats before, but this was the first time I came face to face with the death of a fellow human being, especially one so close to me. This even, undoubtedly served as a catalyst for a lot of other events in my life. My maternal grandmother was like the pillar holding the whole family together. Before her death, my alcoholic father's alcoholism was kept moderated. After her death, my father just went all out of his way. Sure, my life had its ups and downs even after her death, but all in all, ever since she has been gone, it has been mostly a downwards spiral. The aforementioned duality got widened out: we expanded our house, I got my own separate room, and my separate computer. I didn't lack any material goods I desired. But material goods alone don't bring happiness. What awaited me at home was my very own room and very own computer, but what good was that, when I had to dread every evening, knowing that I'll have to go to school in the morning, where I'll be bullied? But the deaths did not stop in 2004. One year later, my uncle - my father's sister's husband - died, and he was very close to the family, and a very very close friend of my father's. He was the one who made my maternal grandmother's grave, and contributed to the expansion of the house. In 2008, my paternal grandfather died. One year later, my parents got divorced - due to my father's alcoholism escalating to the point where my mother considered it unbearable -, and two years later, in 2011, my father died as well. In 2018, my maternal grandfather died, then one year later, my other uncle (the husband of one of my father's other sisters) died as well. In the summer of 2020, the last of my grandparents - my paternal grandmother - died, and on the 6th of Januarry, 2021, my cat Longfur - my one and only friend in real-life - also died. https://www.youtube.com/watch?v=zDA86CxlWxM Yes, so far, I emphasized the negatives, when obviously, my life had some ups too, some positive moments, but the overall tendency since 2004 was decline. Before the death of my maternal grandmother, we were very close as a family, and my mother took me and my siblings to visit our cousins all the time. Now, as of 2021, it has been literal years since I last saw any of my cousins, aunts, uncles, etc. The family drifted apart. Another common thread in my life was not being the master of my own fate. I was always forced to make decisions I just wasn't read to make. *"Which high school will you go to? What do you wanna do once you grow up?"* - *"Uhhh, I dunno, I guess I'll go grammer school..."* - *"Which college will you go to? What do you wanna do once you grow up?"* - *"Uhhhh, I dunno, I guess I'll go for Business Information Technology" (even at that time, I was considering Historian as an alternative career choice, but in the end, BIT won)*. Only now did it become clear to me, that I never really wanted to do anything *(besides maybe developing video games)*, and college only served to extend my childhood. Mentally, I might not be right either. Ever since 2011, I have been suffering from what seems to be dysthimia *(never diagnosed)*, and both real-life family and online *"friends"* say that I probably have Asperger's. https://en.wikipedia.org/wiki/Dysthymia https://en.wikipedia.org/wiki/Asperger_syndrome But when did things truly go wrong in my life? https://www.youtube.com/watch?v=nvwBLv0ZsUU 26th of June, 2020 - Rainclouds gather around my brains As I made it clear in the previous few paragraphs, I wasn't truly happy even before the aforementioned dreaded day. That day, was a birthday of mine. What should have been a happy day for me, was the day of tragedy that accelerated the downwards spiral of my life. Everyone talks about how Covid-19 completely ruined their lives, how everything went wrong as soon as the pandemic hit, or as soon as the lockdowns came, but I wasn't affected negatively. If anything, I welcomed the fact that education and exams became online, given how introverted I am, and how I hate commuting. Everything from the convenience of my room? Sign me up! But on the dreaded day, I had an exam. And I failed. And it was from a subject, at which I already failed five times. Sixth failure meant, that I'm out - kicked out of college, forced to re-join if I intend to get a diploma. From what did I fail? I failed from *"Expert Systems"*, a subject involving neural systems, fuzzy logic and so-called *"expert systems"*. I was expected to learn the material like a poem, learn it from the heart, something I am incapable of. I am a software developer, not a reciter of poems. And so, on that day, my life effectively ended. I'm not suicidal. I'm not going to end my own life *(at least, not feeling like doing that, as of the time of writing this article)*, but I am already dead inside. I won't end my own life - not by my own hand, at least - but if something comes to end it, I won't protest. That day, I lost the will to live, and things haven't improved one bit since. At that day, I might as well have died, because what followed, was nothing but negativity. I re-applied to college, but I also had to get a job. Two months later, my paternal grandmother died, which I already mentioned. Then, five months later, my cat died. Three months later, here I am writing this *"article"*, thinking to myself - When did it all go so wrong? When will the rainclouds go away? Why won't the rainclouds go away? Is there any light at the end of the tunnel? Right now, I begrudgingly do my day job on the weekdays, and am supposed to be studying on the weekend, but just cannot get myself to do it, out of inability to sacrifice free time. I am planning to learn Android programming to develop some kind of app that will earn me some money on the side, but I never get around actually doing that. No matter what am I forced to do, all workdays leave me exhausted of my energy, not even capable of relaxing and having fun. I spend my free time staring at the computer screen, constantly looking at the clock. The sight of time passing makes me want to cry, but I lost the ability to weep and emit tears a long time ago - so all I can do is just to do the thousand-yard stare. https://tvtropes.org/pmwiki/pmwiki.php/Main/ThousandYardStare This is why I am hoping that the economy will collapse. This is why I am cheering for the day robots take my job and force the implementation of Universal Basic Income. This is why I defend scammers. I don't want to return to the pre-pandemic world - it was hell for me. https://read.cash/@Metalhead33/is-economical-prosperity-hurting-you-and-your-causes-949fe1ac https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4 https://read.cash/@Metalhead33/in-defense-of-scammers-dcfd55ae Less than one year of being a worker, and I already resent every single aspect of it. **Am I lazy?** Probably, but can't really blame me if I don't really have much interest in what I'm doing. **Am I spoiled?** Probably - most people of my age already have almost a decade of work experience behind themselves, while I had an extended 28-year childhood, and just can't get broken in. **Am I depressed?** - Well, take your guesses. Mental health isn't taken seriously at all in Hungary, and everyone just considers me lazy. Whatever my mental condition is, even if I'm diagnosed, it definitely won't warrant any sort of welfare or liberation from wageslavery. I desperately need to take a break from life and re-think everything, but doing so would mean burning through my savings. If I got diagnosed with some kind of disability and got officially recognized as unfit-for-work, I'd feel guilty for possibly taking the money from those who are genuinely disabled, despite all my previous talks about being an egoist who only cares about himself. I want either a Basic Income - so I can quit my job and re-think my life - or the collapse of the global economical system, so that I can invent myself on a leveled playing field. Most people want to be rich to buy a big villa or a super nice car. I want to be rich to be liberated from wageslavery. My monthly salary is 200 000 HUF *(666 USD)* - but as I said, that's a bit deceptive, and functionally it's a lot more than that - and for 50 years, that would be 120 000 000 HUF *(400 062 USD)* - that's all I'd need. I want to be rich to finally have some breathing room, re-think my life, and discover what I actually like doing and want to do. https://read.cash/@Metalhead33/get-into-crypto-while-you-can-282cd3be#my-life-for-seven-coins Obviously, that amount of money won't just fall into my lap out of nowhere. So here I stand, sentenced to this dreadful fate. And so, here am I... ... broken, yet refusing to be broken in. Still standing, yet refusing to stand in line. I was never truly the master of my own fate, and the current system won't allow me to be. Something needs to happen to activate my survival instincts and make me invent myself. Every night, I think to myself - **When will this nightmare end?**

+7 more

@Metalhead33

Three Undersaturated Video Game Markets When it comes to not just game development, but anything that involves competitive markets, there are two major traps a budding entrepreneur can fall into. Number one is entering a dying market with a declining number of buyers. As my friend Geri says, it is impossible to overestimate the importance of doing some market-research before committing to a project, or even conceiving of the very idea of said project - failing to do so is the fastest route to unemployment and bankruptcy. However, taking this advice too rigorously can result in you going to the other extreme, and falling into the second trap: attempting to enter an already oversaturated market. In other words, the sweet spot would be to enter an undersaturated, but still very much thriving market. But how does one do that? Well, obviously, you need to be creative, but lacking that, you can always look at the most popular genres and games that make millions, and try to find the ones where a single company has monopolized the entire genre. I am going to give you three examples, where, for all intents and purposes, a single company has a monopoly on an entire genre, and is long overdue for some new blood to enter the competition. Number 1: Bethesda Softworks - The sandboxing action-RPG The Elder Scrolls and the Fallout series aren't the only action-RPGs out there. There are a bazillions of them, many of them far higher-quality than what Bethesda produces, when judged purely as action RPGs. The Elder Scrolls and Fallout aren't the only sandboxing games either: after all, Minecraft exists too, and offers far more freedom as a sandboxing game. The Elder Scrolls and Fallout aren't even the only games that combine high-octane action, RPG elements and sandboxing: Mount and Blade also exists, offering far more freedom and far more RPG elements at the same time. So what makes Bethesda's games so unique then? I'd say it's the Bethesda-formula. Bethesda combines these elements in a unique fashion that no one has attempted to replicate before. Bethesda's games put the player's fully-customizable character into a wide-open sandbox full of fully voice-acted NPCs. The games give the player a lot of freedom - freedom to do sidequests, to enter any building *(not just plot-important ones)*, to talk to any NPC *(not just plot-important ones)*, to buy property, etc. - but not too much, where the player's actions would be completely derailing the lore *(e.g. becoming a monarch of a country)*. The games don't railroad you like 90s JRPGs, but neither do they put you into the world with no guiding hand or leads to go off. They give you a main quest, but you are free to ignore. Obviously, there is one more hard-to-replicate ingredient to the Bethesda-formula: moddability. The ability to mod the game is precisely what has kept the community alive literal decades after the release of each game. Hell, **The Elder Scrolls III Morrowind** and **The Elder Scrolls IV Oblivion** were released respectively in 2002 and 2006, but people are still making mods for them! Hell, some people reverse-engineered the **Elder Scrolls II Daggerfall** *(1996)*, and are making mods for it. Sadly, the Elder Scrolls and Fallout games are AAA-games, which makes than an indie-developer won't be able to just craft a TES-clone and enter the market. However, a wise studio with the budget for the voice actors, artists and writers could easily take the Bethesda formula and apply it to their own fantasy, to build a game that offers players the same mix of character customizability, modding-support, wide-open sandboxing, RPG elements and high-octane action. Bethesda is clearly overdue for some competition. Number 2: Creative Assembly - Grand strategy The Total War games are unique. They offer a formula, that no one seems to have managed to replicate so far, at least, not in the way that you'd recognize. Effectively, the Total War games consist of turn-based strategy and real-time tactics. Or rather, turn-based *"ordering your armies and agents to move around your provinces"*, and real-time battles once your armies actually engage with the enemy. The Total War games additionally augment this with RPG elements, allowing your agents and armies' generals to have different traits *(virtues and vices)*, skills, level up, etc. In the first two Total War games - **Shogun Total War** *(2000)* and **Medieval Total War** *(2002)* - your armies and agents were all put onto a Risk-style map, where they just snapped from one province to another, while fighting their actual battles on a 3D battlefield. In **Rome Total War** *(2004)* and all following Total War games, the Risk-style map was replaced by a two-dimensional map where armies and agents can freely move, adding additional strategic elements into the game, e.g. hiding your armies in forests to set an ambush, building forts to block chokepoints with your armies, flanking the enemy army, etc. Surely, the Total War games can't be the only ones that combine turn-based strategy with real-time battles, right? And you'd be right - I actually know a few others, tho most of them are almost two decades old. However, none of them feature battles as realistic, and none of them feature the same amount of freedom to shape the world to your will *(albeit, some alternatives - like Gates of Troy - compensate by having a more sophisticated economical simulation).* Creative Assembly is clearly overdue for some competition. Number 3: Paradox Development Studio - Grand strategy Paradox Interactive may have published several diverse kinds of games, but when people say "Paradox games", what they actually mean is a subgenre of pseudo-realtime Grand Strategy games developed by Paradox Development Studio: **Europa Universalis**, **Victoria**, **Crusader Kings**, **Hearts of Iron**, **Stellaris**, and last but not least - **Imperator: Rome**. What do all these games have in common? Well, in all of them - except Crusader Kings, which I'll get into later *(and maybe Imperator: Rome, which I never played)* - involve you taking control of a nation, and controlling its armies and agents to conquer land, managing the economy of said nation, and so forth. These games also involve randomly generated events, as well as decisions the player can take to influence the course of history. Battles are just as turn-based as the game itself, and you have no direct control - it's basically all just dice rolls, where you get periodic updates on what's going on. The games are pseudo-realtime, or rather, turn-based, but you don't get to pick when do turns end: it happens at a regular interval, unless you pause the game. In all Paradox games save for Hearts of Iron, a turn represents a day, while in Hearts of Iron, a turn represents an hour. What makes Crusader Kings different? Well, given how it's a game set in the Middle Ages, it has to simulate the medieval feudal system: nations didn't exist yet, instead, feudal lords swore fealty to one another. A king may have had 10-15 dukes as his vassals, but each duke in turn also had 2-3 counts as his vassals, and each count probably had 1-2 barons as vassals. If you were a king, you had vassals, who had vassals, who had vassals. Which means that in Crusader Kings, most provinces - ruled by counts - are effectively their own independent actors, albeit if they are someone's vassals, they do provide levies and tax revenue. Crusader Kings is thus, more character-driven than the other Paradox games, where even a lot of unlanded characters play vital roles and their traits matter. But otherwise, the Crusader Kings games follow the Paradox formula, and play like the rest. Where was I going again? Right, so, unlike the Elder Scrolls series and Total War series, the aforementioned Paradox games are **not** AAA games. There are no voice actors. There's a rather limited amount of low-poly 3D modeling involved, lots of 2D art and lots of scripting, but otherwise, we're talking about games that could be reasonably replicated by a handful of indie game developers, if in a form somewhat reduced in the audiovisual department. Paradox is clearly overdue for some competition. To clone, or not to clone? Imitation is the highest form of flattery. Where does a game genre end, and *[insert game name]*-clones begin? When the genre of first-person shooters first became a thing, FPSs were called *"Doom-clones" (1993)*, and the term *"First Person Shooter"* didn't become widespread until Quake came out in 1996. At what point do start defining the Elder Scrolls games - such as Skyrim - and any potential Skyrim-clones as a new genre? Likewise, the same question goes for the Total War games and Paradox's pseudo-realtime Grand Strategy games. They clearly have many common elements. Competition breeds innovation, and anyone with the budget should see the potential behind usurping the monopoly of the aforementioned three companies on their respective genres. Obviously, I'm not encouraging you to wholesale copy-paste and entire game with all of its mechanics. No, quite the contrary, I am encouraging developers to innovate. What I am encouraging however, is adding some new blood into the mix.

+2 more

@Metalhead33

Push vs Pull APIs In multimedia programming - and to an extent, network programming - we typically deal with two kind of application interfaces: ones that require the programmer to explicitly **push** data onto the underlying implementation of said API, and ones that **pull** data, usually via callbacks, which are little more than *requests to push*. But hey, at least it's automatic-ish! I am going to posit examples of these two contrasting types of APIs in two applications I typically develop in as a hobby. Audio Push - OpenAL Generally, pull APIs that rely on callbacks tend to be higher-level APIs to be contrasted with their push-based low-level counterparts, but audio seems to be an exception. **OpenAL** is an audio processing API intentionally designed to be similar to **OpenGL**, which is arguably not a very clever choice, but oh well. How do we play audio in OpenAL? Oh, it's very simple.... ALuint buffId; ALuint sourceId; alGenBuffers(1,&buffId); alBufferData(buffId,AL_FORMAT_MONO16,pointer_to_your_audio_data,size_of_your_data,44100); alGenSources(1,&sourceId); alSourcei(sourceId,AL_BUFFER,buffId); alSourcePlay(sourceId); Except, this is for playing a single sound-effect, where the entire sound sample is stored in the memory *(or on the soundcard's memory, in case of hardware-based implementations of OpenAL)*. But what if we want to stream some music from the disk, and stream it as it's being played instead of loading it all and decoding it into PCM for storage on the PCM? On modern machines, storing a 3-minute song in PCM format - around 30 megabytes - in RAM might be trivial, on older machines, it was not such a trivial task. Besides, reading it from the disk and decoding all at once causes delay, while streaming is a marginal mallus to performance. But how do we stream on OpenAL? Sadly, OpenAL does not natively support streaming, and it does not support ringbuffers either, which means that we are forced to rely on buffer queues. std::vector<ALuint> buffers(8); ALuint sourceId; alGenBuffers(8,buffers.data()); size_t offset = 0; for(const auto& it : buffers) { alBufferData(it,AL_FORMAT_MONO16,pointer_to_your_audio_data+offset,size_of_your_data,44100); offset += size_of_your_data; } alGenSources(1,&sourceId); alSourceQueueBuffers(sourceId,buffers.size(),buffers.data()); alSourcePlay(sourceId); However, this also comes with the added drawback that we have to periodically check with buffers have *"expired"*, unqueue them from the source, fill them with new audio, and queue them back. ALuint expiredBuffers = 0; ALuint unqueuedBuffer = 0; size_t offset = 0; alGetSourcei(sourceId,AL_BUFFERS_PROCESSED,&expiredBuffers); while(expiredBuffers) { alSourceUnqueueBuffers(source, 1, &unqueuedBuffer); tmpCtr = soundfile->bufferSound(tmpBuff, &framePosition); alBufferData(unqueuedBuffer,AL_FORMAT_MONO16,pointer_to_your_audio_data+offset,size_of_your_data,44100); alSourceQueueBuffers(source, 1, &unqueuedBuffer); --expiredBuffers; } This is a really cumbersome solution that requires basically running a checker on another thread to queue up the buffers, or doing it every frame, if we want to force it into an existing loop. Don't get me wrong, OpenAL is a great API, but this shows a big shortcoming of the whole push API, as well as OpenAL's shtick about trying to have the same syntax as OpenGL. This kind of thing is best handled with callbacks, or in absence of that, a ringbuffer of sorts. Sadly, OpenAL does not support either. Pull - SDL and PortAudio SDL and PortAudio - despite being lower-level API that basically only serve as an abstraction around the native platform-dependent audio systems *(PulseAudio and Alsa on Linux, DirectSound on Windows, etc.)* - don't force you to go through such hoops to stream audio. It's about as trivial as this: class Streamer { public: // Bla bla bla void streamTo(void* destination,int framerate,int channels,int bufferSize) { // Bla bla bla ... // Bla bla bla } }; class SDL_Driver { private: SDL_AudioSpec spec; SDL_AudioDeviceID devId; Streamer* streamer; public: static void callback(void* userdata, Uint8* stream, int len) { SDL_Driver* driver = reinterpret_cast<SDL_Driver*>(userdata); driver->streamer->streamTo(stream,driver->spec->freq,driver->spec->channels,driver->spec->samples); } SDL_Driver(Streamer* streamer, int framerate, int channels, int bufferSize) { SDL_AudioSpec spec; SDL_AudioSpec want; memset(&want,0,sizeof(SDL_AudioSpec)); want.freq = framerate.var; want.format = AUDIO_F32SYS; want.channels = channels.var; want.samples = bufferSize.var; want.callback = callback; want.userdata = this; devId = SDL_OpenAudioDevice(nullptr, 0, &want, &spec, SDL_AUDIO_ALLOW_FORMAT_CHANGE); } ~SDL_Driver() { if(devId) SDL_CloseAudioDevice(devId); } void pause() { SDL_PauseAudioDevice(devId,1); } void unpause() { SDL_PauseAudioDevice(devId,0); } }; Yes, it is arguably longer code than its OpenAL counterpart, but it's also much more intuitive: you set up your SDL audio context, you provide it with a callback, and every time you need to stream audio, you'll know exactly where to stream, and how many samples to stream. It's a **pull** API. It pulls audio from whatever you provide it. It's *"automatic"* - no need to periodically check up on expired buffers on another thread *(or any loop)*, no need to queue or unqueue buffers, all you need is a single function to do the streaming, and this function gets called automatically every time SDL wants more samples. What more could a man ask for? Obviously, if you want to mix audio, you need to go through a few hoops, but that's basically just implementing a few classes. class Playable { public: virtual ~Playable() = default; virtual void streamTo(void* destination,int framerate,int channels,int bufferSize) = 0; }; class Mixer : public Playable { private: std::unordered_map<std::shared_pointer<Playable>,float> playables; public: void streamTo(void* destination,int framerate,int channels,int bufferSize) { // Clear the output buffer for(auto it = std::begin(playables); it != std::end(playables); ++it) { // Clear the input buffer it->streamTo(...) // Stream into the input buffer // After we did stream to the input buffer, add its contents to the output buffer, but multiplied with the volume. } } }; Obviously, if we needed fancy effects like echo, reverb, delay, lowpass filter, highpass filter, etc. we'd have to implement them manually, while OpenAL already has them for us, but still, the point stands - if a lower-level API like SDL and PortAudio can do this right out of the box, why do we have to go through hoops in OpenAL? Video Immediate-mode - Legacy OpenGL and Direct3D In older 3D rendering APIs - namely, legacy OpenGL, Direct3D 8 and before, Glide, etc. - you typically had a so-called *"immediate mode"*, where you had to push the vertices into the API*('s implementation)* to render them. struct Vertex { glm::vec3 pos; glm::vec2 tex; glm::vec4 clr; } std::vector<Vertex> vertices; glBegin(GL_TRIANGLES); for(const auto& it : vertices) { Vertex transformedVertex = transformVertex(it); glVertex3f(transformedVertex.pos.x, transformedVertex.pos.y, transformedVertex.pos.z); glTexCoord2f(transformedVertex.tex.x, transformedVertex.tex.w); glColor4f(transformedVertex.clr.x, transformedVertex.clr.y, transformedVertex.clr.z, transformedVertex.clr.w); } glEnd(); And you had to do this every single frame. Fine if you only have a couple thousand polygons on the screen at a time - like in Quake and Quake II, or any PlayStation 1 game - but obviously, we hit a bottleneck at a certain point. Not only is transforming vertices on the CPU rather costly - you might as well be doing full-on software rendering at that point - but so is the pushing of vertices to your GPU. Those OpenGL API calls - **glVertex3f()**, **glTexCoord2f()** and **glColor4f()** - obviously come with quite a bit of overhead. Can't we transform our vertices on the GPU instead of the CPU? Well, yes we can, if your GPU has hardware-based T&L *(Trasnform and Lighting)* support, sure, but then you're stuck with whatever the fixed-function pipeline of legacy OpenGL offers to you, which is rather limited. Also, the aforementioned three calls are still there, still called for every vertex on every frame. *"Retained"* mode - Modern OpenGL and Direct3D But obviously, in the early 2000s, GPU have implemented support for VBOs *(Vertex Buffer Objects)*, which stored - **retained** - the vertex data of a mesh for you, and support for this feature came in OpenGL 1.5 in 2003 and Direct3D 7 in 1999 *(that's quite a long gap in computer history!)*. However, this feature was essentially only useful for static objects, and was quite useless for anything you wanted to animate. Ergo, if the hardware transform & lighting capabilities weren't good enough for you, you were stuck with immediate mode. As a matter of fact, games like Quake III Arena and future games using its engine *(e.g. Jedi Outcast,* *Jedi Academy**, Return to Castle Wolfenstein, Medal of Honor Allied Assault, Call of Duty, etc.)* still relied on immediate mode *(obviously, since Quake III came out in 1999, and it was an OpenGL game)*. https://read.cash/@Geri/tragedy-of-gaming-in-early-2000s-0c795aa1#jedi-academy It wasn't until the arrival of shaders that this retained mode of storing vertices in in-VRAM buffers really caught on. In 2000, Direct3D 8 came out with support for vertex and pixel shaders. The same feature came to OpenGL first in 2002 as two extensions *(ARB_vertex_program and ARB_fragment_program)*, then as core features of the newly released OpenGL 2.0 in 2004. Forgetting about immediate mode finally made sense for the first time, since you could transform your vertices entirely on the GPU by writing a shader. Obviously, the trivial example was: #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; out vec2 TexCoords; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { TexCoords = aTexCoords; gl_Position = projection * view * model * vec4(aPos, 1.0); } But now that GPUs were programmable and 3D acceleration APIs supported this programmability, you could do all kinds of funky things on the GPU, like animation *(whether keyframe or skeletal)*, and random distortions. Just upload your vertices once, update a few parameters every now and then, and render away! Except that.... this *"retained mode"* is... well, still a kind of immediate mode. We just moved the vertex processing from the CPU onto the GPU. But you still have to make a draw call every frame for every single mesh you want to render, you still have to modify the shader uniform variables, etc. This is still very much a push-API. Truly Retained mode - Game engines What's simple-to-use for the end user is complex-to-implement for the programmer, and vice versa. This isn't news. What might be news to the uninitiated, is that this apply to different kinds of programmers too: what's easy-to-use for a game developer is complex-to-implement for the game engine's developer, and vice versa, and an API that's easy-to-use for a game engine's developer is going to be hell to implement for GPU vendors. In 2016, they gave up on that altogether, gave us all the middle-finger and released Vulkan, a very low-level close-to-the-metal API where even drawing a single triangle requires several thousands of lines of code. https://read.cash/@Metalhead33/simplicity-d7cf30a2#point-of-view https://en.wikipedia.org/wiki/Vulkan_(API) But enough of that segue. So, what do game engines offer us? Well, they offer us something much closer to true retained mode: because the game engines wrap around the lower-level 3D APIs and do all the draw calls for us, us - the game developers - no longer need to make explicit API calls. Instead, we just maintain a list of sors for all the objects we wish to have rendered, provided that they appear on the screen *(good engines implement frustum-culling)*. These lists can be as simple as lists of objects we want rendered, or more sophisticated solutions, such as scenegraphs that also implement many of the transform functions. https://en.wikipedia.org/wiki/Scene_graph So, what mode is the best? It depends on what you want. I'm of the opinion that push APIs are good for 3D rendering *(all 3D APIs are push APIs, even software renderers are push APIs)*, but rather asinine for audio, which clearly requires a more pull-based solution. It's a rather sad state of affairs, that OpenAL is forced into a push-type API by a desperate desire to immitate OpenGL, when it's clear that for streaming audio, you'll need a ringbuffer and/or callbacks, two things OpenAL does not support.

+5 more

@Metalhead33

Software Renderer 4: Complex shapes, Z-Buffers, alpha-blending, perspective-correction, cameras In the previous episode, we got into texturing, texture filtering and also dithering. However, all that we rendered so far was a flat plane, which is not very impressive. Unless you want a 2D game based around nothing but sprites, you're going to need to render more complex 3D scenes. https://read.cash/@Metalhead33/software-rendering-3-textures-and-more-colours-21500514 A more advanced pipeline Obviously, if you intend to render the *"more complex 3D scenes"*, you're not going to be passing clip-space coordinates to the renderer directly. You're going to want to **transform** your models by using some hacky matrix maths. And for that, we need a more advanced *"vertex shader"* with a more advanced *"uniform"*. #ifndef MODELPIPELINE_HPP #define MODELPIPELINE_HPP #include <glm/glm.hpp> #include "Texture.hpp" #include "RenderingPipeline.hpp" #include <memory> #include "ZBuffer.hpp" struct ModelUniform { std::shared_ptr<Texture> tex; TextureFiltering filtering; glm::mat4 model; glm::mat4 projection; }; struct ModelVertexIn { glm::vec3 POS; glm::vec2 TEXCOORD; glm::vec4 COLOUR; }; struct ModelVertexOut { glm::vec3 POS; glm::vec2 TEXCOORD; glm::vec4 COLOUR; inline static ModelVertexOut split(const ModelVertexOut& t, const ModelVertexOut& m, const ModelVertexOut& b, float dy, float iy) { return { glm::vec3( t.POS.x + ((b.POS.x - t.POS.x) / dy) * iy, m.POS.y, t.POS.z + ((b.POS.z - t.POS.z) / dy) * iy ), glm::vec2( t.TEXCOORD.r + ((b.TEXCOORD.r - t.TEXCOORD.r) / dy) * iy, t.TEXCOORD.g + ((b.TEXCOORD.g - t.TEXCOORD.g) / dy) * iy ), glm::vec4( t.COLOUR.r + ((b.COLOUR.r - t.COLOUR.r) / dy) * iy, t.COLOUR.g + ((b.COLOUR.g - t.COLOUR.g) / dy) * iy, t.COLOUR.b + ((b.COLOUR.b - t.COLOUR.b) / dy) * iy, t.COLOUR.a + ((b.COLOUR.a - t.COLOUR.a) / dy) * iy ) }; } }; typedef RenderingPipeline<ModelVertexIn,ModelVertexOut,ModelUniform> ModelPipeline; ModelVertexOut ModelVertexShader(const ModelUniform& uniform, const ModelVertexIn& vertex, const glm::ivec4& viewport); void ModelFragmentShader(Texture& framebuffer, const glm::ivec2& point, const ModelUniform& uniform, const ModelVertexOut& v0,const ModelVertexOut& v1, const ModelVertexOut& v2, float w0, float w1, float w2); #endif // MODELPIPELINE_HPP #include "ModelPipeline.hpp" #include <glm/gtc/matrix_transform.hpp> ModelVertexOut ModelVertexShader(const ModelUniform &uniform, const ModelVertexIn &vertex, const glm::ivec4 &viewport) { ModelVertexOut out{ glm::projectNO(vertex.POS,uniform.model,uniform.projection,viewport), vertex.TEXCOORD, vertex.COLOUR }; return out; } void ModelFragmentShader(Texture &framebuffer, const glm::ivec2 &point, const ModelUniform &uniform, const ModelVertexOut &v0, const ModelVertexOut &v1, const ModelVertexOut &v2, float w0, float w1, float w2) { if(!uniform.tex) return; if(point.x < 0 || point.y < 0) return; glm::vec2 texCoord = { (w0 * v0.TEXCOORD.r) + (w1 * v1.TEXCOORD.r) + (w2 * v2.TEXCOORD.r), (w0 * v0.TEXCOORD.g) + (w1 * v1.TEXCOORD.g) + (w2 * v2.TEXCOORD.g) }; glm::vec4 colourKernel = uniform.tex->sample(texCoord,point,uniform.filtering) * glm::vec4{ (w0 * v0.COLOUR.r) + (w1 * v1.COLOUR.r) + (w2 * v2.COLOUR.r), (w0 * v0.COLOUR.g) + (w1 * v1.COLOUR.g) + (w2 * v2.COLOUR.g), (w0 * v0.COLOUR.b) + (w1 * v1.COLOUR.b) + (w2 * v2.COLOUR.b), (w0 * v0.COLOUR.a) + (w1 * v1.COLOUR.a) + (w2 * v2.COLOUR.a) }; framebuffer.setPixelDithered(point,point,colourKernel); } Now, the things to unpack here: This pipeline mixes the sampled texture colour with the per-vertex colour. What are those two new uniform variables? **Model** and **Projection**? Both are 4x4 matrices. We multiply our original object-space coordinates with them. I could write paragraphs upon paragraphs on the matrix mathematics involved, but what you need to know is that these matrix multiplications will take your object-space coordinates, transform them first into world-space coordinates *(via multiplication with Model)*, then from world-space coordinates into clip-space coordinates and finally screen-space coordinates *(via multiplication with Projection)*. In your typically hardware-accelerated application, you actually have three such matrices: **Model**, **View** and **Projection**. By multiplying our object-space coordinates with the Model matrix, we get our world-space coordinates. By multiplying our world-space coordinates with View, we get our clipspace coordinates. By multiplying our clipspace coordinates with Projection, we get our screenspace coordinates. In this program, our *"projection"* is actually Projection multiplied by View, so we translate world-space coordinates into screen-space coordinates in one stage rather than two. We obviously need to set these new variables somehow.... but how? As I mentioned, our *"projection"* really is the merger of two other variables - a projection frustum matrix and a view matrix. The frustum matrix is rather trivial: just pick either glm::frustum, glm::perspective or glm::perspectiveFov. To keep things simple, I went with **glm::frustum**. https://glm.g-truc.net/0.9.4/api/a00151.html#ga2a5b0a83f78884d9cf3cc1ba99131299 https://glm.g-truc.net/0.9.4/api/a00151.html#ga283629a5ac7fb9037795435daf22560f https://glm.g-truc.net/0.9.4/api/a00151.html#gac2bbb4ae38c7cc549feefae5406517d7 glm::frustum(-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 100.0f); But we need to also have a view matrix. How will we get it? Enter the camera The view matrix is supposed to represent the location of our camera or eyes, and the direction it is looking at. For this purpose, I brought back an old camera class of mine, which I probably cobbled together from the code of programmers more skilled than me, which kind of makes it black magic to me. #ifndef CAMERA_HPP #define CAMERA_HPP #include <glm/glm.hpp> class Camera { public: enum Movement { FORWARD, BACKWARD, LEFT, RIGHT }; private: // Camera Attributes glm::vec3 Position; glm::vec3 Front; glm::vec3 Up; glm::vec3 Right; glm::vec3 WorldUp; // Euler Angles float Yaw; float Pitch; // Camera options float MovementSpeed; float MouseSensitivity; float Zoom; protected: void updateCameraVectors(); public: Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH); Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch); void ProcessMouseMovement(float xoffset, float yoffset, float W, float H, bool constrainPitch = true); void ProcessKeyboard(Movement direction, float deltaTime); glm::mat4 GetViewMatrix(); float getZoom(); void reset(); static const float YAW; static const float PITCH; static const float SPEED; static const float SENSITIVITY; static const float ZOOM; float getYaw() const; void setYaw(float value); float getPitch() const; void setPitch(float value); float getMovementSpeed() const; void setMovementSpeed(float value); float getMouseSensitivity() const; void setMouseSensitivity(float value); }; #endif // CAMERA_HPP And the **Camera.cpp**, which is too big for a screenshot: #include "Camera.hpp" #include <glm/gtc/matrix_transform.hpp> const float Camera::YAW = 3.14f; const float Camera::PITCH = 0.0f; const float Camera::SPEED = 2.5f; const float Camera::SENSITIVITY = 0.005f; const float Camera::ZOOM = 45.0f; Camera::Camera(glm::vec3 position, glm::vec3 up, float yaw, float pitch) : MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) { Front = glm::vec3(0.0f, 0.0f, 1.0f); Position = position; WorldUp = up; Yaw = yaw; Pitch = pitch; updateCameraVectors(); } float Camera::getZoom() { return Zoom; } void Camera::reset() { Front = glm::vec3(0.0f, 0.0f, 1.0f); Position = glm::vec3(0.0f, 0.0f, 0.0f); WorldUp = glm::vec3(0.0f, 1.0f, 0.0f); Yaw = YAW; Pitch = PITCH; updateCameraVectors(); } Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) { Front = glm::vec3(0.0f, 0.0f, -1.0f); Position = glm::vec3(posX, posY, posZ); WorldUp = glm::vec3(upX, upY, upZ); Yaw = yaw; Pitch = pitch; updateCameraVectors(); } void Camera::ProcessMouseMovement(float xoffset, float yoffset, float W, float H, bool constrainPitch) { xoffset *= MouseSensitivity; yoffset *= MouseSensitivity; Yaw += xoffset; Pitch += yoffset; // Make sure that when pitch is out of bounds, screen doesn't get flipped if (constrainPitch) { if (Pitch >= 3.14) Pitch = 3.14; if (Pitch <= -3.14) Pitch = -3.14; } // Update Front, Right and Up Vectors using the updated Euler angles updateCameraVectors(); } void Camera::ProcessKeyboard(Movement direction, float deltaTime) { float velocity = MovementSpeed * deltaTime; if (direction == FORWARD) Position += Front * velocity; if (direction == BACKWARD) Position -= Front * velocity; if (direction == LEFT) Position -= Right * velocity; if (direction == RIGHT) Position += Right * velocity; } glm::mat4 Camera::GetViewMatrix() { return glm::lookAt(Position, Position + Front, Up); } float Camera::getYaw() const { return Yaw; } void Camera::setYaw(float value) { Yaw = value; } float Camera::getPitch() const { return Pitch; } void Camera::setPitch(float value) { Pitch = value; } float Camera::getMovementSpeed() const { return MovementSpeed; } void Camera::setMovementSpeed(float value) { MovementSpeed = value; } float Camera::getMouseSensitivity() const { return MouseSensitivity; } void Camera::setMouseSensitivity(float value) { MouseSensitivity = value; } void Camera::updateCameraVectors() { Front = glm::vec3( cos(Pitch) * sin(Yaw), sin(Pitch), cos(Pitch) * cos(Yaw) ); Front = glm::normalize(Front); Right = glm::vec3( sin(Yaw - 3.14f/2.0f), 0.0f, cos(Yaw - 3.14f/2.0f) ); Right = glm::normalize(Right); Up = glm::cross( Right, Front ); Up = glm::normalize(Up); } So, just what in the name of RNGesus are we dealing with here? In the source file, the first few lines after the header inclusions are a bunch of default constants, and they're not particularly interesting. What's far more interesting is the class's variables. **Position**, as its name implies is the position of our camera in world-space. **Front** is a 3D vector calculated from our **Pitch** and **Yaw**, and then normalized. Its pre-normalization values are `{ cos(pitch) * sin(yaw), sin(pitch), cos(pitch) * cos(yaw) }`. How? Why? I don't know. It's black magic. Also, Pitch and Yaw are in radians, obviously. **Right** is calculated from **Yaw**, and then normalized. Its pre-normalization values are `{ sin(yaw - (3π/2)), 0, cos(yaw - (3π/2)) }`. Another piece of our mysterious mathematical black magic. **Up** is calculated from **Front** and **Right** - as a matter of fact, it **is** the vector cross-product of Front and Right, but normalized afterwards. But what the hell are **Pitch** and **Yaw** supposed to be anyway? Euler angles, or more specifically, Tait-Byran angles. Basically, they represent the orientation of our camera. https://en.wikipedia.org/wiki/Euler_angles https://en.wikipedia.org/wiki/Euler_angles#Tait%E2%80%93Bryan_angles So, with these variables at hand, what we do is this: We manipulate the **Yaw** and **Pitch** based on mouse movement. We manipulate **Position** based on keyboard movement. To calculate our View matrix, we simply need use glm::lookAt. https://glm.g-truc.net/0.9.4/api/a00151.html#gae2dca3785b6d5796e876114af58a60a1 glm::lookAt(Position, Position + Front, Up); Nice, now we have a camera that we can use to generate our **View** as well! More complex shapes Rendering just flat planes - especially ones consisting of two triangles or a single triangle - is boring. We need to import models into our renderer. For this purpose, I recommend Assimp. http://www.assimp.org/ void SoftwareRendererSystem::loadModel() { Assimp::Importer importer; importer.SetPropertyBool(AI_CONFIG_PP_PTV_NORMALIZE,true); const aiScene* scene = importer.ReadFile("cube.dae", aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_JoinIdenticalVertices); if(!scene) throw std::runtime_error("Error loading the scene!"); if(!scene->mNumMeshes) throw std::runtime_error("No models in the mesh!"); const auto& mesh = *scene->mMeshes[0]; if(!mesh.mNumVertices) throw std::runtime_error("There are no vertices in the mesh!!"); if(!mesh.mNumFaces) throw std::runtime_error("There are no faces! in the mesh!!"); if(!mesh.HasPositions()) throw std::runtime_error("There are no positions the mesh!!"); if(!mesh.HasTextureCoords(0)) throw std::runtime_error("There are no texture coordinates the mesh!!"); for(unsigned int i = 0; i < mesh.mNumVertices; ++i) { const auto& POS = mesh.mVertices[i]; const auto& TEX = mesh.mTextureCoords[0][i]; ModelVertexIn ver; ver.POS = { POS.x, POS.y, POS.z +2.0f }; ver.TEXCOORD = { TEX.x, TEX.y }; switch (i % 3) { case 0: ver.COLOUR = glm::vec4(1.0f, 0.5f, 0.5f, 0.5f); break; case 1: ver.COLOUR = glm::vec4(0.5f, 1.0f, 0.5f, 0.5f); break; case 2: ver.COLOUR = glm::vec4(0.5f, 0.5f, 1.0f, 0.5f); break; default: ver.COLOUR = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); break; } vertices.push_back(ver); } for(unsigned int i = 0; i < mesh.mNumFaces; ++i) { const auto& face = mesh.mFaces[i]; for(unsigned int j = 0; j < face.mNumIndices; ++j) indices.push_back(face.mIndices[j]); } } Now that we have our model, we can just set our model matrix to an identity matrix, maybe scale it up if we want.... pipeline.uniform.model = glm::mat4(1.0f); pipeline.uniform.model = glm::translate(pipeline.uniform.model, glm::vec3(0.0f, -1.75f, 0.0f)); // translate it down so it's at the center of the scene And now we'll have our 3D scene with the cube. Uh-oh. We did something terribly wrong, didn't we? Those who are already familiar with 3D rendering pipelines should already know what's the problem here, but for those who don't... The first problem at hand, is that we're not clearing our texture every time we render, so we end up constantly painting over what we have already painted, instead of starting with a blank canvas every frame. We solve this by adding a new function to textures: **clearToColour()**. virtual void clearToColour(const glm::vec4& colourKernel) = 0; And implement it on our **StandardTexture**: void clearToColour(const glm::vec4& colourKernel) { for(int x = 0; x < w; ++x) { for(int y = 0; y < h; ++y) { PixelType& pix = buff[((y) * w) + x]; pix.fromKernelDithered(colourKernel,glm::ivec2(x,y)); } } } Now we just need to clear the colour every time we start rendering... void SoftwareRendererSystem::render() { enderBuffer.clearToColour(glm::vec4(0.0f, 0.5f, 1.0f, 0.0f)); pipeline.renderTriangles(vertices.data(),indices.data(), indices.size() ); SDL_UpdateTexture(framebuffer.get(), nullptr, renderBuffer.getRawPixels(), renderBuffer.getStride() ); SDL_RenderCopy(renderer.get(), framebuffer.get(), nullptr, nullptr); SDL_RenderPresent(renderer.get()); SDL_Delay(16); } And voila, we got something much better! Eh, so-so. It's still not perfect, and I think we can all see why: we have no way of knowing in which order are we rendering all the triangles, and we simply paint over the ones we have already painted. Not good. The visibility problem Obviously, we need to find a way to determine which pixels are visible or not. This is called the **visibility problem**, and there are several schools of thought at solving it: **The Painter's algorithm****:** We draw the scene from from back to front, overwriting the things in the back that we already drew when drawing things in the front. Wastes processing power, and is only viable if we're dealing with sprites, with no 3D polygons. Even when you pre-cull your triangles, you'll still have some triangles clipping over others and popping in and out, just like in several PS1 games. Oh, and in a software renderer, it's also terrible for cache-efficency, because you just render different triangles with different textures. https://en.wikipedia.org/wiki/Painter%27s_algorithm https://youtu.be/x8TO-nrUtSI?t=286 **The reverse-painter's algorithm:** The technique used in Doom to draw the environment, it's a modification of the Painter's algorithm - we draw the scene from front to back, conserving processing power by not drawing what we don't have to. The caveat is that this technique pretty much only works for binary-space partitioned environments, and simply not viable for polygonal 3D models or even 2D sprites, unless we combine it with... https://en.wikipedia.org/wiki/Binary_space_partitioning **Z-Buffering****:** We simply keep a numerical value for each pixel on the screen, to keep track of its depth value - or rather, how far or near is the object represented by the pixel to the camera. This allows us to easily ensure that objects standing in front of other objects will at least partially obscure said objects in a realistic manner. We test for depth, and if we write our pixel, we also write our depth value. This means that we don't need to order our triangles at all, and just render them in any order we desire. https://en.wikipedia.org/wiki/Z-buffering Obviously, even with Z-Buffering, if we just rendered our objects in a completely arbitrary ordered - or God forbid, combined Z-Buffering with the Painter's algorithm - we would kill our performance. So, ideally, you would combine Z-Buffering with the reverse-painter's algorithm by rendering objects front-to-back and aggressively culling triangles that are guaranteed to be unnecessary to render. But we won't go down that rabbit hole for now, and will simply resort to simple Z-Buffering. #ifndef ZBUFFER_HPP #define ZBUFFER_HPP #include <vector> class ZBuffer { private: std::vector<float> buff; unsigned w,h; public: ZBuffer(); ZBuffer(unsigned w, unsigned h); ZBuffer(const ZBuffer& cpy); ZBuffer& operator=(const ZBuffer& cpy); ZBuffer(ZBuffer&& mov); ZBuffer& operator=(ZBuffer&& mov); unsigned getW() const; unsigned getH() const; void clear(); float& get(unsigned x, unsigned y); const float& get(unsigned x, unsigned y) const; float* operator[](unsigned y); const float* operator[](unsigned y) const; }; #endif // ZBUFFER_HPP **ZBuffer.cpp** #include "ZBuffer.hpp" #include <limits> #include <cstring> ZBuffer::ZBuffer() : w(0), h(0) { } ZBuffer::ZBuffer(unsigned w, unsigned h) : buff(w*h), w(w), h(h) { } unsigned ZBuffer::getH() const { return h; } unsigned ZBuffer::getW() const { return w; } void ZBuffer::clear() { //memset(buff.data(),0,buff.size()*sizeof(float)); for(auto& it : buff) { it = std::numeric_limits<float>::infinity(); //it = 1.0f; } } float &ZBuffer::get(unsigned x, unsigned y) { return buff[(y*w)+x]; } const float &ZBuffer::get(unsigned x, unsigned y) const { return buff[(y*w)+x]; } float *ZBuffer::operator[](unsigned y) { return &buff[y*w]; } const float *ZBuffer::operator[](unsigned y) const { return &buff[y*w]; } ZBuffer::ZBuffer(const ZBuffer &cpy) : buff(cpy.buff), w(cpy.w), h(cpy.h) { } ZBuffer &ZBuffer::operator=(const ZBuffer &cpy) { this->buff = cpy.buff; this->w = cpy.w; this->h = cpy.h; return *this; } ZBuffer::ZBuffer(ZBuffer &&mov) : buff(std::move(mov.buff)), w(mov.w), h(mov.h) { mov.w = 0; mov.h = 0; } ZBuffer &ZBuffer::operator=(ZBuffer &&mov) { this->buff = std::move(mov.buff); this->w = mov.w; this->h = mov.h; mov.w = 0; mov.h = 0; return *this; } Next, we modify our pipeline by adding a pointer to the Z-Buffer to our uniform: struct ModelUniform { ZBuffer* zbuff; std::shared_ptr<Texture> tex; TextureFiltering filtering; glm::mat4 model; glm::mat4 projection; }; And finally, we make use of said Z-Buffer: #include "ModelPipeline.hpp" #include <glm/gtc/matrix_transform.hpp> ModelVertexOut ModelVertexShader(const ModelUniform &uniform, const ModelVertexIn &vertex, const glm::ivec4 &viewport) { ModelVertexOut out{ glm::projectNO(vertex.POS,uniform.model,uniform.projection,viewport), vertex.TEXCOORD, vertex.COLOUR }; return out; } void ModelFragmentShader(Texture &framebuffer, const glm::ivec2 &point, const ModelUniform &uniform, const ModelVertexOut &v0, const ModelVertexOut &v1, const ModelVertexOut &v2, float w0, float w1, float w2) { if(!uniform.tex) return; if(point.x < 0 || point.y < 0) return; const float z = ((w0 * v0.POS.z) + (w1 * v1.POS.z) + (w2 * v2.POS.z)); float& zbuffpoint = uniform.zbuff->get(point.x,point.y); if(z >= 0.0f && z <= zbuffpoint) { glm::vec2 texCoord = { (w0 * v0.TEXCOORD.r) + (w1 * v1.TEXCOORD.r) + (w2 * v2.TEXCOORD.r), (w0 * v0.TEXCOORD.g) + (w1 * v1.TEXCOORD.g) + (w2 * v2.TEXCOORD.g) }; glm::vec4 colourKernel = uniform.tex->sample(texCoord,point,uniform.filtering) * glm::vec4{ (w0 * v0.COLOUR.r) + (w1 * v1.COLOUR.r) + (w2 * v2.COLOUR.r), (w0 * v0.COLOUR.g) + (w1 * v1.COLOUR.g) + (w2 * v2.COLOUR.g), (w0 * v0.COLOUR.b) + (w1 * v1.COLOUR.b) + (w2 * v2.COLOUR.b), (w0 * v0.COLOUR.a) + (w1 * v1.COLOUR.a) + (w2 * v2.COLOUR.a) }; zbuffpoint = z; framebuffer.setPixelDithered(point,point,colourKernel); } } And modify our rendering loop to make sure it gets cleared every time... void SoftwareRendererSystem::render() { renderBuffer.clearToColour(glm::vec4(0.0f, 0.5f, 1.0f, 0.0f)); zbuff.clear(); pipeline.renderTriangles(vertices.data(),indices.data(), indices.size() ); SDL_UpdateTexture(framebuffer.get(), nullptr, renderBuffer.getRawPixels(), renderBuffer.getStride() ); SDL_RenderCopy(renderer.get(), framebuffer.get(), nullptr, nullptr); SDL_RenderPresent(renderer.get()); SDL_Delay(16); } And, we got something better! More observant viewers may have noticed something being wrong with our rendered shape. It seems warpy. Like the texture is warping. And that is because we are using **affine texture mapping**. To get **perspective-correct texture-mapping**, we'll need to divide up all our per-vertex attributes with the Z-coordinate after running the vertex shader, then multiply them with our Z-coordinate again in the fragment shader. https://en.wikipedia.org/wiki/Texture_mapping#Affine_texture_mapping https://en.wikipedia.org/wiki/Texture_mapping#Perspective_correctness And if we do just that... #include "ModelPipeline.hpp" #include <glm/gtc/matrix_transform.hpp> ModelVertexOut ModelVertexShader(const ModelUniform &uniform, const ModelVertexIn &vertex, const glm::ivec4 &viewport) { ModelVertexOut out{ glm::projectNO(vertex.POS,uniform.model,uniform.projection,viewport), vertex.TEXCOORD, vertex.COLOUR }; out.TEXCOORD /= out.POS.z; out.COLOUR /= out.POS.z; return out; } void ModelFragmentShader(Texture &framebuffer, const glm::ivec2 &point, const ModelUniform &uniform, const ModelVertexOut &v0, const ModelVertexOut &v1, const ModelVertexOut &v2, float w0, float w1, float w2) { if(!uniform.tex) return; if(point.x < 0 || point.y < 0) return; const float z = ((w0 * v0.POS.z) + (w1 * v1.POS.z) + (w2 * v2.POS.z)); float& zbuffpoint = uniform.zbuff->get(point.x,point.y); if(z >= 0.0f && z <= zbuffpoint) { glm::vec2 texCoord = { (w0 * v0.TEXCOORD.r) + (w1 * v1.TEXCOORD.r) + (w2 * v2.TEXCOORD.r), (w0 * v0.TEXCOORD.g) + (w1 * v1.TEXCOORD.g) + (w2 * v2.TEXCOORD.g) }; glm::vec4 clr = glm::vec4{ (w0 * v0.COLOUR.r) + (w1 * v1.COLOUR.r) + (w2 * v2.COLOUR.r), (w0 * v0.COLOUR.g) + (w1 * v1.COLOUR.g) + (w2 * v2.COLOUR.g), (w0 * v0.COLOUR.b) + (w1 * v1.COLOUR.b) + (w2 * v2.COLOUR.b), (w0 * v0.COLOUR.a) + (w1 * v1.COLOUR.a) + (w2 * v2.COLOUR.a) }; texCoord *= z; clr *= z; glm::vec4 colourKernel = uniform.tex->sample(texCoord,point,uniform.filtering) * clr; zbuffpoint = z; framebuffer.setPixelDithered(point,point,colourKernel); } } We get perspective-correct texture mapping! Except, we have a problem... Clipping Notice what happens when you move too close to the model. Our perspective-correction fails, and we also get some funky and trippy artifacts going on. What's the cause? **The lack of clipping.** We need to clip our triangles. But what does that even mean? Well, it means we simply don't render anything that is outside the bounds of the screen. But aren't we already refusing to render what falls outside the screen? Well, not truly. Sure, when we rasterize our triangles, in our loop, we define the amount of scanlines accordingly, but we could optimize this further by checking if the triangle even needs to be rendered at all. void rasterize(const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2) { bool isInside = false; const int xCoords[] = { int(v0.POS.x), int(v1.POS.x), int(v2.POS.x) }; const int yCoords[] = { int(v0.POS.y), int(v1.POS.y), int(v2.POS.y) }; const float zCoords[] = { v0.POS.z, v1.POS.z, v2.POS.z }; for(int a : xCoords) { isInside = std::max(isInside,a <= viewport[2] && a >= viewport[0]); } for(int a : yCoords) { isInside = std::max(isInside,a <= viewport[3] && a >= viewport[1]); } for(float a : zCoords) { isInside = std::max(isInside,a >= 0); } if(!isInside) return; const VertexOutType *t = &v0; const VertexOutType *m = &v1; const VertexOutType *b = &v2; // Sort by Y ... Now we know up-front, if a triangle needs to be rendered or not - if all of its coordinates fall outside the viewport, or the triangle has a wrong Z-coordinate, we simply won't render the triangle at all. This saves us the sorting, the splitting, the looping over the scanlines, etc. But it's not enough. When a triangle is partially outside the bounds of the screen - or is too close to the viewer in regards to its Z-coordinate -, we need to **clip** it, which is to say, we have to split it. But how do we go about doing just that? There are four widely known algorithms for clipping triangles: the Greiner–Hormann clipping algorithm, the Sutherland–Hodgman algorithm, the Vatti clipping algorithm, and last but not least, the Weiler–Atherton clipping algorithm. https://en.wikipedia.org/wiki/Greiner%E2%80%93Hormann_clipping_algorithm https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm https://en.wikipedia.org/wiki/Vatti_clipping_algorithm https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm But before we implement any of them, we might want to do a little bit of refactoring.... Vertex Interpolation So far, the interpolation of all of our vertex attributes took place in the fragment shader. This leads to repeated code that will be prone to errors. So I propose that we move this code into our vertex definitions. To demonstrate on the **BasicVertexOut**: struct BasicVertexOut { glm::vec3 POS; glm::vec4 COLOUR; inline static BasicVertexOut split(const BasicVertexOut& t, const BasicVertexOut& m, const BasicVertexOut& b, float dy, float iy) { return { glm::vec3( t.POS.x + ((b.POS.x - t.POS.x) / dy) * iy, m.POS.y, t.POS.z + ((b.POS.z - t.POS.z) / dy) * iy ), glm::vec4( t.COLOUR.r + ((b.COLOUR.r - t.COLOUR.r) / dy) * iy, t.COLOUR.g + ((b.COLOUR.g - t.COLOUR.g) / dy) * iy, t.COLOUR.b + ((b.COLOUR.b - t.COLOUR.b) / dy) * iy, t.COLOUR.a + ((b.COLOUR.a - t.COLOUR.a) / dy) * iy ) }; } inline static BasicVertexOut interpolate(const BasicVertexOut& v0, const BasicVertexOut& v1, const BasicVertexOut& v2, float w0, float w1, float w2, bool perspectiveCorrect) { BasicVertexOut out = { glm::vec3( // POS (v0.POS.x * w0) + (v1.POS.x * w1) + (v2.POS.x * w2), // X (v0.POS.y * w0) + (v1.POS.y * w1) + (v2.POS.y * w2), // Y (v0.POS.z * w0) + (v1.POS.z * w1) + (v2.POS.z * w2) // Z ), glm::vec4( // COLOUR (v0.COLOUR.x * w0) + (v1.COLOUR.x * w1) + (v2.COLOUR.x * w2), // X (v0.COLOUR.y * w0) + (v1.COLOUR.y * w1) + (v2.COLOUR.y * w2), // Y (v0.COLOUR.z * w0) + (v1.COLOUR.z * w1) + (v2.COLOUR.z * w2), // Z (v0.COLOUR.w * w0) + (v1.COLOUR.w * w1) + (v2.COLOUR.w * w2) // W ) }; if(perspectiveCorrect) out.COLOUR *= out.POS.z; // We're assuming that the COLOUR attribute of each vertex was divided by POS.z prior to calling this function return out; } }; This **interpolate()** function needs to be added to every vertex output struct now. This will allow us to simplify the signature of our fragment shaders, after we modified the basic rendering pipeline. template<typename VertexInType, typename VertexOutType, typename UniformType> struct RenderingPipeline { typedef std::function<VertexOutType(const UniformType&, const VertexInType&, const glm::ivec4&, bool)> VertexShader; typedef std::function<void(Texture&, const glm::ivec2&, const UniformType&, const VertexOutType&)> FragmentShader; UniformType uniform; VertexShader vert; FragmentShader frag; Texture* framebuffer; glm::ivec4 viewport; bool perspectiveCorrection; ... void renderScanline(float areaReciprocal, int y, int minX, int maxX, const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2) { // Clamp the scanline's left and right ends into the viewport minX = std::max(minX,viewport[0]); maxX = std::min(maxX,viewport[2]); // Okay, let's render! for(int x = minX; x < maxX; ++x) { const glm::vec2 p = glm::vec2(float(x)+0.5f,float(y)+0.5f); const float w0 = edgeFunction(v1.POS, v2.POS, p) * areaReciprocal; const float w1 = edgeFunction(v2.POS, v0.POS, p) * areaReciprocal; const float w2 = edgeFunction(v0.POS, v1.POS, p) * areaReciprocal; frag(*framebuffer,glm::ivec2(x,y),uniform, VertexOutType::interpolate(v0,v1,v2,w0,w1,w2,perspectiveCorrection)); } } void renderTriangle(const VertexInType& i0, const VertexInType& i1, const VertexInType& i2) { const VertexOutType o0 = vert(uniform,i0,viewport,perspectiveCorrection); const VertexOutType o1 = vert(uniform,i1,viewport,perspectiveCorrection); const VertexOutType o2 = vert(uniform,i2,viewport,perspectiveCorrection); rasterize(o0,o1,o2); } void renderTriangles(const VertexInType* vertices, size_t vertexCount) { for(size_t i = 0; i < vertexCount; i += 3) { renderTriangle( vertices[i],vertices[i+1],vertices[i+2]); } } void renderTriangles(const VertexInType* vertices, const unsigned* indices, size_t indexCount) { for(size_t i = 0; i < indexCount; i += 3) { renderTriangle(vertices[indices[i]],vertices[indices[i+1]],vertices[indices[i+2]]); } } This means that we need to add that boolean parameter to our vertex shaders to account for perspective-correction, but otherwise, our codebase was slightly simplified. BasicVertexOut basicVertexShader(const BasicUniform &uniform, const BasicVertexIn &vertex, const glm::ivec4 &viewport, bool perspectiveCorrection) { const int viewportW = viewport[2] - viewport[0]; const int viewportH = viewport[3] - viewport[1]; BasicVertexOut out = { glm::vec3( ((vertex.POS[0] + 1.0f) / 2.0f * viewportW) + viewport[0] , (((vertex.POS[1]-1.0f) / -2.0f) * viewportH) + viewport[1], 1.0f ), vertex.COLOUR }; if(perspectiveCorrection) out.COLOUR /= out.POS.z; return out; } void basicFragmentShader(Texture &framebuffer, const glm::ivec2 &point, const BasicUniform &uniform, const BasicVertexOut &v0) { if(point.x < 0 || point.y < 0) return; framebuffer.setPixelDithered(point,point,v0.COLOUR); } ModelVertexOut ModelVertexShader(const ModelUniform &uniform, const ModelVertexIn &vertex, const glm::ivec4 &viewport, bool perspectiveCorrection) { ModelVertexOut out{ glm::projectNO(vertex.POS,uniform.model,uniform.projection,viewport), vertex.TEXCOORD, vertex.COLOUR }; if(perspectiveCorrection) { out.TEXCOORD /= out.POS.z; out.COLOUR /= out.POS.z; } return out; } void ModelFragmentShader(Texture &framebuffer, const glm::ivec2 &point, const ModelUniform &uniform, const ModelVertexOut &v0) { if(!uniform.tex) return; if(point.x < 0 || point.y < 0) return; float& zbuffpoint = uniform.zbuff->get(point.x,point.y); if(v0.POS.z >= 0.0f && v0.POS.z <= zbuffpoint) { glm::vec4 colourKernel = uniform.tex->sample(v0.TEXCOORD,point,uniform.filtering) * v0.COLOUR; zbuffpoint = v0.POS.z; framebuffer.setPixelDithered(point,point,colourKernel); } } Clippin' for real Now that we have vastly simplified our shaders, and have provided for a way to interpolate vertices outside of the fragment shader, we need to implement clipping. I already mentioned there being four algorithms. So which one will we choose? Two out of the four I mentioned - Vatti and Weiler-Atherton - are 2D-only, so that leaves us with Greiner–Hormann and Sutherland–Hodgman. I'm going with the latter, because it has pseudocode written on Wikipedia. https://en.wikipedia.org/wiki/Vatti_clipping_algorithm https://en.wikipedia.org/wiki/Weiler%E2%80%93Atherton_clipping_algorithm https://en.wikipedia.org/wiki/Greiner%E2%80%93Hormann_clipping_algorithm https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm#Pseudocode But before we go ahead and try to translate the pseudocode found on the Wikipedia page to C++ code, we need to keep a few things in mind: Dynamic lists lead to **malloc()** calls for each added element, which is bad for real-time applications. We need to replace it with a fixed-size stack. But how? We are dealing with triangles and triangles only, so we only need to account for **three** inputs every single time. We are limited to only 6 clip edges: `{ -1, 0, 0, 1 }`, `{ 1, 0, 0, 1 }`, `{ 0, -1, 0, 1 }`, `{ 0, 1, 0, 1 }`, `{ 0, 0,0 -1, 1 }` and `{ 0, 0,0 1, 1 }` Within the process of clipping for a single edge, for every vertex, the algorithm is guaranteed to push either one or two vertices onto the stack. But we need to run the aforementioned loop for every edge we wish to clip the triangle against, which is to say, 6 times. Worst case scenario is `6*6=36` vertices? Because every iteration of the algorithm *(we run as many iterations as we have clipping edges)*, uses the previous iteration of the algorithm was its input list, we need to maintain two fixed-size stacks. So, with this in mind, we'll create a helper template. #ifndef FIXEDSTACK_HPP #define FIXEDSTACK_HPP #include <array> #include <cstring> template <typename T, size_t siz> struct FixedStack { public: typedef std::array<T,siz> array_type; typedef T* iterator; typedef const T* const_iterator; private: size_t stackPointer; array_type storage; public: FixedStack() : stackPointer(0) { } FixedStack(const FixedStack& cpy) : stackPointer(cpy.stackPointer) { std::memcpy(storage.data(),cpy.storage.data(),sizeof(T) * cpy.stackPointer); } FixedStack& operator=(const FixedStack& cpy) { this->stackPointer = cpy.stackPointer; std::memcpy(storage.data(),cpy.storage.data(),sizeof(T) * cpy.stackPointer); return *this; } void push_back(const T& val) { storage[stackPointer] = val; ++stackPointer; } void push_back(T&& val) { storage[stackPointer] = std::move(val); ++stackPointer; } iterator begin() { return storage.data(); } const_iterator begin() const { return storage.data(); } iterator end() { return &storage[stackPointer]; } const_iterator end() const { return &storage[stackPointer]; } void clear() { stackPointer = 0; } size_t size() const { return stackPointer; } T& get(size_t i) { return storage[i]; } T& operator[](size_t i) { return storage[i]; } const T& get(size_t i) const { return storage[i]; } const T& operator[](size_t i) const { return storage[i]; } }; #endif // FIXEDSTACK_HPP Now we can go ahead and create yet another helper class: the **VertexClipper**. Okay, so we need to clip three vertices to eight edges, right? What's so hard about that? Let's just implement the Sutherland–Hodgman algorithm and get over i! https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm Luckily, someone else already did the bulk of the dirty work for us, so what we have to do is take a good luck at his work, and modify it with the optimization I mentioned before *(using a fixed-size stack instead of a dynamic list)*. https://trenki2.github.io/blog/2017/07/03/developing-a-software-renderer-part4/ #ifndef VERTEXCLIPPER_HPP #define VERTEXCLIPPER_HPP #include "FixedStack.hpp" #include <glm/glm.hpp> template <typename T> int sgn(T val) { return (T(0) < val) - (val < T(0)); } template <typename VertexType> struct VertexClipper { typedef FixedStack<VertexType,36> VertexStack; typedef FixedStack<unsigned,36> IndexStack; IndexStack inputList, outputList; VertexStack vertices; glm::vec4 getClippingEdge(int i) { switch (i) { case 0: return glm::vec4(-1, 0, 0, 1); case 1: return glm::vec4( 1, 0, 0, 1); case 2: return glm::vec4( 0,-1, 0, 1); case 3: return glm::vec4( 0, 1, 0, 1); case 4: return glm::vec4( 0, 0,-1, 1); case 5: return glm::vec4( 0, 0, 1, 1); default: return glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); } } void clipToEdge(const glm::vec4& clippingEdge) { if(inputList.size() < 3) return; outputList.clear(); unsigned idxPrev = inputList[0]; outputList.push_back(idxPrev); const VertexType& vertPrev = vertices[idxPrev]; float dpPrev = (clippingEdge.x * vertPrev.POS.x) + (clippingEdge.y * vertPrev.POS.y) + (clippingEdge.z * vertPrev.POS.z) + (clippingEdge.w * vertPrev.POS.w); for(size_t j = 1; j < inputList.size(); ++j) { unsigned idx = inputList[j]; const VertexType& vert = vertices[idx]; float dp = (clippingEdge.x * vert.POS.x) + (clippingEdge.y * vert.POS.y) + (clippingEdge.z * vert.POS.z) + (clippingEdge.w * vert.POS.w); if (dpPrev >= 0) outputList.push_back(idxPrev); if (sgn(dp) != sgn(dpPrev)) { float t = dp < 0 ? dpPrev / (dpPrev - dp) : -dpPrev / (dp - dpPrev); VertexType vOut = VertexType::interpolate(vertices[idxPrev], vertices[idx], 1.0f-t, t, false); vertices.push_back(vOut); outputList.push_back((unsigned)(vertices.size() - 1)); } idxPrev = idx; dpPrev = dp; } inputList = outputList; } void clipTriangle(const VertexType& v0, const VertexType& v1, const VertexType& v2) { vertices.clear(); vertices.push_back(v0); vertices.push_back(v1); vertices.push_back(v2); inputList.clear(); outputList.clear(); inputList.push_back(0); inputList.push_back(1); inputList.push_back(2); for(int i = 0; i < 6; ++i) { clipToEdge(getClippingEdge(i)); } } }; #endif // VERTEXCLIPPER_HPP There's quite a lot to unpack here, and I'll try my best: We have our six clipping edges: `{ -1, 0, 0, 1 }`, `{ 1, 0, 0, 1 }`, `{ 0, -1, 0, 1 }`, `{ 0, 1, 0, 1 }`, `{ 0, 0,0 -1, 1 }` and `{ 0, 0,0 1, 1 }` We first push our vertices to the stack, along with the indices. For each of the six clipping edges, we run the following algorithm: If we have less than 3 inputs, we don't do anything. Otherwise we clear the ouput stack. We take the very first index from the output list, and use as the first "previous index" variable. It's gonna be important later on. For every index on the input stack, we do this: We calculate the dot products of the clipping edge with the vertices pointed to by the previous index and the current index. If the previous index's vertex is inside the edge *(has a positive or zero dot product)*, we push it onto the output stack. If the sign of the two dot products are different, calculate the intersection between the previous vertex and the current vertex, interpolating to the point where it's on the clipping edge. We then push this new vertex onto the vertex stack, and its index onto the output stack. The next iteration's previous index is the current index we operated on in this iteration. We copy the previous iteration's outputs to our inputs. Oh great, one more functions we'll have to add to our vertex output class. Ugh. We need an overload of the **interpolate()** function to interpolate between only two vertices, not three. Okay, that's trivial enough, we just copy-paste our existing interpolate functions, and simply remove parts of it. Except there is one problem. A major problem. Everything at its apropriate time So far, our vertex shaders have produced screen-space coordinates for us, which has worked well for us so far. However, now it has come to majorly bite us in the behind, because this whole clipping thing is not going to work with purely signed numbers. We'll need normalized numbers between 1 and -1. We'll need clip-space coordinates. This means some modifications of our pipelines. Especially our vertex shaders. This means that our vertex shaders will no longer take viewports as arguments, as they will output heterogenous coordinates. BasicVertexOut basicVertexShader(const BasicUniform &uniform, const BasicVertexIn &vertex) { BasicVertexOut out = { glm::vec4(vertex.POS,0.5f, 1.0f) , vertex.COLOUR }; return out; } This obviously means saying goodbye to our trusty old **glm::project()** function too. https://glm.g-truc.net/0.9.9/api/a00666.html#gaf36e96033f456659e6705472a06b6e11 ModelVertexOut ModelVertexShader(const ModelUniform &uniform, const ModelVertexIn &vertex) { glm::vec4 tmp = glm::vec4(vertex.POS,1.0f); tmp = uniform.model * tmp; tmp = uniform.projection * tmp; ModelVertexOut out{ tmp, vertex.TEXCOORD, vertex.COLOUR }; return out; } Basically, we do by hand what **glm::project()** did for us *"automatically"*, minus the W-divison and the conversion to screenspace. https://glm.g-truc.net/0.9.9/api/a00666.html#gaf36e96033f456659e6705472a06b6e11 Obviously, now we'll have to do the conversion to screenspace in the pipeline template, but before that, we'll do the clipping. void renderTriangle(const VertexInType& i0, const VertexInType& i1, const VertexInType& i2) { const VertexOutType o0 = vert(uniform,i0); const VertexOutType o1 = vert(uniform,i1); const VertexOutType o2 = vert(uniform,i2); if( ((o0.POS.x > 1.0f || o0.POS.x < -1.0) && (o0.POS.y > 1.0f || o0.POS.y < -1.0) && (o0.POS.z > 1.0f || o0.POS.z < 0.0)) && ((o1.POS.x > 1.0f || o1.POS.x < -1.0) && (o1.POS.y > 1.0f || o1.POS.y < -1.0) && (o1.POS.z > 1.0f || o1.POS.z < 0.0)) && ((o2.POS.x > 1.0f || o2.POS.x < -1.0) && (o2.POS.y > 1.0f || o2.POS.y < -1.0) && (o2.POS.z > 1.0f || o2.POS.z < 0.0)) ) return; clipper.clipTriangle(o0,o1,o2); for(size_t i = 0; i < clipper.vertices.size(); ++i) { // Convert to screenspace. clipper.vertices[i].POS.x /= clipper.vertices[i].POS.w; clipper.vertices[i].POS.y /= clipper.vertices[i].POS.w; clipper.vertices[i].POS.z /= clipper.vertices[i].POS.w; clipper.vertices[i].POS.x = (((clipper.vertices[i].POS.x + 1.0f) * 0.5f) * float(viewport[2]-viewport[0])) + float(viewport[0]); clipper.vertices[i].POS.y = (((clipper.vertices[i].POS.y - 1.0f) * -0.5f) * float(viewport[3]-viewport[1])) + float(viewport[1]); if(perspectiveCorrection) clipper.vertices[i].perspectiveCorrect(); } indOut.clear(); indOut.push_back(clipper.inputList[0]); indOut.push_back(clipper.inputList[1]); indOut.push_back(clipper.inputList[2]); for (size_t idx = 3; idx < clipper.inputList.size(); ++idx) { indOut.push_back(clipper.inputList[0]); indOut.push_back(clipper.inputList[idx - 1]); indOut.push_back(clipper.inputList[idx]); } for(size_t i = 0; i < indOut.size(); i+= 3) { // Finally, we got to render them rasterize(clipper.vertices[indOut[i]],clipper.vertices[indOut[i+1]],clipper.vertices[indOut[i+2]]); } } And, we've got clipping! It's not perfect, and we still get some artifacts, but it's much better than before, and at least we no longer get funky effects when we get too close. However, before we truly wrap up, there is one more subject we need to cover. Alpha-Blending So far, all shapes that we rendered we opaque. But what if we wanted to render transparent objects with transparent textures, like this? Well, if we just try to render it, we get this: Doesn't look quite right, now does it? We'll need to do alpha-blending. #ifndef TEXTURE_HPP #define TEXTURE_HPP #include <glm/glm.hpp> enum TextureFiltering { NEAREST_NEIGHBOUR, DITHERED, BILINEAR }; enum AlphaBlending { ALPHA_TESTING, ALPHA_DITHERING, ALPHA_BLENDING }; class Texture { public: virtual ~Texture() = default; // Data getters virtual int getWidth() const = 0; virtual float getWidthF() const = 0; virtual int getHeight() const = 0; virtual float getHeightF() const = 0; virtual int getStride() const = 0; // Pixel manipulation virtual void getPixel(const glm::ivec2& pos, glm::vec4& colourKernel) const = 0; inline glm::vec4 getPixel(const glm::ivec2& pos) const { glm::vec4 tmp; getPixel(pos,tmp); return tmp; } virtual void setPixel(const glm::ivec2& pos, const glm::vec4& colourKernel) = 0; virtual void setPixelDithered(const glm::ivec2& pos, const glm::ivec2& screenpos, const glm::vec4& colourKernel) = 0; bool setPixelWithBlending(const glm::ivec2& pos, const glm::ivec2& screenpos, const glm::vec4& colourKernel, AlphaBlending blendingType); virtual void* getRawPixels() = 0; virtual const void* getRawPixels() const = 0; virtual void sample(const glm::vec2& pos, const glm::ivec2& screenpos, TextureFiltering filteringType, glm::vec4& colourKernel) const; inline glm::vec4 sample(const glm::vec2& pos, const glm::ivec2& screenpos, TextureFiltering filteringType) const { glm::vec4 tmp; sample(pos,screenpos,filteringType,tmp); return tmp; } virtual void clearToColour(const glm::vec4& colourKernel) = 0; }; #endif // TEXTURE_HPP const glm::vec4 thresholdMatrix[4] = { glm::vec4(1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0), glm::vec4(13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0), glm::vec4(4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0), glm::vec4(16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0) }; static const glm::vec2 LOOKUP[2][2] = { { glm::vec2( 0.25f, 0.00f ), glm::vec2( 0.50f, 0.75f ) }, { glm::vec2( 0.75f, 0.50f ), glm::vec2( 0.00f, 0.25f ) } }; bool Texture::setPixelWithBlending(const glm::ivec2 &pos, const glm::ivec2 &screenpos, const glm::vec4 &colourKernel, AlphaBlending blendingType) { float a = colourKernel.a; switch (blendingType) { case ALPHA_TESTING: { if(a >= 0.99607843137255f) { setPixelDithered(pos,screenpos,colourKernel); return true; } else return false; } case ALPHA_DITHERING: { a = (a >= thresholdMatrix[screenpos.x % 4][screenpos.y % 4]) ? 1.0f : 0.0f; if(a >= 0.99607843137255f) { setPixelDithered(pos,screenpos,colourKernel); return true; } else return false; } case ALPHA_BLENDING: { if(a >= 0.99607843137255f) { setPixelDithered(pos,screenpos,colourKernel); return true; } else if(a >= 0.003921568627451) { return false; } else { glm::vec4 kernel = getPixel(pos); const float rem = 1.0f - a; kernel.r = (kernel.r * rem) + (colourKernel.r * a); kernel.g = (kernel.g * rem) + (colourKernel.g * a); kernel.b = (kernel.b * rem) + (colourKernel.b * a); kernel.a = std::min(1.0f,kernel.a + a); setPixelDithered(pos,screenpos,kernel); return true; } } } } So, what's there to unpack? We defined three alpha-blending modes: **Alpha-testing**, **alpha-dithering** and **alpha-blending**. **Alpha-testing** simply tests if the new pixel is above a certain threshold, and overwrites the old pixel if it is. If the alpha is above that threshold, we can overwrite the Z-buffer variable too. This means we don't need to order our triangles. **Alpha-blending** actually blends the two colours, if the alpha value is anything other than 1 or 0 - that is, anywhere between 1 and 0. But how do we know if we need to overwrite the Z-buffer variable, or don't? Generally, the rule of thumb is to write to the Z-buffer if the alpha is above 0, but then we have to render opaque objects before transparent objects, and render transparent objects sorted by Z. Yikes. **Alpha-dithering** fakes alpha-blending by dithering the alpha and reducing it to 1-bit. It also ensures that we have either completely overwritten a pixel, or kept the old one, ensuring that we don't need to sort any objects, and we can render in any speed we want. We implemented said modes. But this also requires a slight modification of our fragment shader, as well as our uniform. struct ModelUniform { ZBuffer* zbuff; std::shared_ptr<Texture> tex; TextureFiltering filtering; AlphaBlending blending; glm::mat4 model; glm::mat4 projection; }; void ModelFragmentShader(Texture &framebuffer, const glm::ivec2 &point, const ModelUniform &uniform, const ModelVertexOut &v0) { if(!uniform.tex) return; if(point.x < 0 || point.y < 0) return; float& zbuffpoint = uniform.zbuff->get(point.x,point.y); if(v0.POS.z >= 0.0f && v0.POS.z <= zbuffpoint) { glm::vec4 colourKernel = uniform.tex->sample(v0.TEXCOORD,point,uniform.filtering) * v0.COLOUR; if(framebuffer.setPixelWithBlending(point,point,colourKernel,uniform.blending)) zbuffpoint = v0.POS.z; } } Aaaand, after setting the renderer to use alpha-dithering, we got alpha-dithering! And that about wraps up the series about software rendering. As usual, the code in the tutorial is uploaded to Github. If it is requested, I may write a tutorial about Vulkan or OpenGL, or even a second software rendering series. Until then, please donate. https://github.com/Metalhead33/SoftwareRendererTutorial Outro Congratulations! Hopefully, now you learned how to write a software renderer, or at the very least, how the GPU works under the hood! Obviously, there are many ways this software renderer could be improved: Using fixed-point numbers instead of floating-point numbers can speed up performance, as Geri said. https://read.cash/@Geri/software-rendering-is-better-than-directx-or-opengl-d94704d6#integers **De-generalization:** This software renderer uses *"shaders"*, which requires it to be generic. But if it was turned into a completely fixed-function pipeline specialized for a pre-set task like an existing game *(engine)* instead of desperately trying to emulate the GPU, it could be optimized nicely. **Implementing aggressive triangle-culling:** because clipping is not enough. More optimized code overall.

+34 more

@Metalhead33

Get into crypto - while you can ****Warning!**** **This article was written by me a very long time ago, and no longer accurately reflects my views! I am no longer into crypto. Reader discretion is advised.** This article isn't a tutorial on how to get into trading with cryptocurrencies and investing. If you are already convinced, just go Google the relevant question. This article is for the naysayers who still doubt the potential of crypto, and still need convincing. To do the convincing, I'll tell all the naysayers my personal story: My life for seven coins! My day job currently earns me 200 000 HUF per month, which is not too shabby by Hungarian standards. As of the time of writing this article - 2021, 20th of April - the exchange rate between the HUF and USD is so that a single American dollar can be exchanged for 299.95 HUF, which means that my monthly salary is roughly 666.77 USD. How the hell is that *"not too shabby"*?! That's little more than half of the average American's rent *(1 120$ in 2020)*! And therein lies the deception. Yes, if you take my 200 000 HUF and get it exchanged for USD, you'll only get 666.77 USD, but this is deceptive: costs of living are much lower in Hungary than in America. Before me and my siblings sold my dead father's apartment room, we used to rent it for 30 000 HUF a month, which is just 100$ using today's rates of exchange. In other words, controlling for costs of living instead of relying purely on the official rate of exchange, my monthly salary of 200 000 HUF isn't really comparable to 666$, but more to 2000$. Where was I going again? So yes, my monthly salary is 200 000 HUF *(666.77 USD)*, so my yearly salary is 2 400 000 *(8001.24 USD)*. I am 28 years old as of the time of writing the article, so if we're being generous and assume that I'm going to live to the age of 78 *(very unlikely, given my obesity and other factors)*, then, for 50 years, my salary would be 120 000 000 HUF *(400 062 USD)*. As of the time of writing this article, 400 062 USD could buy me 7.09 bitcoins. Why is this so relevant? Well, you see... A wasted opportunity In the December of 2011, a single bitcoin had the cost of a measly 2 USD, and a single American dollar could be exchanged for 224.85 HUF. In other words, the aforementioned 7.09 bitcoins would have set me back by a measly 14.18 USD *(3 188.373 HUF)*, but let's round it up to 15 USD *(3 372.75 HUF)*. That's right. An investment of a puny 15 USD - or 3.4k HUF - in the December of 2011 would have secured me enough bitcoins to pay for my 50-year retirement fund in 2021 *(400 062 USD)*, assuming that I would have held onto the bitcoins until the April of 2021, and only sold them now. But alas, in 2011, I was not even aware of the existence of bitcoin to begin with, let alone aware of the future potential behind cryptocurrency. Obviously, what I neglected to mention so far, that before 2014, you couldn't just buy bitcoins - or other cryptocurrencies - like you can today, at least, it wasn't as nearly convenient as it is today. Sure, there were a few *(rather fraudulent)* exchanges, but for the most part, you had to either mine them or get them from faucets. But that is completely besides the point. The point is, that if I had the same amount of foresight in 2011 as I have hindsight in 2021, I would be wealthy enough to retire for life - after all, everyone who reads my articles knows, that I'm not too fond of work. The moral of the story So what is the moral of my personal tragedy? What is there to be learned from my story? **I personally think that it's quite simple:** Always do your research, don't be ignorant of new things. You may just learn about a goldmine hiding in plain sight. Because that's what bitcoin was back in the days, before it got famous. Some investments are very long-term, as in, 10-year or more. Don't get impatient. Hold onto your crypto. Don't sell it after the few rise in its price. Those couple of sh*tcoins you just bought might be your retirement fund one day. Get into things while you can. Because if you wait too long, it might be late. I only got into cryptocurrencies in 2021, and it's highly unlikely that I'll be trading and speculating myself into an early retirement any time soon. However, I'm still investing in hopes of - even if marginal - returns. When something new comes up, don't underestimate its potential. People called DogeCoin just a meme, and look where it is now: worth one third of a dollar. He who dares wins. Without knowledge of the future, blowing all your lifesavings on one cryptocurrency or memecoin is a terrible decision. But, in hindsight, if you spent all your life savings on buying bitcoin back in 2013, you'd be a billionaire by 2021. **Get into crypto, because you can.**

+2 more

@Metalhead33

A different point of view Anyone who reads my articles may have noticed a common theme: I often talk about there being two good extremes, and one bad, cursed middle-ground, that combines the worst of both worlds. No different is work. Anyone who has read my articles - the ones not about 3D graphics and software rendering - will know that I'm not too fond of the very concept of work to begin with, and that's quite the understatement. But, just to please those who have read my articles and are getting bored of every single one of them turning into pro-UBI propaganda by the end, I will resist the urge to further elaborate on that. https://read.cash/@Metalhead33/in-defense-of-scammers-dcfd55ae#but-metalhead33-why-do-you-hate-being-a-wageslave-so-much So, what are the two extremes of work, and why are we in a cursed middle-ground? The employer's perspecetive People often accuse me of not being able to put myself into the shoes of others, but that simply isn't true. I definitely **can** put myself into the shoes of others, and I'm going to put myself into the shoes of the employer now. You see, from the employer's perspective, the idea of a monthly or hourly wage makes no sense. If I'm employing you, I'm not paying you - or rather, I don't want to be paying you - just for being physically present in the office, factory or field or whatever. I want to pay you for delivering results. If I hire someone to pick strawberries, I'm not hiring them to do it for 8 hours a day - I'm hiring them for picking X-amount of strawberries. I want to pay you per kilogram of strawberries picked, not per hour you spent picking the strawberries. I don't care if you fulfill your daily quota under 8 hours, 4 hours or even 1 hour, as long as you do it. Once you picked it, you're free to go home, or work for extra. Likewise, if I had my way, a programmer wouldn't be told *"work on this piece of software for 8 hours every day"* - a programmer would be told "*deliver till the deadline, and if you are super-fast, the time between you finishing and the deadline is free time"*. Again, if I was an employer, I would want to pay people for results, not for hours. Paying people for hours encourages workers to stretch the work out and be lazy. Paying people for results encourages workers to finish the job as fast as possible, then go home. But you see, there is one fatal flaw within this kind of thinking... The employee's perspective One of the most childish delusions of corporate suits is the idea that us employees are loyal vassals. No, we're not. We're mercenaries. I'm yours for 8 hours, after that, kindly bugger off, buddy - unless you agree to pay me for overtime. I have **zero** loyalty to any company I work for - they might fire me if I said it to them out loud, but I'll write it here anyway: I'm only in it for the money. https://tvtropes.org/pmwiki/pmwiki.php/Main/OnlyInItForTheMoney You see, from our perspective, time is money. We're trading away our time *- time we could be spending with our family, having fun, doing something we actually enjoy doing, etc. -* for money. So, if commutes turn my de jure 8-hour workday into a de facto 12-hour workday *(which they did before the pandemic convinced everyone to convert to home office - God bless Covid-19)*, that ought to be compensated somehow. Because that extra 4 hours isn't free time for me. I spend half of it traveling to **work**, and the other half of it going home from **work**. Work is the common denominator, and I consider it part of work. In fact, if I fully had my way, I ought to be compensated for even thinking about work outside of official work hours, because that is a clear invasion of my free time and peace of mind by **work**. There is just one major flaw within my more worker-friendly approach: if employers had to compensate their employees for the time spent traveling to work, they would **never** hire anyone who lives in a rural area *(except for remote work, which only applies to a small fraction jobs as of 2021)*, anyone who lives 1-2 hours away from the inner city, etc. This would exacerbate the already prominent chicken-and-egg problem that plagues the European countryside: all the young people are moving to the cities because that's where all the jobs are *(the ones that pay a livable wage, that is)*, and all the jobs are in the cities because all the young people are moving into the cities, thus villages get deserted. The cursed middle-ground What we have today, is the cursed middle-ground that pleases no one. Employees spend hours commuting to work, and are paid only for the time they physically spend in the office, which is bad for employees. But it's bad for the employers too, because the employees are paid for time spent at the workplace, rather than actual productive work and results, so employees are encouraged to stretch out the work and generally be lazy about it. It's literally the worst of both worlds. Is there a solution? This is the point where I'd normally derail the article into more pro-UBI propaganda, but since that's getting boring, I'll have to provide other alternatives, other potential solutions: **Remote work.** If there is no real need for your to be physically present at the workplace to do your job, then why even force you to commute to work? For everyone whose job doesn't involve physically touching anything other than a computer, remote work should always be offered as an option. **Decentralization and** **deurbanization****.** Right now, much of the economical activity is centralized in the largest cities. But what if it wasn't? https://en.wikipedia.org/wiki/Counterurbanization The real culprit might be mega-corporations. Small, family-owned workshops simply can't compete. Sadly, you require starting capital to even life off the land. If you don't own land and don't have money to buy land, tough luck trying to be a self-sustaining farmer. The government doesn't treat kindly those who illegally occupy even unused land. Sadly, homesteading is impossible in most developed countries as of 2021. https://read.cash/@Metalhead33/in-defense-of-scammers-dcfd55ae#a-world-half-full-a-world-half-empty You can't really decentralize the economy without either a major catastrophe ruining all the major businesses, or some Communist wealth redistribution, which I oppose out of principle. **Business deregulation.** Because regulating businesses is exactly what got us into this mess. If there was no regulation, and you were allowed to set up business wherever you please, it would be much easier to set up shop in your home town and create jobs for the locals. This might backfire though, if we do it *ceteris paribus* and don't change other aspects of the system at the same time: under the current system, corporations have every incentive to concentrate their assets in urban areas, no matter the amount of regulation. https://en.wikipedia.org/wiki/Ceteris_paribus **Alternative economics.** Obviously, the elephant in the room that I'm resisting the urge to mention is one of them, but surely, there are other ways of achieving a more localized economy where people aren't forced to travel 50+ kilometres from their home every day just to put bread on the table. https://read.cash/@Metalhead33/consent-23a8f725#ubi-the-great-equalizer https://read.cash/@Metalhead33/freedom-462857a3#ubi-the-great-reconciliator There's Mutualism, Agorism, Distributism and some even want to revive guilds. Sadly, all of these ideas would require a massive sweeping change, either through the abolition of the state, or through a semi-apocalyptic cataclysm that does the dirty work for us. https://en.wikipedia.org/wiki/Mutualism_(economic_theory) https://en.wikipedia.org/wiki/Agorism https://en.wikipedia.org/wiki/Distributism https://en.wikipedia.org/wiki/Guild_socialism Or just homesteading. But again, that would require us to cut the government down the size. Not that I'm complaining, since I'm very anti-statist to begin with. https://en.wikipedia.org/wiki/Homestead_principle **The Amish might be onto something.** They have a strong communal identity and emphasize self-sufficiency. It's a common misconception that the Amish hate technology. **They don't.** They just oppose anything that makes them dependent on outsiders. An Amish blacksmith can easily fix a broken horse cart or make a new one - an automobile, not so much. So, why didn't I mention the government's intervention as a possible solution? Is it just because I'm such an anti-statist? Yes and no. I'm too jaded and cynical to put my faith in any government-sponsored *"save our villages!"* initiative. Whenever the government tries to desperately go against the wind and artificially redirect it, it always fails, or at the very least backfires. When the government sponsors movies and songs for a certain cause, youngsters always consider it *"cringe"* and favour the non-governmental alternatives. **So the solution has to be organic change, rather than government intervention.**

+5 more

@Metalhead33

Freedom Everyone claims to love freedom. Who doesn't want freedom? Who doesn't want to be able to do whatever they want without fear of consequences? That's absolute freedom. But there is no absolute freedom. First of all, physics, chemistry and logic will stop you from doing literally what you want. But second of all, if you have the freedom to kill someone, you basically have the freedom to reduce someone else's freedom to zero. That means that your absolute freedom is at the expense of someone else's freedom. Compromise is necessary. But pretty much everyone knows this already, and what I just wrote is nothing new. So, with absolute freedom out of the way, what does freedom actually mean in a society? Well, it depends on your political views... As written in a previous article of mine, the concept of simplicity is a matter of a point of view, and depending on the point of view, it can actually mean two completely opposite things. From the point of view of a software developer - like me - a *"simple"* program is one that requires the least amount of code to implement, but from the user's point of view, that results in a complex, hard-to-use program where the user has to manually input all sorts of parameters that should be self-evident... not to mention, the fact that a user expects an easy-to-use intuitive GUI, which obviously takes effort to program, and makes the codebase *"complex"* from the programmer's point of view. https://read.cash/@Metalhead33/simplicity-d7cf30a2#point-of-view With absolute freedom thrown out of the window, what we're left with is interpretations that may or may not clash with each other. So, I am going to focus on three main interpretations on freedom, each based on the extremes within the political compass. Auth-Right People on the Auth-Right believe in freedom of association, as well as collective freedom *(that is, national sovereignty)*. They hold the belief, that any attempts at stopping discrimination - whether racial, religious or sexual - inherently clash with property rights, and they obviously prioritize property rights. Under this interpretation of freedom, certain individual freedoms - such as freedom of movement and freedom of trade - clash with a nation's or collective's freedom of self-determination, i. e. deciding whether they want someone or some type of foreigner to live amongst them. Lib-Right People on the Lib-Right focus on freedom of commerce, freedom of business, the complete or near-complete absence of regulations. They believe, that business regulations clash with their property rights, and they obviously prioritize property rights. Under this interpretation of freedom, certain individual freedoms - such as one's right to self-determination - clash with some else's property rights, e.g. in case you own slaves. It may sound cold to many, but yes, under this interpretation of freedom, employers absolutely have the right to make you work in terrible working conditions *(though you always have the right to just quit)*, because forcing employers would need to be coerced into improving working conditions at the expense of their profits. Basically, under the Lib-Right interpretation of freedom, freedom is effectively synonymous with private property rights and the right not to be coerced by any third parties - especially the government - out of your own property. Auth-Left and Lib-Left These two quadrants don't differ much in their interpretation of *"freedom"*, except maybe that Auth-Left maybe focuses more on basic elemental human needs, while Lib-Left focuses more on the freedom to do certain actions *(e.g. freedom of movement)*. However, on both of them, there is a focus on freedom of choice. Despite **not** being a leftist myself *(quite the opposite, I'm on the Lib-Right)*, my previous article's exploration of the concept of consent might hit very close to home for a lot of Leftists, because ultimately, they're saying the same thing that I am: if you are forced to make a binary choice between an unpleasant existence *(work)* and death *(by starvation, because no money = no food = you starve to death)*, you don't really have a freedom of choice. Yes, on paper, you do have a choice, but unless you have a death wish, you don't really have a choice. https://read.cash/@Metalhead33/consent-23a8f725#sex-and-work However, just like absolute freedom, this kind of freedom comes at the expense of others' freedom. If you are to be freed from the negative consequences of refusing to work, someone has to be forced to produce the food and shelter that you require to survive *(or we just rely on robots and have* *Fully-Automated Luxury Gay Space Communism**)*, while you give nothing back. Either that, or those who work have to be taxed to make sure that you don't suffer the consequences of your choice. And that clearly violates the freedoms of others. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4#okay-we-just-implemented-fully-automated-post-scarcity-luxury-gay-space-communism-now-what Lib-Left also puts a strong emphasis on freedom of movement, which clashes with property rights and the rights of collectives to determine whether someone is fit to live among them or not. Can they be reconciled? Well, if you compromise - sure, to a degree, you can. The Auth-Right and Lib-Right interpretations of freedom certainly don't clash, or don't seem to clash at first sight *(since both put a strong emphasis on property rights)*, but they do when you factor in how much emphasis does Auth-Right put on collective rights, while Lib-Right is individualist to the core. Under the Auth-Right interpretation, a community should have the freedom to decide if they want X or Y on their streets in plain sight - while under the Lib-Right interpretation, it should be up to each individual to do whatever they want as long as they don't break the NAP *(Non-Aggression Principle - basically, don't aggro people, violence is only okay out of self-defense)*. The Auth-Right interpretation of freedom wouldn't clash with the Leftist interpretation of freedom, if you toned down the emphasis on things like freedom of movement. However, the Leftist interpretation of freedom clearly comes clashing hard with the Lib-Right interpretation, which effectively makes freedom synonymous with property rights, something the left is rather weak on *(intentional understatement)*. Except, there is one way, I can think of... UBI: The Great Reconciliator At this point, it's becoming a running gag that my articles start as something, but always end up turning into advertisement for the concept of Universal Basic Income by the end, but hey, it's relevant, isn't it? https://en.wikipedia.org/wiki/Universal_basic_income Universal Basic Income, in a way unifies - or attempts to unify and reconcile - all the previously mentioned interpretations of *"freedom"*, because for it to work, you require a system that respects and unifies all of them, save for freedom of movement. UBI obviously cannot coexist with a reckless open-border immigration policy *(if it did, it would immediately cause a huge strain on the whole system and collapse)*, so any government that implements UBI must have respect for the collective national will to have sovereignty over who is and who isn't allowed to enter and settle down in the country. And if the government is literally giving every adult citizen 1000$ per month, the fewer people the better. This means that you have to have some degree of Auth-Right streak to have UBI. UBI obviously cannot exist without a degree of respect for private property rights. The whole point of UBI, is that instead of collectivizing anyone's property, we simply directly give people money, which they can spend on growing their private property. Because UBI is implied to make welfare, pension and minimum wage redundant, you could even say that it's more of a Lib-Right policy than anything, since the recipient has full control over how they spend their money, and it's implied that things like roads and healthcare would be at least partially privatized. Seriously, even for a borderline Ancap like me *(if we temporarily disregard the fact that my support for UBI is mainly motivated by my laziness and spoiled background)*, giving everyone 1000$ per month to shut up is the lesser evil compared to all those business regulations, red tape, etc. Not to mention the amount of bureaucracy we would be rendering redundant by it. Let's just have this compromise with the leftists: they can have UBI, but in exchange for privatizing everything. The private sector does everything better than the public sector anyway. UBI cannot exist without a degree of respect for individual freedoms. If robots took all our jobs, the government could just expand the public sector massively and employ everyone in it - giving people money for no reason instead betrays a respect for individual freedoms. You get the money, and you get to decide what you spend it on. Pretty much all four quadrants of the political compass can find plenty of motivation to throw their weight behind UBI.

+6 more

@Metalhead33

Consent ****Warning!**** **This article was written by me a very long time ago, and no longer accurately reflects my views! I no longer identify as Libertarian, much less an Anarcho-Capitalist. Reader discretion is advised.** I'm a Libertarian, and I guess somewhat of an Anarcho-Capitalist too, if we discount my support for UBI. Libertarians and Anarcho-Capitalists are sadly the target of deriding memes about age of consent, and how us Libertarians all just want to have sex with children. That stereotype is obviously false, as one cannot simply make such a generalizing blanket statement about the followers of any political ideology, and even if we did want to abolish Age of Consent laws *(we don't)*, what people are doing is not genuine criticism of our ideas, but rather a knee-jerk reaction, largely motivated out of fear of social ostracism for daring to explore controversial ideas - or just inability to have an emotionally detached rational discussion about anything. The *Raison d'Être* of AoC laws It's commonly said, that children cannot consent. That statement may or may not be true, depending on how we look at it. Is it true in the literal sense? I'd hate to break it to you, but no. Does it have to be though? Also no. When we say that *"children cannot consent"*, what we actually mean, is that the power disparity between adult and child is so large, that it renders the child's consent - or lack thereof - irrelevant. When an adult wants to engage in sexual acts with a defenseless child, *(s)*he is going to be able to do it, whether the child consents or not. And because it's unreasonable to assume that a 6-year old has any idea what's going on, we can safely assume, that the 6-year old child did **not** consent. And even if they did, there's a high chance of getting physically hurt. So, when we say that children cannot consent, we don't mean it in the literal sense, but in a functional sense. Sure, they do possess the ability to consent in the literal sense, but functionally, they might as well not, due to the power imbalance rendering it*(s absence)* irrelevant. However.... there is just one tiny problem with this logic. It applies to adults too We like to think of children as these good-but-ignorant innocent angels to be contrasted with the evil-but-wise adults already jaded *and/or* corrupted by the world, but in reality, this line of thinking doesn't completely stand up to scrutiny. Yes, there is **some** truth to the idea that children are naive and charitable while adults are paranoid and miserable, but to say that children are innocent while adults are immoral is just plain untrue - children are creatures of instinct, which, just like civilized society's norms, has its light side and dark side. But before this segue gets too long, I'll just close it with this remark: children are not as innocent as you may think, and school bullying is the best proof for that. https://read.cash/@Metalhead33/does-work-ennoble-you-ea078864#quota-munka-nemesitquot Where was I going again? So yes, this whole idea about consent being dubious is not exclusive to children. It applies to adults too. We say that children cannot consent, because the power disparity between adult and child is so huge that it renders their consent - or lack thereof - irrelevant to whether the sex will happen or not, but there are a **lot** of situations where such a power disparity exists between two adults as well. Sure, the adult may have a better ability to physically resist rape, and might have slightly more confidence to say no, but what if there are negative consequences to saying no? Think of cases when a person *(usually a woman, but in rare cases a man too)* has to sleep with his/her *(future)* employer to get a job or a promotion. Or when a prostitute *(or gigolo)* has to sleep with her/his client to make a living, since that's his/her job. Is that consent? The client or *(future)* employer isn't exactly holding a gun to the subject to force the sex to happen, and is most likely not forcing themself violently onto the subject either, so you can't call it rape, but it's not exactly a textbook example of consensual sex either. Of course, if feminists had their way, even regretting sex with someone would retroactively turn the consensual *(at the time)* sex into rape, but when it comes to calling attention to this kind of power-play, they do have a point. Hey, even a broken clock is right twice a day, right? Sex and work Some would argue, if the consequences of not having sex with someone are highly negative *(e.g. not getting paid, not getting the job, not getting the promotion, etc.)*, even if that person didn't rape you in the literal sense, functionally, it was rape, or at the very least, dubious consent. As an anti-feminist, I find this line of thinking highly suspicious, but, the more I think about it, the more it starts to make sense. Hey, even a broken clock is right twice a day, right? But why stop at sex? Why not apply the same logic at other things.... like work? We think of work as something like death and urination - an inescapable fact of life that we just have to accept - but that logic falls apart when you realize that in certain cultures, having your genitalia mutilated at an early age is also an inescapable fact of life. So what if there's more to work than it just being a fact of every person's life? No one is forcing you to work in a literal sense. If you live in the developed world - or even in the majority of developing countries - you are not a slave, you are a free individual. No one is holding a gun to your head and forcing you to find a job and start working. Just like the self-employed pimpless prostitute who can reject clients at will, you too **can** reject work.... but what if you do? Then you will have no income, no means of buying food *(meaning you starve to death)*, no means of paying for water and electricity, no means of paying property tax, no means of paying for healthcare, etc. So sure, no one is holding a gun to your head in the literal sense, but functionally, it might as well be that situation, because the freedom of choice is an illusion, and there is only one viable choice that results in your prolonged existence rather than premature demise. You could say, that life is holding a gun to your head, and life itself is raping the unwilling prostitute. But what can we do? UBI: The Great Equalizer Universal Basic Income is the controversial *(but growing in popularity, especially since the Covid-19 pandemic)* idea that the state should simply unconditionally give every single citizen above 18 a certain amount of money - like $1000 - every month, no questions asked. https://en.wikipedia.org/wiki/Universal_basic_income Despite being a quasi-Anarcho-Capitalist who generally opposes the government's very existence and wants to privatize everything, I support it, not just because I'm lazy and spoiled, but also because it's pretty much the only alternative to letting 99% of the population starve to death in a post-automation world. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4#on-the-inevitability-of-ubi But besides ensuring our continued survival in a world where Terminator has taken our jobs, it also comes with a few additional advantages: It strengthens the weight of genuine consent, for both sex and work. In a UBI-less world, prostitution is often the last attempt of financially desperate women who have ran out of other options to **survive**. But in a world with UBI, only the genuine nymphomaniacs would sign up for such a job, and even then they would still have the option of declining clients without worrying about their financial well-being. In a UBI-less world, people work to live. Companies would like workers to have some corporate loyalty and live to work, but deep-down, everyone knows that us workers have no corporate loyalty and all, we work to live *(to survive)*, and not the other way around. In a world with UBI, workplaces would be rid of unenthusiastic workers like me, and would be full of willing volunteers who actually enjoy what they're doing. UBI would give more bargaining power to workers, because workplaces would have to be really enticing to get people to give up their free time for extra money, if work is made optional. UBI would also be beneficial for companies, because it would render welfare, pension, even paid leave and workers' benefits redundant. UBI would benefit **both** landlords and their tenants: Tenants wouldn't have to fear not being able to pay rent, assuming that UBI more or less covers the rent of the average citizen. Landlords would be never taking risks by renting out their houses and apartments, because UBI would ensure that everyone - except the most foolish and financially inept - can always pay their rents, no excuses. If the state gives you $1000 per month, you have literally no excuses for being late with the rent. This in turn would remove the landlords' motivation for charging high prices. In the present-day world, high rent is basically compensation for high risk being taken by the landlord. But with UBI, the high risk disappears, and so will the high rent, since it will literally become counterproductive at that point *(renting out your home at a reasonable price that everyone's UBI covers vs renting out at a price no one is willing to pay, and losing money as a result - it's a no brainer, really)*. UBI would benefit big corporations by ensuring that they will continue to have a steady stream of consumers even after all their jobs get destroyed by automation. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4#on-the-inevitability-of-ubi Making waged work optional would be a godsend for all the non-profit good samaritans, who just want to cook soup for the poor, plant trees, and whatnot. UBI would also make copyright even more redundant than it already is. Once you have your recording equipment, your survival no longer depends on you being able to sell or license your works. Hell, with UBI, you may even get more patronage then ever. https://read.cash/@Metalhead33/is-copyright-maintainable-f023b710#a-world-before-and-after-copyright

+6 more

@Metalhead33

Why fantasy villages shouldn't have inns - or, why writers can't do economics In just about every fictional medieval fantasy setting, arguably the most booming and economically profitable industry is the hospitality industry. Don't believe me? There's a tavern in just about every village, no matter how backwater or isolated said village is. If you think that doesn't sound ridiculous, then, please, just think about it for a minute. the modern-day real-life analogue of that would be to have a hotel *(or at very least, a motel)* in every single hamlet, every single human settlement, no matter how few inhabitants. Doesn't sound so realistic now, does it? Supply and demand This is basically elementary-level economics. When there's a demand for something, producers will attempt to provide a supply, otherwise the existing supply simply gets more expensive. When there's too much supply with too little demand, prices should drop in theory, but economics are more complex than that. It's a chicken and egg question - which ones comes first, supply or demand? Usually, it's demand, but there are a few exceptions: for consumer goods, demand is artificially created by technological innovation. No one wanted an iPhone 40 years ago, when they didn't even exist in the first place. Yet, eventually, it was invented, supply was created before there was even any demand for it, and once it became popular, demand grew and grew. The same cannot be said for goods and services that fufill an individual's basic, elementary physiological needs, like the need for food *(to avoid starving to death)*, the need for water *(to avoid dehydrating to death)* and the need for shelter *(to avoid falling victim to the elements, such as extreme environmental temperature)*. The demand for goods and services related to these needs is already there for every human being. What kind of needs do inns and taverns service? What's their business model? These two questions are essential, when economically analyzing these two similar types of institutions. What do they do? How do they generate profit? How do they accrue capital? What is the nature of their activity? In a modern context, the words *"inn"*, *"tavern"* and *"pub"* are largely synonymous and interchangeable. However, in a pre-modern - especially medieval - setting, they were anything but. **Taverns and pubs** generate profit by selling you food and *(alcoholic)* beverage, and the temporary right to sit on a chair by a table while consuming said items. This may be augmented by services related to various social games, but this is purely optional: the main focus is on the drinks. https://en.wikipedia.org/wiki/Pub_game Okay, that's that for taverns - what do **inns** do then? Literally everything that taverns do, **plus** offering accommodation, that is, rooms for the night, shelter. That's basically the only distinction. Yet, that's a very big distinction, because it changes the whole business model. To offer accommodation, one has to have empty rooms to fill with beds, the actual beds with all the accouterments *(pillows, blankets, etc.)*, maintenance staff to keep it at least moderately hygienic *(even by medieval standards)*, and possibly even security to prevent thefts. All of these things cost money. But does that money pay off? Remember, if your expenses outweigh your income, you're not making any profit - you're going into deficit, and eventually bankruptcy. The Inn is NOT a monopoly The inn does not have a monopoly on accommodation. Yes, I know, it sounds crazy, but really, it does not. Our group of adventurers arrives at a village, or even a smaller rural town. They need a place to sleep. What do they do? The obvious answer might be *"book a room at the inn"*, but there are several alternatives, pretty much all of them cheaper than an inn: Crash at a friend's place, if you have local friends. Or, literally just pay any person with a large enough house. Go sleep in a barn. The barn's owner - or their children - will probably want to hear your stories, and might let you stay for free. Even if you have to pay to the barn's owner for the lodging, it's cheaper than an inn, albeit the quality of service is near-nonexistent. Go camp outside *(only viable if you have camping equipment, duh)*, albeit if you camp too close to human settlement, the guards might enforce their anti-vagrancy laws on you - not to mention, you might get exposed to all sorts of dangers in the night. If you're on a quest for the government, the government might arrange for accommodations for you, on the taxpayer's expense. As you can see, the inn has plenty of competition. To be inviting to its intended clients, it has to provide a superior service at a lower cost-to-quality ratio than its competitors. It's hard not to be better than the cold hard ground, but to a party that already has access to a barn, that inn bed better be warm and comfy to make it worth its price. It does not compute Okay, so let's say, that your inn offers pristine services at a cheap price. You offer merchants, traveling nobles and weary adventurers a warm and comfortable bed for an unrealistically low price. What then? **You still need customers.** If your village is isolated, backwards, or otherwise doesn't see too much traffic, it makes zero sense to have an inn. But if your village sees so much traffic, then why is it still a village? A small fishing village sitting between rock mountains and an ocean is not going to have an inn. A town or city located on a river delta flowing into the ocean, connected to other towns that produce lots of goods, however, will have plenty of inns to accommodate merchants coming from those places, and abroad as well. If we take our fantasy adventurer heroes out of the equation, a human settlement's likelihood of having even one inn to begin with - let alone multiple - is directly correlated with the amount of economical activity that takes place in said settlement, especially if said economical activity is trade. Don't you have it backwards though? I just said that large settlements *(towns and cities)* should have inns, while small settlements *(villages)* shouldn't have inns, because they don't feature much economical activity, and the typical merchant - or adventurer - passing through will likely just be fine with the local barn. Okay, but aren't we now putting the cart in front of the horse? It's not that villages have low economical throughput because they're villages, but actually, the other way around: villages remain villages due to the low economical activity. Villages actively engaged in lots of trade - whether domestic or foreign - **will**, inevitably, grow into towns, and eventually cities. If a new trade route comes into existence, and suddenly lots of merchants are constantly passing through a village, the local barn-owners may eventually get fed up with constantly having to lodge travelers in their barns, and we may see the building of some inns to accommodate for them. This creates new jobs, which sucks people from the surrounding rural areas into the growing town, and as population *(density)* increases, the aforementioned village gradually grows into a town, and eventually a city, partially due to the hospitality industry. So obviously, not every village should be bereft an inn - the ones in the process of growing into towns due to trade, obviously should have **inns**. However, villages that sit in the middle of the empty land and barely have any traffic at all, should **not** have any inns. As a general rule of thumb, villages located by rivers are far more likely candidates for growing into towns *(and thus having inns)*, than villages having no water connections. It makes for more interesting scenarios You all meet in an inn. It's such a cliché, that there's literally a TvTropes page about it. Obviously, tropes are just tools, so a single cliché shouldn't ruin anything by itself. However, it is such an overused trope, that surely, we can find something more exciting by exploring the alternatives. https://tvtropes.org/pmwiki/pmwiki.php/Main/YouAllMeetInAnInn https://tvtropes.org/pmwiki/pmwiki.php/Administrivia/TropesAreTools Inns inherently *"gamify"* roleplaying sessions and stories, even when said sessions or stories are intended to be realistic. If we take the ubiquity of inns out of the equation, and try to approximate reality better what we have is: Adventurers having to account for the dangers of wilderness not just when actively adventuring, but also when camping, sleeping. Adventurers having to actually engage with *(NPCs who approximate)* real people, use their social skills to bargain the price of staying at the barn by the night. Adventurers having opportunities to brag about their adventures to the owners of the barn, or their children The relationships between adventurers and the quest-givers being more fleshed out. When the government hires the adventurers to do something somewhere, does the government cover accommodation costs? Does the King give you a certificate to show at a local castle to let you sleep in a reserved room in the castle? Adventurers having to be picky about where they are adventuring. If you go adventuring in a backwater rural area, you better get used to there being no inns, and instead having to convince some local farmer to let you crash at his barn. Meanwhile, if you go to a city, having a local friend with a big house might be a cheaper alternative to going to an inn. So why not try it?

+6 more

@Metalhead33

Software Rendering 3: Textures and more colours In our previous episode, we managed to render our first triangle, and I promised that in this episode, we'll get into texturing, more complex shapes, Z-Buffering and all that fine stuff. So naturally, I'm here to deliver on that promise.... at least, partially. Today, we'll be discussing textures, which are an integral part of any graphical applications. https://read.cash/@Metalhead33/software-rendering-2-pipelines-and-triangles-f4379638 Textured Rectangle Remember our trusty little texture class? Well, we can use it for more than just framebuffers. But to make our work easier, we should add a convenience function to the Texture class. #ifndef TEXTURE_HPP #define TEXTURE_HPP #include <glm/glm.hpp> enum TextureFiltering { NEAREST_NEIGHBOUR, DITHERED, BILINEAR }; class Texture { public: virtual ~Texture() = default; // Data getters virtual int getWidth() const = 0; virtual float getWidthF() const = 0; virtual int getHeight() const = 0; virtual float getHeightF() const = 0; virtual int getStride() const = 0; // Pixel manipulation virtual void getPixel(const glm::ivec2& pos, glm::vec4& colourKernel) const = 0; inline glm::vec4 getPixel(const glm::ivec2& pos) const { glm::vec4 tmp; getPixel(pos,tmp); return tmp; } virtual void setPixel(const glm::ivec2& pos, const glm::vec4& colourKernel) = 0; virtual void* getRawPixels() = 0; virtual const void* getRawPixels() const = 0; virtual void sample(const glm::vec2& pos, const glm::ivec2& screenpos, TextureFiltering filteringType, glm::vec4& colourKernel) const; inline glm::vec4 sample(const glm::vec2& pos, const glm::ivec2& screenpos, TextureFiltering filteringType) const { glm::vec4 tmp; sample(pos,screenpos,filteringType,tmp); return tmp; } }; #endif // TEXTURE_HPP #include "Texture.hpp" #include <cmath> void Texture::sample(const glm::vec2 &pos, const glm::ivec2 &screenpos, TextureFiltering filteringType, glm::vec4 &colourKernel) const { switch (filteringType) { case NEAREST_NEIGHBOUR: getPixel(glm::ivec2( int( std::round(pos.x*getWidthF())) % getWidth() , int(std::round(pos.y * getHeightF())) % getHeight() ), colourKernel); break; case DITHERED: // To be implemented later break; case BILINEAR: // To be implemented later break; default: break; } } So, what are those three types of texture filtering, and why do we need to have a screen position as an argument for sampling a texture?! **Nearest-Neighbour filtering** simply multiplies the normalized texture coordinate with the texture's dimensions and rounds it to an integer. E.g. in case of a 128x128 texture, the normalized texture coordinate `{ 0.533, 0.73 }` is first translated to `{ 68.224, 93.44 }`, then rounded to the integers `{ 68, 93 }`. This results in the infamous crispy pixelated look. Recommended for pixel art, but looks horrible on textures that are intended to be photorealistic. This is the simplest type of texture-filtering to implement. **Bilinear filtering** takes a weighted average of the colour value of four pixels. In case of a 128x128 texture, for the normalized texture coordinate `{ 0.533, 0.73 }` - which gets translated to `{ 68.224, 93.44 }` -, that would mean taking a weighted average of the colour values of `{ 68, 93 }`, `{ 69, 93 }`, `{ 68, 94 }` and `{ 69, 94 }` respectively, but we'll get into the details of the algorithm later. The point is, that this type of texture filtering results in a smooth - and blurry, if texture resolution is low - look. Good for photorealistic textures, bad for pixel art. **Dithered** is basically a type of pseudo-bilinear filtering. It fakes bilinear filtering by employing ordered dithering. This results in a noticeable checkerboard pattern if you look too closely, but is indistinguishable from real bilinear filtering if you are far enough from the monitor. For dithered texture filtering, we'll also need some kind of input to manipulate the dither pattern, and the screen position is the most ideal for that. https://en.wikipedia.org/wiki/Ordered_dithering For now, we'll be content with **nearest-neighbour** filtering, but we will implement the other two algorithms down the line. Okay, but we'll need to import a texture! For this purpose, I recommend using stb. But first, need to adjust our StandardTexture interface to accomodate it for pre-existing pixel data. https://github.com/nothings/stb #ifndef STANDARDTEXTURE_HPP #define STANDARDTEXTURE_HPP #include "Texture.hpp" #include <vector> #include <cstring> template <typename PixelType> class StandardTexture : public Texture { private: std::vector<PixelType> buff; int w,h,stride; float fw, fh; public: StandardTexture(const StandardTexture& cpy) : buff(cpy.buff), w(cpy.w), h(cpy.h), stride(cpy.stride), fw(cpy.fw), fh(cpy.fh) { // Copy constructor } StandardTexture(StandardTexture&& mov) : buff(std::move(mov.buff)), w(mov.w), h(mov.h), stride(mov.stride), fw(mov.fw), fh(mov.fh) { // Move constructor } StandardTexture& operator=(const StandardTexture& cpy) { // Copy assignment operator this->buff = cpy.buff; this->w = cpy.w; this->h = cpy.h; this->stride = cpy.stride; this->fw = cpy.fw; this->fh = cpy.fh; return *this; } StandardTexture& operator=(StandardTexture&& mov) { // Move assignment operator this->buff = std::move(mov.buff); this->w = mov.w; this->h = mov.h; this->stride = mov.stride; this->fw = mov.fw; this->fh = mov.fh; return *this; } StandardTexture(const PixelType* pixelData, int w, int h) : w(w), h(h), stride(w*sizeof(PixelType)), fw(w-1), fh(h-1), buff(w*h) { memcpy(buff.data(),pixelData,sizeof(PixelType) * buff.size()); } StandardTexture(int w, int h) : w(w), h(h), stride(w*sizeof(PixelType)), fw(w-1), fh(h-1), buff(w*h) { memset(buff.data(),0,sizeof(PixelType) * buff.size()); } int getWidth() const { return w; } float getWidthF() const { return fw; } int getHeight() const { return h; } float getHeightF() const { return fw; } int getStride() const { return stride; } // Pixel manipulation void getPixel(const glm::ivec2& pos, glm::vec4& colourKernel) const { const PixelType& pix = buff[((pos.y % h) * w) + (pos.x % w)]; pix.fillKernel(colourKernel); } virtual void setPixel(const glm::ivec2& pos, const glm::vec4& colourKernel){ PixelType& pix = buff[((pos.y % h) * w) + (pos.x % w)]; pix.fromKernel(colourKernel); } void* getRawPixels() { return buff.data(); } const void* getRawPixels() const { return buff.data(); } }; #endif // STANDARDTEXTURE_HPP But we're still not quite ready to load images using stb. We'll need a few more helper functions. #ifndef NORMALIZE_HPP #define NORMALIZE_HPP #include <limits> #include <cmath> #include <type_traits> template <typename T> struct _normalize { constexpr static const float minF = -1.0f * float(std::numeric_limits<T>::min()); constexpr static const float maxF = float(std::numeric_limits<T>::max()); constexpr static const float recMinF = ((std::numeric_limits<T>::min())) ? 1.0f / minF : 0.0f; constexpr static const float recMaxF = 1.0f / maxF; inline static constexpr float normalize(const T& val) { return (val < 0) ? (float(val) * recMinF) : (float(val) * recMaxF); } inline static T denormalize(const float& val) { return (std::signbit(val)) ? T(val*minF) : T(val*maxF); } }; template <> struct _normalize<float> { inline constexpr static float normalize(const float& val) { return val; } inline constexpr static float denormalize(const float& val) { return val; } }; template <> struct _normalize<double> { inline constexpr static float normalize(const double& val) { return float(val); } inline constexpr static double denormalize(const float& val) { return double(val); } }; template <typename T> inline float normalize(const T& val) { return _normalize<T>::normalize(val); } template <typename T> inline T denormalize(const float& val) { return _normalize<T>::denormalize(val); } #endif // NORMALIZE_HPP With this set of functions, we can easily normalize integers into floating-point values and vice versa. Think of it as a way of mapping the `[0,255]` range to `[0,1]` and vice versa, or `[-128,127]` range into `[-1,1]` and vice versa. Why is this necessary? Well, this will allow us to template pixel formats. template <typename T> struct StdPixelGreyscale { T val; inline void fillKernel(glm::vec4& colourKernel) const { const float norm = normalize(val); colourKernel.r = norm; colourKernel.g = norm; colourKernel.b = norm; colourKernel.a = 1.0f; } inline void fromKernel(const glm::vec4& colourKernel) { val = denormalize<T>( (0.2989f * colourKernel.r) + (0.5870f * colourKernel.g) + ( 0.1140f * colourKernel.b) ); } }; template <typename T> struct StdPixelRgb { T r,g,b; inline void fillKernel(glm::vec4& colourKernel) const { colourKernel.r = normalize(r); colourKernel.g = normalize(g); colourKernel.b = normalize(b); colourKernel.a = 1.0f; } inline void fromKernel(const glm::vec4& colourKernel) { r = denormalize<T>(colourKernel.r); g = denormalize<T>(colourKernel.g); b = denormalize<T>(colourKernel.b); } }; template <typename T> struct StdPixelRgba { T r,g,b,a; inline void fillKernel(glm::vec4& colourKernel) const { colourKernel.r = normalize(r); colourKernel.g = normalize(g); colourKernel.b = normalize(b); colourKernel.a = normalize(a); } inline void fromKernel(const glm::vec4& colourKernel) { r = denormalize<T>(colourKernel.r); g = denormalize<T>(colourKernel.g); b = denormalize<T>(colourKernel.b); a = denormalize<T>(colourKernel.a); } }; typedef StdPixelGreyscale<uint8_t> PixelGrey8; typedef StandardTexture<PixelGrey8> TextureGrey8; typedef StdPixelGreyscale<uint16_t> PixelGrey16; typedef StandardTexture<PixelGrey16> TextureGrey16; typedef StdPixelRgb<uint8_t> PixelRgb24; typedef StandardTexture<PixelRgb24> TextureRgb24; typedef StdPixelRgba<uint8_t> PixelRgba32; typedef StandardTexture<PixelRgba32> TextureRgba32; typedef StdPixelRgb<uint16_t> PixelRgb48; typedef StandardTexture<PixelRgb48> TextureRgb48; typedef StdPixelRgba<uint16_t> PixelRgba64; typedef StandardTexture<PixelRgba64> TextureRgba64; This will allow us to easily work with just about any kind of standard texture type, even floating-point ones. And now, we can write a function for loading with stb. #ifndef LOADIMAGE_HPP #define LOADIMAGE_HPP #include "Texture.hpp" #include <memory> #include <string> std::shared_ptr<Texture> loadImage(const char* path); inline std::shared_ptr<Texture> loadImage(const std::string& path) { return loadImage(path.c_str()); } #endif // LOADIMAGE_HPP #include "LoadImage.hpp" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "StandardPixelType.hpp" std::shared_ptr<Texture> loadImage(const char *path) { int width, height, channels; unsigned char *img = stbi_load(path, &width, &height, &channels, 0); std::shared_ptr<Texture> toReturn = nullptr; if(!img) return toReturn; switch (channels) { case 1: toReturn = std::shared_ptr<Texture>(new TextureGrey8(reinterpret_cast<PixelGrey8*>(img),width,height)); break; case 3: toReturn = std::shared_ptr<Texture>(new TextureRgb24(reinterpret_cast<PixelRgb24*>(img),width,height)); break; case 4: toReturn = std::shared_ptr<Texture>(new TextureRgba32(reinterpret_cast<PixelRgba32*>(img),width,height)); break; default: break; } stbi_image_free(img); return toReturn; } An image having a single channel means it being greyscale. If it has 3 channels, it's 24-bit RGB, and if it has 4 channels, it's 32-bit RGBA. Now, more experienced C++ programmers might want to gouge out their eyes at the sight of my usage of **reinterpret_cast**, but we know exactly what we're doing - unless STB lies to us, we already know how big will each struct be. This is why the polymorphism of C++ is such a useful features. Look at how easily we can abstract the three different types of texture formats! However, to display our little texture, we'll need a new pipeline with new inputs. #ifndef TEXTUREDPIPELINE_HPP #define TEXTUREDPIPELINE_HPP #include <glm/glm.hpp> #include "Texture.hpp" #include "RenderingPipeline.hpp" #include <memory> struct TexturedUniform { std::shared_ptr<Texture> tex; TextureFiltering filtering; }; struct TexturedVertexIn { glm::vec3 POS; glm::vec2 TEXCOORD; }; struct TexturedVertexOut { glm::vec2 POS; glm::vec2 TEXCOORD; inline static TexturedVertexOut split(const TexturedVertexOut& t, const TexturedVertexOut& m, const TexturedVertexOut& b, float dy, float iy) { return { glm::vec2( t.POS.x + ((b.POS.x - t.POS.x) / dy) * iy, m.POS.y ), glm::vec2( t.TEXCOORD.r + ((b.TEXCOORD.r - t.TEXCOORD.r) / dy) * iy, t.TEXCOORD.g + ((b.TEXCOORD.g - t.TEXCOORD.g) / dy) * iy ) }; } }; typedef RenderingPipeline<TexturedVertexIn,TexturedVertexOut,TexturedUniform> TexturedPipeline; TexturedVertexOut TexturedVertexShader(const TexturedUniform& uniform, const TexturedVertexIn& vertex, const glm::ivec4& viewport); void TexturedFragmentShader(Texture& framebuffer, const glm::ivec2& point, const TexturedUniform& uniform, const TexturedVertexOut& v0,const TexturedVertexOut& v1, const TexturedVertexOut& v2, float w0, float w1, float w2); #endif // TEXTUREDPIPELINE_HPP #include "TexturedPipeline.hpp" TexturedVertexOut TexturedVertexShader(const TexturedUniform &uniform, const TexturedVertexIn &vertex, const glm::ivec4 &viewport) { const int viewportW = viewport[2] - viewport[0]; const int viewportH = viewport[3] - viewport[1]; return { glm::vec2( ((vertex.POS[0] + 1.0f) / 2.0f * viewportW) + viewport[0] , (((vertex.POS[1]-1.0f) / -2.0f) * viewportH) + viewport[1] ), vertex.TEXCOORD }; } void TexturedFragmentShader(Texture &framebuffer, const glm::ivec2 &point, const TexturedUniform &uniform, const TexturedVertexOut &v0, const TexturedVertexOut &v1, const TexturedVertexOut &v2, float w0, float w1, float w2) { if(point.x < 0 || point.y < 0) return; if(!uniform.tex) return; glm::vec2 texCoord = { (w0 * v0.TEXCOORD.r) + (w1 * v1.TEXCOORD.r) + (w2 * v2.TEXCOORD.r), (w0 * v0.TEXCOORD.g) + (w1 * v1.TEXCOORD.g) + (w2 * v2.TEXCOORD.g) }; framebuffer.setPixel(point,uniform.tex->sample(texCoord,point,uniform.filtering)); } Those who already have some experience with graphics may notice something missing from our pipeline, but those who do - don't spoil it for the rest ;) It's similar to our pre-existing basic pipeline, but our secondary vertex attribute is not a 4-dimensional vector *(RGBA colour)* but instead a 2-dimensional vector *(**UV coordinate**)*. We use this UV coordinate to sample our texture to fill our colour kernel, and then paint the pixel onto the screen. https://en.wikipedia.org/wiki/UV_mapping Once sampling the texture to fill our colour kernel, we could modulate the colour *(e.g. multiplying it with a per-vertex colour)*, but since we're not doing that, I did a shortcut and cut off the intermediate colour kernel. Next, we modify our application class to include this new pipeline. #include "SoftwareRendererSystem.hpp" #include "LoadImage.hpp" SoftwareRendererSystem::SoftwareRendererSystem(int width, int height) : AppSystem("Software Renderer Demo",0,0,width,height,0), renderer(SDL_CreateRenderer(this->window.get(),0,0),SDL_DestroyRenderer), framebuffer(SDL_CreateTexture(this->renderer.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width,height),SDL_DestroyTexture), renderBuffer(width,height) { pipeline.viewport[0] = 0; pipeline.viewport[1] = 0; pipeline.viewport[2] = width; pipeline.viewport[3] = height; pipeline.vert = TexturedVertexShader; pipeline.frag = TexturedFragmentShader; pipeline.framebuffer = &renderBuffer; pipeline.uniform = { nullptr, NEAREST_NEIGHBOUR }; pipeline.uniform.tex = loadImage("brickwall.png"); } void SoftwareRendererSystem::processEvent(const SDL_Event &ev, bool &causesExit) { switch(ev.type) { case SDL_QUIT: causesExit = true; break; default: break; } } void SoftwareRendererSystem::updateLogic() { } static const std::vector<TexturedVertexIn> vertices = { { glm::vec3(-0.8f, 0.8f,0.f), glm::vec2(0.0f, 0.0f) }, // 0 { glm::vec3(-0.8f, -0.8f,0.f), glm::vec2(0.0f, 1.0f) }, // 1 { glm::vec3(0.8f, 0.8f,0.0f), glm::vec2(1.0f, 0.0f) }, // 2 { glm::vec3(0.8f, -0.8f,0.0f), glm::vec2(1.0f, 1.0f) } // 3 }; static const std::vector<unsigned> indices = { 0, 1, 2, 1, 2, 3 }; void SoftwareRendererSystem::render() { pipeline.renderTriangles(vertices.data(),indices.data(), indices.size() ); SDL_UpdateTexture(framebuffer.get(), nullptr, renderBuffer.getRawPixels(), renderBuffer.getStride() ); SDL_RenderCopy(renderer.get(), framebuffer.get(), nullptr, nullptr); SDL_RenderPresent(renderer.get()); SDL_Delay(16); } And voila, we got a textured rectangle! But it looks rather pixelated, doesn't it? Perhaps it's high time to implement the other two texture filtering methods. Texture Filtering Bilinear Filtering As stated previously, bilinear filtering is implement by taking the weighted averages of our pixels. #include "Texture.hpp" #include <cmath> void Texture::sample(const glm::vec2 &pos, const glm::ivec2 &screenpos, TextureFiltering filteringType, glm::vec4 &colourKernel) const { switch (filteringType) { case NEAREST_NEIGHBOUR: getPixel(glm::ivec2( int( std::round(pos.x*getWidthF())) % getWidth() , int(std::round(pos.y * getHeightF())) % getHeight() ), colourKernel); break; case DITHERED: // To be implemented later break; case BILINEAR: { const int w = getWidth(); const int h = getHeight(); const glm::vec2 tmp = glm::vec2(pos.x * getWidthF(),pos.y * getHeightF() ); const glm::vec2 coordEdgeTopLeft( std::floor(tmp[0]), std::floor(tmp[1]) ); const glm::vec2 coordEdgeTopRight( std::ceil(tmp[0]), std::floor(tmp[1]) ); const glm::vec2 coordEdgeBottomLeft( std::floor(tmp[0]), std::ceil(tmp[1]) ); const glm::vec2 coordEdgeBottomRight( std::ceil(tmp[0]), std::ceil(tmp[1]) ); const glm::vec2 weight = tmp - coordEdgeTopLeft; glm::vec4 colourTopLeft, colourTopRight, colourBottomLeft, colourBottomRight; getPixel(glm::ivec2( int(coordEdgeTopLeft[0]) % w,int(coordEdgeTopLeft[1]) % h ),colourTopLeft ); getPixel(glm::ivec2( int(coordEdgeTopRight[0]) % w,int(coordEdgeTopRight[1]) % h ),colourTopRight ); getPixel(glm::ivec2( int(coordEdgeBottomLeft[0]) % w,int(coordEdgeBottomLeft[1]) % h ),colourBottomLeft ); getPixel(glm::ivec2( int(coordEdgeBottomRight[0]) % w,int(coordEdgeBottomRight[1]) % h ),colourBottomRight ); colourTopLeft *= ((1.0f-weight[0]) * (1.0f-weight[1])); colourTopRight *= (weight[0] * (1.0f-weight[1])); colourBottomLeft *= ((1.0f-weight[0]) * weight[1]); colourBottomRight *= (weight[0] * weight[1]); colourKernel = colourTopLeft + colourTopRight + colourBottomLeft + colourBottomRight; break; } default: break; } } And, we got bilinear filtering! But I bet you are curious about what kind of mathematical black magic did we have to perform to do this, so I will try my best to explain. We translate our normalized texture coordinate from `{ [0,1], [0,1] }` to `{ [0,WIDTH], [0,HEIGHT] }`. In the case of this 64x64 texture, that means translating `{ [0,1], [0,1] }` to `{ [0,64], [0,64] }`. We calculate the four edges by using different types of rounding. To get the top left edge, we need to round down both coordinates. To get the top-left corner's coordinate, we round down both of the aforementioned coordinates. To get the top-right corner's coordinate, we round up the X-coordinate, but round down the Y-coordinate. To get the bottom-left coordinate, we round down the X-coordinate, but round up the Y-coordinate. To get the bottom-right coordinate, we round down up both coordinates. We get a normalized two-dimensional weight coordinate by subtracting the top-left coordinate from the aforementioned temporary coordinate. The end result is a two-dimensional vector, with both numbers between 0 and 1. We declare four colour kernels and fill them with colours of each corner. We multiply the four colour kernels with the appropriate weights. For the top-left one, that is going to be `{ 1-weightX, 1-weightY }`. For the top-right one, that is going to be `{ weightX, 1-weightY }`. For the bottom-left one, that is going to be `{ 1-weightX, weightY }`. For the bottom-right one, that is going to be `{ weightX, weightY }`. We add all the colour kernels together, which results in a weighted average of them, given how we multiplied them with the weights before. Congratulations, you just bilinearly sampled a pixel colour. Needless to say, this will be slow as hell, but hey - beauty requires sacrifices. Among others, Outcast used a software renderer with bilinear filtering. https://www.youtube.com/watch?v=D1gru-m1fqg Dithered texture filtering True bilinear filtering is slow, and best left out of software renderers altogether. But if you really want to avoid the jagged sharp pixels, we can still fake bilinear filtering with a simple ordered dither, as was done in Unreal. https://www.flipcode.com/archives/Texturing_As_In_Unreal.shtml static const glm::vec2 LOOKUP[2][2] = { { glm::vec2( 0.25f, 0.00f ), glm::vec2( 0.50f, 0.75f ) }, { glm::vec2( 0.75f, 0.50f ), glm::vec2( 0.00f, 0.25f ) } }; void Texture::sample(const glm::vec2 &pos, const glm::ivec2 &screenpos, TextureFiltering filteringType, glm::vec4 &colourKernel) const { switch (filteringType) { case NEAREST_NEIGHBOUR: getPixel(glm::ivec2( int( std::round(pos.x*getWidthF())) % getWidth() , int(std::round(pos.y * getHeightF())) % getHeight() ), colourKernel); break; case DITHERED: { glm::vec2 texelCoords = glm::vec2(pos.x * getWidthF(),pos.y * getHeightF()); texelCoords += LOOKUP[screenpos[1]&1][screenpos[0]&1]; getPixel(glm::ivec2( int(std::round(texelCoords.x))%getWidth(),int(std::round(texelCoords.y))%getHeight() ),colourKernel); break; } case BILINEAR: { // We already discussed you, bilinear... } default: break; } } And the end result is... Virtually indistinguishable from the regular bilinear filter, save for the checkerboard pattern that only the most observant will notice. But hey, it comes with almost zero performance impact! But these rectangles are getting boring, we should render something more exciting... In the next episode, we'll get into more exciting things: Z-Buffers, cameras, and more complex shapes! But before that, I'd like to take a little detour on dithering... Dithering colours Most of the times, when you hear the word *"dithering"*, it refers to dithering performed on a colour kernel, rather than texture coordinates. Now, why would you want to dither colours? Well, let's just, what if you need to resort to a lower colour depth, say 16-bit colour instead of 24-bit colour? struct PixelRgb565 { uint16_t rgb; static constexpr const float i4r = 1.0f / 15.0f; static constexpr const float i5r = 1.0f / 31.0f; static constexpr const float i6r = 1.0f / 63.0f; static constexpr const float reciprocal = 1.0f / 255.0f; inline void fillKernel(glm::vec4& colourKernel) const { const uint16_t r = (rgb & 0xF800) >> 11; const uint16_t g = (rgb & 0x07E0) >> 5; const uint16_t b = (rgb & 0x001F); colourKernel.r = float(r) * i5r; colourKernel.g = float(g) * i6r; colourKernel.b = float(b) * i5r; colourKernel.a = 1.0f; } inline void fromKernel(const glm::vec4& colourKernel) { const uint16_t r = uint16_t(colourKernel.r * 31.0f); const uint16_t g = uint16_t(colourKernel.g * 63.0f); const uint16_t b = uint16_t(colourKernel.b * 31.0f); rgb = ((r << 11) | (g << 5) | b); } }; typedef StandardTexture<PixelRgb565> TextureRgb565; If we just modify the renderer's framebuffer to this type, we're going to be met with a nasty surprise: Look at that terrible colour-banding! It's horrible! We need to do something about it! And the solution is dithering. Ordered dithering. https://en.wikipedia.org/wiki/Ordered_dithering #ifndef DITHER_HPP #define DITHER_HPP #include <cstddef> #include <algorithm> template <typename T, T maximum> struct OrderedDither { static constexpr const float MAX = ((float(maximum)+1.0f) * 8.0f) - 1.0f; static constexpr const float MAX_RECIPROCAL = 1.0f/float(MAX); static constexpr const float LookupTable[4][4] = { {0.0f * MAX_RECIPROCAL, 8.0f * MAX_RECIPROCAL, -2.0f * MAX_RECIPROCAL, 10.0f * MAX_RECIPROCAL}, {12.0f * MAX_RECIPROCAL, -4.0f * MAX_RECIPROCAL, 14.0f * MAX_RECIPROCAL, -6.0f * MAX_RECIPROCAL}, {-3.0f * MAX_RECIPROCAL, 11.0f * MAX_RECIPROCAL, -1.0f * MAX_RECIPROCAL, 9.0f * MAX_RECIPROCAL}, {15.0f * MAX_RECIPROCAL, -7.0f * MAX_RECIPROCAL, 13.0f * MAX_RECIPROCAL, -5.0f * MAX_RECIPROCAL} }; static constexpr float ditherUp(float value, const glm::ivec2& coords) { return std::clamp(value + LookupTable[coords.y%4][coords.x%4],0.0f,1.0f); } static constexpr float ditherDown(float value, const glm::ivec2& coords) { return std::clamp(value - LookupTable[coords.y%4][coords.x%4],0.0f,1.0f); } }; #endif // DITHER_HPP Next, we update our pixel and texture classes to accomodate for this new functionality. struct PixelRgb565 { uint16_t rgb; typedef OrderedDither<uint8_t,31> Dither5; typedef OrderedDither<uint8_t,63> Dither6; static constexpr const float i5r = 1.0f / 31.0f; static constexpr const float i6r = 1.0f / 63.0f; static constexpr const float reciprocal = 1.0f / 255.0f; inline void fillKernel(glm::vec4& colourKernel) const { const uint16_t r = (rgb & 0xF800) >> 11; const uint16_t g = (rgb & 0x07E0) >> 5; const uint16_t b = (rgb & 0x001F); colourKernel.r = float(r) * i5r; colourKernel.g = float(g) * i6r; colourKernel.b = float(b) * i5r; colourKernel.a = 1.0f; } inline void fromKernel(const glm::vec4& colourKernel) { const uint16_t r = uint16_t(colourKernel.r * 31.0f); const uint16_t g = uint16_t(colourKernel.g * 63.0f); const uint16_t b = uint16_t(colourKernel.b * 31.0f); rgb = ((r << 11) | (g << 5) | b); } inline void fromKernelDithered(const glm::vec4& colourKernel, const glm::ivec2& screencoord) { const uint16_t r = uint16_t(Dither5::ditherDown(colourKernel.r,screencoord) * 31.0f); const uint16_t g = uint16_t(Dither6::ditherDown(colourKernel.g,screencoord) * 63.0f); const uint16_t b = uint16_t(Dither5::ditherDown(colourKernel.b,screencoord) * 31.0f); rgb = ((r << 11) | (g << 5) | b); } }; typedef StandardTexture<PixelRgb565> TextureRgb565; template <typename T> struct StdPixelRgb { typedef OrderedDither<T,std::numeric_limits<T>::max()> Dither; T r,g,b; inline void fillKernel(glm::vec4& colourKernel) const { colourKernel.r = normalize(r); colourKernel.g = normalize(g); colourKernel.b = normalize(b); colourKernel.a = 1.0f; } inline void fromKernel(const glm::vec4& colourKernel) { r = denormalize<T>(colourKernel.r); g = denormalize<T>(colourKernel.g); b = denormalize<T>(colourKernel.b); } inline void fromKernelDithered(const glm::vec4& colourKernel, const glm::ivec2& screencoord) { r = denormalize<T>(Dither::ditherDown(colourKernel.r,screencoord)); g = denormalize<T>(Dither::ditherDown(colourKernel.g,screencoord)); b = denormalize<T>(Dither::ditherDown(colourKernel.b,screencoord)); } }; class Texture { public: virtual ~Texture() = default; // Data getters virtual int getWidth() const = 0; virtual float getWidthF() const = 0; virtual int getHeight() const = 0; virtual float getHeightF() const = 0; virtual int getStride() const = 0; // Pixel manipulation virtual void getPixel(const glm::ivec2& pos, glm::vec4& colourKernel) const = 0; inline glm::vec4 getPixel(const glm::ivec2& pos) const { glm::vec4 tmp; getPixel(pos,tmp); return tmp; } virtual void setPixel(const glm::ivec2& pos, const glm::vec4& colourKernel) = 0; virtual void setPixelDithered(const glm::ivec2& pos, const glm::ivec2& screenpos, const glm::vec4& colourKernel) = 0; virtual void* getRawPixels() = 0; virtual const void* getRawPixels() const = 0; virtual void sample(const glm::vec2& pos, const glm::ivec2& screenpos, TextureFiltering filteringType, glm::vec4& colourKernel) const; inline glm::vec4 sample(const glm::vec2& pos, const glm::ivec2& screenpos, TextureFiltering filteringType) const { glm::vec4 tmp; sample(pos,screenpos,filteringType,tmp); return tmp; } }; And we also update our pipelines to use **setPixelDithered** instead of **setPixel** too. And after that, look at the results! Smooth as butter, not a single banding artifact present. But why even stop at 16-bit colour, if we can go even lower? Let's do 8-bit RGB332 then! struct PixelRgb332 { uint8_t rgb; typedef OrderedDither<uint8_t,3> Dither2; typedef OrderedDither<uint8_t,7> Dither3; static constexpr const float i2r = 1.0f / 3.0f; static constexpr const float i3r = 1.0f / 7.0f; inline void fillKernel(glm::vec4& colourKernel) const { const uint8_t r = (rgb & 0xE0) >> 5; const uint8_t g = (rgb & 0x1C) >> 2; const uint8_t b = (rgb & 0x03); colourKernel.r = float(r) * i3r; colourKernel.g = float(g) * i3r; colourKernel.b = float(b) * i2r; colourKernel.a = 1.0f; } inline void fromKernel(const glm::vec4& colourKernel) { const uint16_t r = uint16_t(colourKernel.r * 7.0f); const uint16_t g = uint16_t(colourKernel.g * 7.0f); const uint16_t b = uint16_t(colourKernel.b * 3.0f); rgb = ((r << 5) | (g << 2) | b); } inline void fromKernelDithered(const glm::vec4& colourKernel, const glm::ivec2& screencoord) { const uint16_t r = uint16_t(Dither3::ditherDown(colourKernel.r,screencoord) * 7.0f); const uint16_t g = uint16_t(Dither3::ditherDown(colourKernel.g,screencoord) * 7.0f); const uint16_t b = uint16_t(Dither2::ditherDown(colourKernel.b,screencoord) * 3.0f); rgb = ((r << 5) | (g << 2) | b); } }; typedef StandardTexture<PixelRgb332> TextureRgb332; First, without dither: Okay, now with dither: So yeah, now we can also render in 16-bit *(64k colours)* and 8-bit *(256 colours)* framebuffers, if we want. But enough of this detour. In the next episode, we'll get serious with more complex shapes, Z-Buffers and cameras! Until then, as always, the code within this episode is uploaded onto Github. https://github.com/Metalhead33/SoftwareRendererTutorial

+23 more

@Metalhead33

In defense of scammers ****Warning!**** **This article was written by me a very long time ago, and no longer accurately reflects my views! Reader discretion is advised.** Scamming people out of their hard-earned money. Is it unethical? Obviously. But how much? The penny-pincher inside of me - especially my old self, from before I became a wageslave - wants to condemn every practice that results in someone having to part with their money for no return, but the Social Darwinist inside me says, that if someone is so lacking in intelligence that they allow themselves to fall victim to a scam, then their money is better off in the hands of the scammer. But, the main subject of this article won't be scammers. The title is intentionally provocative, and meant to be thought-provoking. What kind of a person could defend scammers? You see, I have this friend... I have a friend from the USA. Let's call him Dave. Dave and I agree on many things, but we also disagree on many things. Both of us support Universal Basic Income, but while I'm more of an anti-statist Libertarian who thinks that Capitalism can be salvaged, he's a Statist who wants to regulate away landlords and put all sorts of sanctions on companies. Another disagreement between he and I is on people who have already succeeded at finding an alternative source of income - alternative to being a wageslave, that is. https://en.wikipedia.org/wiki/Universal_basic_income Dave has childish fantasies about enacting violence upon cryptocurrency miners, scammers, shills *(those whose videos are sponsored by Raid Shadow Legends)*, those who have a Patreon, etc. Me on the other hand? I salute these people. Before I became a wageslave, I used to look down on people like Belle Delphine - now that I know what *"honest work"* is like, I actually salute her for managing to avoid it, and sell her bathwater for so much that if she rationed her money wisely, she could retire right now and never have to do anything in her life. Obviously, I get annoyed, when a video I am watching goes *"This video was sponsored by Raid Shadow Legends"*- who doesn't? But unlike Dave, at the very least, I **understand** why these people do it. As much as I like to praise Patreon for bringing back patronage, people aren't terribly charitable these days, so if you want to be a full-time content creator, you better get used to swallowing your pride and shilling. https://read.cash/@Metalhead33/is-copyright-maintainable-f023b710#a-world-before-and-after-copyright Dave calls these people *"parasites"*, but a true parasite exploits a host who is none the wiser, and gradually sucks all the life out of the host without the host even noticing it - these people on the other hand are gamblers gambling their livelihoods on either you liking their content *(YouTubers)* or you being stupid enough to fall to the scam, both of which are very risky strategies. Crypto-miners are basically gambling with their electricity, as there is no guarantee that it'll be profitable. What these people are doing doesn't sound like the road of least resistance to me, not by a long shot. A world half full, a world half-empty The people that Dave is complaining about and has fantasies of enacting violence towards are products of a world that's in the cursed middle-ground. The cursed middle-ground between what? Between Capitalism and Socialism. My other friend Geri laments the death of Capitalism, while Dave complains that we live in an ultra-Capitalist Corpotatocracy where everybody is at the whims of big corporations. Surely, the complaints of both can't be valid at the same time, can they? **But yes they can**, because we live in a system that combines the **worst** of both worlds: just like in any true Capitalist system, everything comes with a price tag attached, but just like under Socialism, the government taxes the hell out of you and puts red tape on everything to prevent innovation and self-sufficiency. https://read.cash/@Geri/the-death-of-capitalism-216e2593 https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4 The end result is a terrible system, where self-sufficiency is all but impossible if you don't have the starting capital, or the luck. For most people, pretty much the only viable option is becoming a part-time slave for some company in an office or a factory. Sure, if you're unlucky enough to be born with a major debilitating disability, but lucky enough to be born in a country that financially compensates you for being born unlucky, good for you; likewise, if you somehow managed to make it and become self-employed, jolly good - but for most people, it's either the plantation, or the starvation. Have a garden at home? Jolly good! Plant some potatoes in it, and you've got the food situation sorted! Except, you still have to pay for water and electricity, so obviously, you'll have to sell some of your potatoes - but what if people don't want to buy them? Then you're screwed. No survival on zero income. Okay, but what if you **do** have the starting capital, and invest it in some solar arrays, water pumps, etc. to produce your own electricity and water? Nice, but you're still not free - the government mafia will still come to collect protection money *(property tax)* for the *"privilege"* of living on their land, meaning that even if you have every single one of your needs accounted for, you still need to accrue capital to avoid having your property confiscated and you getting sent to jail. See where I'm going with this? If you ask me, an ideal system would be one that combines the best of both worlds rather than the worst, e.g. a night-watchman state but with UBI. If we can't have that, then **please** give me one of the two extremes, but not this cursed middle-ground that combines the worst of both worlds. https://en.wikipedia.org/wiki/Night-watchman_state https://en.wikipedia.org/wiki/Universal_basic_income It's a dog-eats-dog world All the people that Dave is complaining about are products of this terrible world I just described. A world where life itself costs money. It's a dog-eats-dog world, and people do whatever it takes to survive. I don't blame them. If my *"job"* was to make videos on a weekly basis and shill for some sponsor, I'd do it. I'd gladly trade my current day-job for being a part-time shill for Raid Shadow Legends, if it was an option for me. Hell, I'd even do some unspeakably unethical things, if they paid enough to ensure retirement for life within less than a year. In a world without UBI, unless you become a welfare-rat, you either become shill for Raid Shadow Legends, you join the ranks of criminals by scamming fools out of their hard-earned cash, you become a part-time slave for some company *(or God forbid, the government)*, or you starve to death. I guess a lucky few do make it and become succesfully self-employed, but let's face it: they're not representative. Maybe, if we had a Universal Basic Income, and everyone was given 1000$ per month, I'd be more partial towards Dave's criticism of scammers and shills - after all, if UBI covers your needs and ensures that work is optional, content creators would have no financial incentive to shill for Raid Shadow Legends, unless the video's production was **that** costly *(under the assumption we only talk about cost in materials and equipment,* ***excluding*** *the opportunity cost of filming instead of doing something potentially more profitable)*. But in a world without UBI, you do what you gotta do to survive. It's a dog-eats-dog world, and I'll even forgive those who sell drugs and weapons to terrorist organizations. Anything's better than being a wageslave. But Metalhead33, why do you hate being a wageslave so much? Isn't it obvious? Bob Black already made many good points about why it's such a terrible thing, but if you **really** want to hear it from my mouth: https://theanarchistlibrary.org/library/bob-black-the-abolition-of-work *"Work"* as we have come to understand it following the Industrial Revolution goes against human nature. Humans weren't designed to sit in an office for 8 hours, work at an assembly line for 8 hours, live in huge cities where you're lonely in spite of constantly being surrounded by people, etc. Humans were designed to hunt and farm, and live in small, culturally homogeneous villages where everyone knows everyone. *"Work"* consists of more than just work, the activity you were allegedly hired to do. *"Work"* has all sorts of unpleasant accoutrements that you will never escape, even if you like what you have to do: the schedules, the commutes, the meetings, the bugging by coworkers and your boss, the fact that you even have a boss to begin with, etc. Before Covid-19 forced companies to allow workers to work from home *(God bless Covid-19)*, commutes turned my de jure 8-hour workdays into de facto 12-hour workdays *(obviously not compensated in any way, shape or form)*. It's not enough for me to just do programming and software development - I also have to talk to people I'd rather not, I also have to adjust to some *"corporate culture"*, I also have to work on **their** schedule, etc. And I resent the hell out of all that. **TL;DR:** If you make your hobby your job, you lose your hobby, not the other way around. The saying *"If you a job you love doing, you'll never work a single day in your life"* couldn't be any further from the truth. *"Work"* takes something you may or may not like, and turns it into a daily chore that will inevitably start wearing on you. No matter how hard you work, the government will still steal the lion's share of your hard-earned income. Taxation is theft... ... but if they are going to steal our money, they might as well spend it on Universal Basic Income, instead of stadiums or corruption. It drains the life out of you to the degree, that you are too tired to look for alternative sources of income. Modern, Industrial and post-Industrial work - along with school - is one of the reasons why children are constantly exposed to the pedophile danger. Because the office - where the wageslave parents work - has to be 50 kilometres away from home, because the school has to be 50 kilometres away from home. This is highly unnatural, to say the least. Throughout much of history, children spent the lion's share of their time in the physical proximity of their parents, not 50 kilometres away from them. **It's outdated.** Yes, you heard me right, human labour is outdated. Automation is improving constantly. I reckon that we already have the technology to automate away most jobs, and the only reason we haven't done so yet is to placate all the plebians who insist that everyone must suffer as much as they did. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-6999b3f4#on-the-inevitability-of-ubi https://read.cash/@Metalhead33/work-ethics-and-the-virtues-of-honest-work-are-a-cope-rooted-in-jealousy-91cdca27#misery-is-the-true-source-of-evil Think about it. The last hundred years was constant technological advancement that multiplied the productivity of individual workers, yet workers didn't get any more leisure time. Quite the contrary. Why? It gets even worse. A medieval peasant worked 150 days a year **at maximum**. A modern-day Hungarian schoolchild goes to school 177 days a year. An adult Hungarian aged 25 goes to work 228 days a year. **We went backwards!** **It is typically useless**. Most people work in the service sector, which mostly consists of fake and made-up jobs invented to prevent mass unemployment. Only a minority of people work in the industrial sector, and even tinier minority in the agricultural sector. Obviously, not every service-sector job is useless, but many of them can be automated, or at the very least made more efficient. Who the hell likes waking up early, commuting to some office or factory, working for 8 hours and then commuting home tired as hell? **No one.** Yet this is the reality for most people. When the day starts, you better be ready to start your part-time slavery, and you better be in pristine condition to drive your car to work and then to home. In the words of the aforementioned Bob Black: *"Free time"* is only *"free"* insofar that it doesn't cost anything to your employers. You're basically a self-maintaining robot. In the words of Bob Black: most deaths can be attributed to work in some way, shape or form. People who die because of traffic accidents typically either get hit by a car driven by someone going to work or someone going home from work. People contact diseases at work, and many die from said diseases. People sometimes die from stress that work no doubt contributed to. People develop addictions *(alcoholism, smoking, drugs, etc.)* to cope with being a wageslave stuck in a dead-end job until retirement, which will probably never come for millenials, due to the demographics, with the ageing population. And the list goes on... When I first started working, I resented the commutes that turned my de-jure 8-hour workdays into de facto 12-hour workdays. Then, when home office came, I resented the fact, that I still had to work 9-to-5 with no flexibility. Now I can start working almost whenever I want, as long as it's 8 hours *(though occasionally, I have an emergency situation that requires my intervention even after my 8 hour-shift has ended)* - yet I am still not happy. Chances are, even if they reduced those 8 hours to just 6 hours or 4 hours, I still wouldn't be happy. Why? **Because I hate work itself. The very concept of work.** And this is why I defend scammers. It's a dog-eats-dog world, and anything is better than being a wageslave. If I live to see a day, when UBI is implemented and work becomes largely optional, maybe my faith in ethics and morality will be restored. But until then, I'll be a Social Darwinist with the belief, that **there is no such thing as an unethical source of income**.

+5 more

@Metalhead33

Modeling Tutorial: How to make a sword blade in Blender and Inkscape in five minutes They say that modelling takes a long time. And that's certainly true, if you are aiming for high-poly models that are ultra-photorealistic. But what if you just need a single sword in like a couple of minutes? Well, then I got a solution for you! **Disclamer:** This tutorial assumes that this is not the first time you are using either Blender or Inkscape. If you are new to Blender, I recommend watching TheSicklyWizard's videos on Blender. https://www.youtube.com/playlist?list=PLwcOTSbo8MJcCvLFwGHrPxMWQcqF9_TIU First, we open up Inkscape The first thing we need to do is to is to draw a little bezier curve... Our next step will be to draw something roughly resembling of a sword's blade, but cut in half horizontally. Don't worry, we'll mirror it in Blender later on. We'll need to ensure that the bottom points are really at the bottom, and that the leftmost points are really at the left. Now, if we want a super low-poly model, this is good enough. But if we want something slightly more realistic, we'll need to smoothen out some edges. Now, we'll need to cut into this shape, to create a fuller for our sword. You can avoid this step, if you don't want your sword to have a fuller, but then some of the Blender tutorial might get confusing for you. Next, you'll want to select both shapes, click on **Path**, then **Division**. Sadly, I could not take a screenshot of that menu. Next, select the smaller of the two, and delete it. Once again, Inkscape blocked my OS from taking a screenshot, but deleting an object should be trivial. What we should end up with is the blade's shape, with the fuller cut off. The next step is to save the SVG file and... ...open up Blender... ... and delete the default cube. Next, we want to import our SVG file into Blender. You might need to install a plugin to be able to do this. Once imported, what we should see is something like... So, what do we need to do next, now that we have imported the blade - with the fuller cut out - into Blender? Well, first, we convert it from a curve to a mesh. Next, we'll mirror it, so it won't look like it was cut in half horizontally. Now, we make some adjustments, so that everything is centered perfectly, the blade is in proper size, etc. If you are already familiar with Blender, you know what do - if you don't, don't worry, you'll figure it out. Next, press **A** to select every single vertex, then press **F** to make them into a face. Then, select the bottom *"inner"* vertex... ... and create a new vertex, by clicking somewhere while pressing Ctrl. In this case however, we want the vertex to be at the center, between the selected point and its mirrored counterpart. Uh, sorta. Okay, next, we'll need to select all the innermost vertices... ... and press **F**. Next, we'll want to select the two vertices at the center... ...and raise them vertically. Following this, we'll need to select the middling points they connect to... ... and raise them vertically even more. You may want to manually raise the one at the back more than the one at the front. Next, let's go back to **Object Mode**, and make sure that the mirroring happens on the Z-axis as well. Next, you will want to triangulate the mesh. And congratulations, you have a sword blade! Just not with the right directions.... Obviously, we need to cut down on both the width and the thickness, so simply scale it down on the those fronts. If you are already familiar with Blender, you know how - if not, then you'll figure it out on your own within minutes, I can guarantee that. That's more like it. But what about the rest of the sword? The rest of the sword depends on whether you are going for low-poly or high-poly, but with my approach, we'll first need to make a crossguard. So, add a cube... And start sculpting it. Once again, we need to add a mirror modifier, so that it will be symmetrical. This might be the time for you to familiarize yourself with some of the face editing tools, like **Extrude**. If you are a total noobie, just play around for 10-15 minutes, and you'll figure it out. In the end, I got something like this: Next, we'll need a grip or handle for our sword. I'll recommend startin with a cylinder. Next, rotate it, scale it, move it, etc. so it gets into place. If you want, you can split it in half, but I'd say it looks fine for now. Now, for last, to make sure that we can end him rightly, we need a pommel. I recommend starting with a cube, and shaping it into the form you desire through mirroring, extruding, etc. https://www.youtube.com/watch?v=jETLCm7k3sU And we're basically done! But wait! What about textures and whatnot? Texturing involves things like UV unwrapping, and other things way beyond the scope of this tutorial. For that, I recommend TheSicklyWizard's video on the subject. https://www.youtube.com/watch?v=1TwvWIsfHMU

+35 more

@Metalhead33

Software Rendering 2: Pipelines and Triangles In our previous episode, we managed to put pixels onto the screen using nothing but software code written in C++. In this episode, we are finally going to be putting triangles onto the screen. https://read.cash/@Metalhead33/software-rendering-part-1-setting-it-all-up-dbd3bd40 Now, as I said in the first episode, this won't just be a tutorial on software rendering, but also a bit of an insight into how GPUs work under the hood. So, we'll be using shaders of sorts. Because this code is getting more complex, I have to put out a **disclaimer**: I'm not claiming that I wrote every single line of code by hand. Some of it was clearly *inspired* by code written by others. However, I did modify it all, not just for my own needs, but for the purposes of this tutorial. Remember the sneak-peak? https://read.cash/@Metalhead33/software-rendering-part-1-setting-it-all-up-dbd3bd40#but-when-are-we-going-to-render-triangles If you remember yesterday's sneak-peak, you're in for a ride, I'm about to explain things. https://read.cash/@Metalhead33/software-rendering-part-1-setting-it-all-up-dbd3bd40#but-when-are-we-going-to-render-triangles First, let's ask ourselves a question. How much state do we want there to be present in our software-renderer? Do we want it to be very stateful *(storing the viewport, the uniform, the pointer to the framebuffer, the pointer to the Z-Buffer, shaders, etc. as variables)*, or to be complete stateless *(having all of these as parameters for the rendering functions)*? Just to demonstrate the two extremes, here's the original sneak-peak, which is completely stateless: #ifndef RENDERINGPIPELINE_HPP #define RENDERINGPIPELINE_HPP #include <functional> #include <glm/glm.hpp> #include "Texture.hpp" template<typename VertexInType, typename VertexOutType, typename UniformType> struct RenderingPipeline { typedef std::function<VertexOutType(const UniformType&, const VertexInType&, const glm::ivec4&)> VertexShader; typedef std::function<void(Texture&, const VertexOutType&, const VertexOutType&, const VertexOutType&, float, float, float)> FragmentShader; static void rasterize(const UniformType& uniform, const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2, Texture& framebuffer, const FragmentShader& frag) { // TO BE IMPLEMENTED } static void renderTriangle(const UniformType& uniform, const VertexShader& vert, const FragmentShader& frag, const glm::ivec4& viewport, Texture& framebuffer, const VertexInType& i0, const VertexInType& i1, const VertexInType& i2) { const VertexOutType o0 = vert(uniform,i0,viewport); const VertexOutType o1 = vert(uniform,i1,viewport); const VertexOutType o2 = vert(uniform,i2,viewport); rasterize(uniform,o0,o1,o2,framebuffer,frag); } static void renderTriangles(const UniformType& uniform, const VertexShader& vert, const FragmentShader& frag, const glm::ivec4& viewport, Texture& framebuffer, const VertexInType* vertices, size_t vertexCount) { for(size_t i = 0; i < vertexCount; i += 3) { renderTriangle(uniform,vert,frag,viewport,framebuffer, vertices[i],vertices[i+1],vertices[i+2]); } } static void renderTriangles(const UniformType& uniform, const VertexShader& vert, const FragmentShader& frag, const glm::ivec4& viewport, Texture& framebuffer, const VertexInType* vertices, unsigned* indices, size_t indexCount) { for(size_t i = 0; i < indexCount; i += 3) { renderTriangle(uniform,vert,frag,viewport,framebuffer, vertices[indices[i]],vertices[indices[i+1]],vertices[indices[i+2]]); } } }; #endif // RENDERINGPIPELINE_HPP In contrast, here is a more stateful variant of the same code: #ifndef RENDERINGPIPELINE_HPP #define RENDERINGPIPELINE_HPP #include <functional> #include <glm/glm.hpp> #include "Texture.hpp" template<typename VertexInType, typename VertexOutType, typename UniformType> struct RenderingPipeline { typedef std::function<VertexOutType(const UniformType&, const VertexInType&, const glm::ivec4&)> VertexShader; typedef std::function<void(Texture&, const VertexOutType&, const VertexOutType&, const VertexOutType&, float, float, float)> FragmentShader; UniformType uniform; VertexShader vert; FragmentShader frag; Texture* framebuffer; glm::ivec4 viewport; void rasterize(const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2) { // TO BE IMPLEMENTED } void renderTriangle(const VertexInType& i0, const VertexInType& i1, const VertexInType& i2) { const VertexOutType o0 = vert(uniform,i0,viewport); const VertexOutType o1 = vert(uniform,i1,viewport); const VertexOutType o2 = vert(uniform,i2,viewport); rasterize(o0,o1,o2); } void renderTriangles(const VertexInType* vertices, size_t vertexCount) { for(size_t i = 0; i < vertexCount; i += 3) { renderTriangle( vertices[i],vertices[i+1],vertices[i+2]); } } void renderTriangles(const VertexInType* vertices, unsigned* indices, size_t indexCount) { for(size_t i = 0; i < indexCount; i += 3) { renderTriangle(vertices[indices[i]],vertices[indices[i+1]],vertices[indices[i+2]]); } } }; #endif // RENDERINGPIPELINE_HPP Personally, I prefer the stateless approach, but hey, you do you. Still, for the purpose of this tutorial, we'll be going with the stateful approach, because it makes the code slightly more readable. To rasterize a triangle - a dead end So, what do we need to rasterize a triangle? There are two main schools of thought. The simpler - but slower - method is to create a bounding box, iterate over all the pixels it covers, find out if the pixel is within the triangle via some mathematical black magic, and then proceed. This is **not** the method we'll be using, but just for demonstration, I'll be present it in C++-style pseudocode: const int left = std::min(std::min(v0.POS.x,v1.POS.x),v2.POS.x); const int right = std::max(std::max(v0.POS.x,v1.POS.x),v2.POS.x); const int top = std::min(std::min(v0.POS.y,v1.POS.y),v2.POS.y); const int bottom = std::max(std::max(v0.POS.y,v1.POS.y),v2.POS.y); for(int x = left; x < right; ++x) { for(int y = top; y < bottom; ++y) { if( pixelIsInTriangle(x,y,v0,v1,v2)) { // Do rendering } } } So, what makes this method slow? Well, a pixel is in the triangle, if its weights are positive. So, the above code should be rewritten to.... const float area = edgeFunction(v0.POS,v1.POS,v2.POS); const int left = std::min(std::min(v0.POS.x,v1.POS.x),v2.POS.x); const int right = std::max(std::max(v0.POS.x,v1.POS.x),v2.POS.x); const int top = std::min(std::min(v0.POS.y,v1.POS.y),v2.POS.y); const int bottom = std::max(std::max(v0.POS.y,v1.POS.y),v2.POS.y); for(int x = left; x < right; ++x) { for(int y = top; y < bottom; ++y) { const glm::vec2 p = glm::vec2(float(x)+0.5f,float(y)+0.5f); const float w0 = edgeFunction(v1.POS, v2.POS, p) / area; const float w1 = edgeFunction(v2.POS, v0.POS, p) / area; const float w2 = edgeFunction(v0.POS, v1.POS, p) / area; if (w0 >= 0 && w1 >= 0 && w2 >= 0) { // Hurray, the pixel is in the triangle! // Now let's render! } } } But what is even this **edgeFunction**? I'm glad you asked, because we'll be using it even in our real pipeline, in the other algorithm I'll mention after I'm demonstrating why this bounding box algorithm is so bad. #include "EdgeFunction.hpp" float edgeFunction(const glm::vec2 &a, const glm::vec2 &b, const glm::vec2 &c) { return ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)); } float edgeFunction(const glm::vec3 &a, const glm::vec3 &b, const glm::vec3 &c) { return ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)); } float edgeFunction(const glm::vec3 &a, const glm::vec3 &b, const glm::vec2 &c) { return ((c.x - a.x) * (b.y - a.y) - (c.y - a.y) * (b.x - a.x)); } So yeah, this is the mathematical black magic I mentioned. With this edgy *"edge function"*, we find out how much weight does each vertex have on the current pixel, so we can calculate things like colour *(if we're using vertex colouring)*, texture coordinates, etc. In the bounding box algorithm, we're also using these weights to figure out, if the pixel is even within the triangle in the first place. Now, we'll be still using this function even within our better algorithm, but clearly, using this edge function for every pixel that may or may not be in the triangle is rather expensive, especially if at least half of all pixels are guaranteed to not even be within the triangle. We can do better, **and we will**. Enter the scanline algorithm Remember when I said that there are two schools of thought for rasterizing triangles, and that the one without the bounding box is better? Instead of wasting so much precious processing power on calculating the weights of pixels that aren't even in the triangle, we simply render scanline-by-scanline, already knowing where does each scanline end and the begin! void renderScanline(float areaReciprocal, int y, int minX, int maxX, const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2) { // Clamp the scanline's left and right ends into the viewport minX = std::max(minX,viewport[0]); maxX = std::min(maxX,viewport[2]); // Okay, let's render! for(int x = minX; x < maxX; ++x) { const glm::vec2 p = glm::vec2(float(x)+0.5f,float(y)+0.5f); const float w0 = edgeFunction(v1.POS, v2.POS, p) * areaReciprocal; const float w1 = edgeFunction(v2.POS, v0.POS, p) * areaReciprocal; const float w2 = edgeFunction(v0.POS, v1.POS, p) * areaReciprocal; frag(*framebuffer,glm::ivec2(x,y),uniform, v0,v1,v2,w0,w1,w2); } } Floating-point multiplication is always faster than floating-point division, so I recommend multiplying with the reciprocal of a number instead of dividing, if you know that you are going to be dividing a lot of different numbers with the same number. The more observant among you may have noticed that I also changed the fragment shader signature. typedef std::function<void(Texture&, const glm::ivec2&, const UniformType&, const VertexOutType&,const VertexOutType&, const VertexOutType&, float, float, float)> FragmentShader; So, what do we have now? What we have, is a way to render a single scanline, provided that we already know the line's beginning and end. But how will we know that? Obviously, we need to somehow divide up our triangle into scanlines. We need some kind of an algorithm for iterating over every line that the triangle is supposed to represent. But how? If the triangle has a flat bottom, the answer is dead simple: void fillBottomFlatTriangle(float areaReciprocal, const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2) { const float invslope1 = float(v1.POS.x - v0.POS.x) / float(v1.POS.y - v0.POS.y); const float invslope2 = float(v2.POS.x - v0.POS.x) / float(v2.POS.y - v0.POS.y); const int minY = std::clamp(int(std::trunc(v0.POS.y)),viewport[1],viewport[3]); const int maxY = std::clamp(int(std::trunc(v1.POS.y)),viewport[1],viewport[3]); for(int i = minY; i < maxY;++i) { const float dy = float(i) - v0.POS.y; const float curx1 = v0.POS.x + (invslope1 * dy); const float curx2 = v0.POS.x + (invslope2 * dy); renderScanline(areaReciprocal,i,int(std::trunc(curx1)),int(std::trunc(curx2)),v0,v1,v2); } } Okay, maybe not so simple, and there may be some stuff to unpack there. So, what's going on here? Truth be told, even I am a bit confused, but from what we can make out of this mathematical black magic, is that we're implying that **v0** has the top, **v1** and **v2** have the exact same Y coordinate, and we're calculating the slopes. Basically, we're finding out how long is each horizontal scanline going to be for a given vertical position within the triangle. But what if the triangle is upside down, and has a flat top? void fillTopFlatTriangle(float areaReciprocal, const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2) { const float invslope1 = float(v2.POS.x - v0.POS.x) / float(v2.POS.y - v0.POS.y); const float invslope2 = float(v2.POS.x - v1.POS.x) / float(v2.POS.y - v1.POS.y); const int minY = std::clamp(int(std::trunc(v0.POS.y)),viewport[1],viewport[3]); const int maxY = std::clamp(int(std::trunc(v2.POS.y)),viewport[1],viewport[3]); for(int i = minY; i < maxY;++i) { const float dy = float(i) - v2.POS.y; const float curx1 = v2.POS.x + (invslope1 * dy); const float curx2 = v2.POS.x + (invslope2 * dy); renderScanline(areaReciprocal,i,int(std::trunc(curx1)),int(std::trunc(curx2)),v0,v1,v2); } } Basically almost the same deal, except that the implications are reversed. Okay, so we can render top-flat and bottom-flat triangles. But what if our triangle isn't either? Or even if it was, how do we know if it is? Well, first, we'll need a new edge function: float edgeFunction(const glm::vec2 &a, const glm::vec2 &b, const glm::vec2 &c); float edgeFunction(const glm::vec3 &a, const glm::vec3 &b, const glm::vec3 &c); float edgeFunction(const glm::vec3 &a, const glm::vec3 &b, const glm::vec2 &c); template <typename Vertex> float edgeFunction(const Vertex& v0, const Vertex& v1, const Vertex& v2) { return edgeFunction(v0.POS,v1.POS,v2.POS); } Now, after this, we can finally work on the real deal... void rasterize(const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2) { const VertexOutType *t = &v0; const VertexOutType *m = &v1; const VertexOutType *b = &v2; // Sort by Y if (t->POS.y > m->POS.y) std::swap(t, m); if (m->POS.y > b->POS.y) std::swap(m, b); if (t->POS.y > m->POS.y) std::swap(t, m); const float dy = (b->POS.y - t->POS.y); const float iy = (m->POS.y - t->POS.y); if (m->POS.y == t->POS.y) { const VertexOutType *l = m, *r = t; if (l->POS.x > r->POS.x) std::swap(l, r); const float area = edgeFunction(*t,*r,*b); const float rArea = 1.0f / area; fillTopFlatTriangle(rArea, *l, *r, *b); } else if (m->POS.y == b->POS.y) { const VertexOutType *l = m, *r = b; if (l->POS.x > r->POS.x) std::swap(l, r); const float area = edgeFunction(*t,*l,*r); const float rArea = 1.0f / area; fillBottomFlatTriangle(rArea, *t, *l, *r); } else { // Split the triangle VertexOutType v4 = VertexOutType::split(*t,*m,*b,dy,iy); const VertexOutType *l = m, *r = &v4; if (l->POS.x > r->POS.x) std::swap(l, r); const float area1 = edgeFunction(*t,*l,*r); const float area2 = edgeFunction(*l,*r,*b); const float rArea1 = 1.0f / area1; const float rArea2 = 1.0f / area2; fillBottomFlatTriangle(rArea1, *t, *l, *r); fillTopFlatTriangle(rArea2, *l, *r, *b); } } Ho boy.... There's quite a lot to unpack there... Okay, so to unpack everything: It's assumed that after going through the vertex shader, the vertex coordinates are in screenspace, so basically, we're implying that `{-1,-1}` and `{1,1}` were respectively translated to `{0,640}` and `{480,0}` in the vertex shader. We first sort our vertices by the Y-coordinates. Because 0 is at the top, and 640 is at the bottom, to ensure that the first coordinate is on the top, we actually check for the Y-coordinate being less, not greater. **t** is upposed to be the top one, while **m** and **b** are the ones below it. We reserve two variables - **dy** and **iy** - for the height difference from the top vertex to the ones below it. If **m** has the same Y-coordinate as **t**, then hurray, our triangle has a **flat top**: we calculate its area and its reciprocal, and render it accordingly, but before that, we sort **m** and **t** according to their X-coordinate, calling the X-sorted vertices **l** and **r**. **l** is on the left, **r** is on the right, obviously. After all the sorting, we call **fillTopFlatTriangle()**. Alternatively, if **m** has the same Y-coordinate as **b**, we're dealing with a **flat-bottom triangle**. Same thing applies as before, except we X-sort **m** and **b** into **l** and **r**, and we call **fillBottomFlatTriangle()**. If the triangle is neither flat-bottom, nor flat-top, we need to split it into two triangles: one flat-top, one flat-bottom. This means that our vertex type needs to be a class or struct with the function **split()** implemented, returning that vertex type - this is also where the aforementioned variables **dy** and **iy** come in. Once we have our fourth vertex, we just sort vertices by X, calculate area for the two triangles, and render each. Finally, some action! Not so fast, you impatient child. Just like in the *"new" (15 years old, as of 2021)* programmable pipelines of OpenGL and Direct3D need to define our uniforms, vertex input and output types, and write our shaders. Except that our shaders will be written in C++, and will be sitting in our application's memory instead of being loaded from a file. So, basically... #ifndef BASICPIPELINE_HPP #define BASICPIPELINE_HPP #include <glm/glm.hpp> #include "Texture.hpp" #include "RenderingPipeline.hpp" struct BasicUniform { int dummy; // I'm only here because something has to be. }; struct BasicVertexIn { glm::vec3 POS; glm::vec4 COLOUR; }; struct BasicVertexOut { glm::vec2 POS; glm::vec4 COLOUR; inline static BasicVertexOut split(const BasicVertexOut& t, const BasicVertexOut& m, const BasicVertexOut& b, float dy, float iy) { return { glm::vec2( t.POS.x + ((b.POS.x - t.POS.x) / dy) * iy, m.POS.y ), glm::vec4( t.COLOUR.r + ((b.COLOUR.r - t.COLOUR.r) / dy) * iy, t.COLOUR.g + ((b.COLOUR.g - t.COLOUR.g) / dy) * iy, t.COLOUR.b + ((b.COLOUR.b - t.COLOUR.b) / dy) * iy, t.COLOUR.a + ((b.COLOUR.a - t.COLOUR.a) / dy) * iy ) }; } }; typedef RenderingPipeline<BasicVertexIn,BasicVertexOut,BasicUniform> BasicPipeline; BasicVertexOut basicVertexShader(const BasicUniform& uniform, const BasicVertexIn& vertex, const glm::ivec4& viewport); void basicFragmentShader(Texture& framebuffer, const glm::ivec2& point, const BasicUniform& uniform, const BasicVertexOut& v0,const BasicVertexOut& v1, const BasicVertexOut& v2, float w0, float w1, float w2); #endif // BASICPIPELINE_HPP #include "BasicPipeline.hpp" BasicVertexOut basicVertexShader(const BasicUniform &uniform, const BasicVertexIn &vertex, const glm::ivec4 &viewport) { const int viewportW = viewport[2] - viewport[0]; const int viewportH = viewport[3] - viewport[1]; return { glm::vec2( ((vertex.POS[0] + 1.0f) / 2.0f * viewportW) + viewport[0] , (((vertex.POS[1]-1.0f) / -2.0f) * viewportH) + viewport[1] ), vertex.COLOUR }; } void basicFragmentShader(Texture &framebuffer, const glm::ivec2 &point, const BasicUniform &uniform, const BasicVertexOut &v0, const BasicVertexOut &v1, const BasicVertexOut &v2, float w0, float w1, float w2) { if(point.x < 0 || point.y < 0) return; glm::vec4 colourKernel = { (w0 * v0.COLOUR.r) + (w1 * v1.COLOUR.r) + (w2 * v2.COLOUR.r), (w0 * v0.COLOUR.g) + (w1 * v1.COLOUR.g) + (w2 * v2.COLOUR.g), (w0 * v0.COLOUR.b) + (w1 * v1.COLOUR.b) + (w2 * v2.COLOUR.b), (w0 * v0.COLOUR.a) + (w1 * v1.COLOUR.a) + (w2 * v2.COLOUR.a) }; framebuffer.setPixel(point,colourKernel); } Okay, so to unpack everything: Our uniform contains nothing but a useless dummy integer that completely gets ignored. Our vertex input has a position and an associated colour. Ditto for our vertex output. When we split our triangle, we have to interpolate, hence the **dy** and **iy**. Our **vertex shader**'s only job is currently to project our triangle onto the screen, translating coordinates like `{0,1}`, `{-1,-1}` and `{1,-1}` into `{240,0}`, `{0,640}` and `{480,640}`. The **fragment shader** interpollates the vertex colours based on the vertex weights. It takes a weighted average of the vertex colours, basically. But does it actually work? Well, yes it does! But obviously, for that to happen, we'll need to modify our render system. #ifndef SOFTWARERENDERERSYSTEM_HPP #define SOFTWARERENDERERSYSTEM_HPP #include "AppSystem.hpp" #include "StandardPixelType.hpp" #include "BasicPipeline.hpp" class SoftwareRendererSystem : public AppSystem { public: typedef std::unique_ptr<SDL_Texture,decltype(&SDL_DestroyTexture)> uSdlTexture; typedef std::unique_ptr<SDL_Renderer,decltype(&SDL_DestroyRenderer)> uSdlRenderer; protected: uSdlRenderer renderer; uSdlTexture framebuffer; TextureRgba8 renderBuffer; BasicPipeline pipeline; void processEvent(const SDL_Event& ev, bool& causesExit); void updateLogic(); void render(); public: SoftwareRendererSystem(int width, int height); }; #endif // SOFTWARERENDERERSYSTEM_HPP #include "SoftwareRendererSystem.hpp" SoftwareRendererSystem::SoftwareRendererSystem(int width, int height) : AppSystem("Software Renderer Demo",0,0,width,height,0), renderer(SDL_CreateRenderer(this->window.get(),0,0),SDL_DestroyRenderer), framebuffer(SDL_CreateTexture(this->renderer.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width,height),SDL_DestroyTexture), renderBuffer(width,height) { pipeline.viewport[0] = 0; pipeline.viewport[1] = 0; pipeline.viewport[2] = width; pipeline.viewport[3] = height; pipeline.vert = basicVertexShader; pipeline.frag = basicFragmentShader; pipeline.framebuffer = &renderBuffer; pipeline.uniform = { 0 }; } void SoftwareRendererSystem::processEvent(const SDL_Event &ev, bool &causesExit) { switch(ev.type) { case SDL_QUIT: causesExit = true; break; default: break; } } void SoftwareRendererSystem::updateLogic() { } static const BasicVertexIn vertices[] = { { glm::vec3(0.0f, 1.0f,0.f), glm::vec4(1.0f, 0.0f, 0.0f, 1.0f) }, { glm::vec3(-1.0f, -1.0f,0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f) }, { glm::vec3(1.0f, -0.8f,0.0f), glm::vec4(0.0f, 0.0f, 1.0f, 1.0f) } }; void SoftwareRendererSystem::render() { pipeline.renderTriangle(vertices[0],vertices[1],vertices[2]); SDL_UpdateTexture(framebuffer.get(), nullptr, renderBuffer.getRawPixels(), renderBuffer.getStride() ); SDL_RenderCopy(renderer.get(), framebuffer.get(), nullptr, nullptr); SDL_RenderPresent(renderer.get()); SDL_Delay(16); } And voila, we got our first triangle! Next time, we'll be looking into Z-Buffers and rendering more complex shapes, with some textures added too. https://read.cash/@Metalhead33/software-rendering-3-textures-and-more-colours-62c350c8 As before, this tutorial is also uploaded onto Github. https://github.com/Metalhead33/SoftwareRendererTutorial

+12 more

@Metalhead33

Software Rendering Part 1: Setting it all up This article is a follow up of my previous article on hardware-accelerated graphics being a mistake. In addition to writing a rudementary software-renderer, I will be demonstrating to the masses more or less how GPUs work under the hood, at least to a degree. https://read.cash/@Metalhead33/hardware-acceleration-was-a-mistake-574a283d This article assumes that you already have some prior knowledge of the C++ language, specifically the C++11 variant, albeit some C++20 features might also be used. I will seek to produce readable code at the expense of performance. What software are we using? This software renderer is written in C++, specifically the C++20 variant, albeit we'll be mostly sticking to C++11 features. Discounting any libraries that import textures, or header-only libraries like GLM, the only external library we'll be relying on is SDL2. This article assumes you already know how to add dynamic linking libraries to projects. So upon up your favourite C++ IDE, and start a new project. I'll be using QtCreator. What greets us, is obviously Hello World.... #include <iostream> using namespace std; int main() { cout << "Hello world!" < endl; return 0; } So, what we need to do is to start working. Now, you may feel tempted to write C-style code, where we simply do this: #include <iostream> using namespace std; static const int WIDTH = 640; static const int HEIGHT = 480; int main() { SDL_Init(SDL_INIT_VIDEO); SDL_Window* win = SDL_CreateWindow("Software Renderer Tutorial",0,0,WIDTH,HEIGHT,0); bool isInterrupted=false; do { SDL_Event ev; while(SDL_PollEvent(&ev)) { switch(ev.type) { case SDL_QUIT: isInterrupted = true; break; default: break; } } } while(!isInterrupted); return 0; } But this will lead to difficult-to-read code on the long run. Sure, it's a lot of up-front investment to do, but I recommend already starting with wrapping SDL constructs within C++ classes from the very start, and encapsulating everything. A little bit of upfront investment After a *"little"* bit of refactoring. // AppSystem.hpp #ifndef APPSYSTEM_HPP #define APPSYSTEM_HPP #include <memory> #include <string> #include <SDL2/SDL.h> class AppSystem { public: typedef std::unique_ptr<SDL_Window,decltype(&SDL_DestroyWindow)> uWindow; private: AppSystem(const AppSystem& cpy) = delete; // Disable copy constructor AppSystem& operator=(const AppSystem& cpy) = delete; // Disable copy assignment operator protected: uWindow window; public: AppSystem(AppSystem&& mov); // Move constructor AppSystem& operator=(AppSystem&& mov); // Move assignment operator // Regular constructors virtual ~AppSystem() = default; AppSystem(const char *title, int offsetX, int offsetY, int width, int height, Uint32 flags); AppSystem(const std::string& title, int offsetX, int offsetY, int width, int height, Uint32 flags); void run(); }; #endif // APPSYSTEM_HPP #include "AppSystem.hpp" AppSystem::AppSystem(AppSystem&& mov) : window(std::move(mov.window)) { } AppSystem& AppSystem::operator=(AppSystem&& mov) { this->window = std::move(mov.window); return *this; } AppSystem::AppSystem(const char *title, int offsetX, int offsetY, int width, int height, Uint32 flags) : window(SDL_CreateWindow(title,offsetX,offsetY,width,height,flags),SDL_DestroyWindow) { } AppSystem::AppSystem(const std::string &title, int offsetX, int offsetY, int width, int height, Uint32 flags) : window(SDL_CreateWindow(title.c_str(),offsetX,offsetY,width,height,flags),SDL_DestroyWindow) { } void AppSystem::run() { bool isInterrupted=false; do { SDL_Event ev; while(SDL_PollEvent(&ev)) { switch(ev.type) { case SDL_QUIT: isInterrupted = true; break; default: break; } } } while(!isInterrupted); } There, much cleaner. In fact, I think we can go a little bit further... void AppSystem::processEvent(const SDL_Event &ev, bool &causesExit) { switch(ev.type) { case SDL_QUIT: causesExit = true; break; default: break; } } void AppSystem::updateLogic() { // Pls implement me } void AppSystem::render() { // Pls implement me } void AppSystem::run() { bool isInterrupted=false; do { SDL_Event ev; while(SDL_PollEvent(&ev)) { processEvent(ev,isInterrupted); } updateLogic(); render(); } while(!isInterrupted); } So, what did we actually do here? We applied RIIA onto the SDL_Window, which meant that we no longer have to manually create it or destroy it. We also split the initial main loop into three clearly separate functions, which allow us to cleanly reimplement them when needed. https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization https://wiki.libsdl.org/SDL_CreateWindow https://wiki.libsdl.org/SDL_DestroyWindow Effectively, we have a **run()** function that contains our loop, which keeps running **processEvent()**, **updateLogic()** and **render()** in that particular order until some event causes the program to quit. So, when are we going to render triangles? We'll get there in time. Be patient, young child. As you can see, this code so far doesn't do much so far. It creates a window, but doesn't even fill it with anything. So, before we proceed, I'm going to refactor just three things. class AppSystem { public: typedef std::unique_ptr<SDL_Window,decltype(&SDL_DestroyWindow)> uWindow; private: AppSystem(const AppSystem& cpy) = delete; // Disable copy constructor AppSystem& operator=(const AppSystem& cpy) = delete; // Disable copy assignment operator protected: uWindow window; virtual void processEvent(const SDL_Event& ev, bool& causesExit) = 0; virtual void updateLogic() = 0; virtual void render() = 0; public: // Regular constructors virtual ~AppSystem() = default; AppSystem(const char *title, int offsetX, int offsetY, int width, int height, Uint32 flags); AppSystem(const std::string& title, int offsetX, int offsetY, int width, int height, Uint32 flags); void run(); }; I'm fully aware that inheritance and the usage of virtual functions are highly frowned upon these days in the programming community, but this will aid code readability. So, now we're going to create a new class that subclasses AppSystem. I'll call it SoftwareRendererSystem. #ifndef SOFTWARERENDERERSYSTEM_HPP #define SOFTWARERENDERERSYSTEM_HPP #include "AppSystem.hpp" #include "StandardPixelType.hpp" class SoftwareRendererSystem : public AppSystem { public: typedef std::unique_ptr<SDL_Texture,decltype(&SDL_DestroyTexture)> uSdlTexture; typedef std::unique_ptr<SDL_Renderer,decltype(&SDL_DestroyRenderer)> uSdlRenderer; protected: uSdlRenderer renderer; uSdlTexture framebuffer; void processEvent(const SDL_Event& ev, bool& causesExit); void updateLogic(); void render(); public: SoftwareRendererSystem(int width, int height); }; #endif // SOFTWARERENDERERSYSTEM_HPP #include "SoftwareRendererSystem.hpp" SoftwareRendererSystem::SoftwareRendererSystem(int width, int height) : AppSystem("Software Renderer Demo",0,0,width,height,0), renderer(SDL_CreateRenderer(this->window.get(),0,0),SDL_DestroyRenderer), framebuffer(SDL_CreateTexture(this->renderer.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width,height),SDL_DestroyTexture) { } void SoftwareRendererSystem::processEvent(const SDL_Event &ev, bool &causesExit) { switch(ev.type) { case SDL_QUIT: causesExit = true; break; default: break; } } void SoftwareRendererSystem::updateLogic() { } void SoftwareRendererSystem::render() { SDL_RenderCopy(renderer.get(), framebuffer.get(), nullptr, nullptr); SDL_RenderPresent(renderer.get()); } #include <iostream> #include "SoftwareRendererSystem.hpp" using namespace std; static const int WIDTH = 640; static const int HEIGHT = 480; int main() { SDL_Init(SDL_INIT_VIDEO); SoftwareRendererSystem app(WIDTH,HEIGHT); app.run(); return 0; } And with this, we are finally producing something... But I think I owe you all an explanation on what just happened, or rather, what is all this new code. What does *SDL_TEXTUREACCESS_STREAMING* event mean?! Well, first of all, the *SDL_Renderer* takes care of presenting our texture on the screen. The *SDL_Texture* is, as its name says, a texture, the one that we'll be putting onto the screen, which makes it actually a framebuffer. We use **SDL_RenderCopy****()** to ensure that the renderer will present the texture, then **SDL_RenderPresent****()** to put it onto the screen. Just like with the Window, we are applying RIIA onto the Renderer and Texture. *SDL_TEXTUREACCESS_STREAMING* simply means that we'll be modifying the texture very often - like, uh, every single frame? https://wiki.libsdl.org/SDL_RenderCopy https://wiki.libsdl.org/SDL_RenderPresent https://wiki.libsdl.org/SDL_TextureAccess However, this black is obviously rather boring, so we'll need to find a way to put colour onto the screen. We'll need another texture - one we can access directly - to store pixel data we are currently modifying, and then we can run **SDL_UpdateTexture****()** to update our framebuffer from it, effectively creating a form of double-buffering. https://wiki.libsdl.org/SDL_UpdateTexture But first we need to do more up-front work! At this point, you might be pulling out your own hair, at the amount of work we have to do perform just to put some stinking pixels onto the screen, but all the up-front investment will pay off in the end. #ifndef TEXTURE_HPP #define TEXTURE_HPP #include <glm/glm.hpp> class Texture { public: virtual ~Texture() = default; // Data getters virtual int getWidth() const = 0; virtual float getWidthF() const = 0; virtual int getHeight() const = 0; virtual float getHeightF() const = 0; virtual int getStride() const = 0; // Pixel manipulation virtual void getPixel(const glm::ivec2& pos, glm::vec4& colourKernel) const = 0; inline glm::vec4 getPixel(const glm::ivec2& pos) const { glm::vec4 tmp; getPixel(pos,tmp); return tmp; } virtual void setPixel(const glm::ivec2& pos, const glm::vec4& colourKernel) = 0; virtual void* getRawPixels() = 0; virtual const void* getRawPixels() const = 0; }; #endif // TEXTURE_HPP Now, once again, I'm fully aware that I am making a million anti-OOP programmers scream in agony, as I am declaring an abstract class with pure virtual functions with the full intent of having other classes inherit it, but trust me, it'll improve code readability and re-usability. If performance was the main concern, I'd be probably relying on templates. In fact, speaking of templates... #ifndef STANDARDTEXTURE_HPP #define STANDARDTEXTURE_HPP #include "Texture.hpp" #include <vector> template <typename PixelType> class StandardTexture : public Texture { private: std::vector<PixelType> buff; int w,h,stride; float fw, fh; public: StandardTexture(int w, int h) : w(w), h(h), stride(w*sizeof(PixelType)), fw(w), fh(h), buff(w*h) { } int getWidth() const { return w; } float getWidthF() const { return fw; } int getHeight() const { return h; } float getHeightF() const { return fw; } int getStride() const { return stride; } // Pixel manipulation void getPixel(const glm::ivec2& pos, glm::vec4& colourKernel) const { const PixelType& pix = buff[((pos.y % h) * w) + (pos.x % w)]; pix.fillKernel(colourKernel); } virtual void setPixel(const glm::ivec2& pos, const glm::vec4& colourKernel){ PixelType& pix = buff[((pos.y % h) * w) + (pos.x % w)]; pix.fromKernel(colourKernel); } void* getRawPixels() { return buff.data(); } const void* getRawPixels() const { return buff.data(); } }; #endif // STANDARDTEXTURE_HPP With this trusty little template, we can easily wrap around any pixel format, as long as we feed it a struct that contains the **fillKernel** and **fromKernel** functions, both taking a reference to a **glm::vec4**. So, let's implement one. #ifndef STANDARDPIXELTYPE_HPP #define STANDARDPIXELTYPE_HPP #include "StandardTexture.hpp" #include <cstdint> struct PixelRgba8 { uint32_t rgba; static constexpr const float reciprocal = 1.0f / 255.0f; inline void fillKernel(glm::vec4& colourKernel) const { colourKernel.r = float(rgba >> 24 & 0xFF) * reciprocal; colourKernel.g = float(rgba >> 16 & 0xFF) * reciprocal; colourKernel.b = float(rgba >> 8 & 0xFF) * reciprocal; colourKernel.a = float(rgba & 0xFF) * reciprocal; } inline void fromKernel(const glm::vec4& colourKernel) { rgba = (uint32_t(colourKernel.r*255.0f) << 24) + (uint32_t(colourKernel.g*255.0f) << 16) + (uint32_t(colourKernel.b*255.0f) << 8) + uint32_t(colourKernel.a*255.0f); } }; typedef StandardTexture<PixelRgba8> TextureRgba8; #endif // STANDARDPIXELTYPE_HPP Okay, so now we can have textures of 8-bit RGBA type, that we can directly modify. I'm not going to get into details on how to manipulate pixels bitwise, there are already a million articles on that - if it's highly requested of me, I might get into dithering though. So anyway... What now? Well, we put it into our renderer! #ifndef SOFTWARERENDERERSYSTEM_HPP #define SOFTWARERENDERERSYSTEM_HPP #include "AppSystem.hpp" #include "StandardPixelType.hpp" class SoftwareRendererSystem : public AppSystem { public: typedef std::unique_ptr<SDL_Texture,decltype(&SDL_DestroyTexture)> uSdlTexture; typedef std::unique_ptr<SDL_Renderer,decltype(&SDL_DestroyRenderer)> uSdlRenderer; protected: uSdlRenderer renderer; uSdlTexture framebuffer; TextureRgba8 renderBuffer; void processEvent(const SDL_Event& ev, bool& causesExit); void updateLogic(); void render(); public: SoftwareRendererSystem(int width, int height); }; #endif // SOFTWARERENDERERSYSTEM_HPP #include "SoftwareRendererSystem.hpp" SoftwareRendererSystem::SoftwareRendererSystem(int width, int height) : AppSystem("Software Renderer Demo",0,0,width,height,0), renderer(SDL_CreateRenderer(this->window.get(),0,0),SDL_DestroyRenderer), framebuffer(SDL_CreateTexture(this->renderer.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width,height),SDL_DestroyTexture), renderBuffer(width,height) { } void SoftwareRendererSystem::processEvent(const SDL_Event &ev, bool &causesExit) { switch(ev.type) { case SDL_QUIT: causesExit = true; break; default: break; } } void SoftwareRendererSystem::updateLogic() { } void SoftwareRendererSystem::render() { for(int x = 0; x < renderBuffer.getWidth(); ++x) { for(int y = 0; y < renderBuffer.getHeight(); ++y) { const float r = float(x) / renderBuffer.getWidthF(); const float g = float(y) / renderBuffer.getHeightF(); renderBuffer.setPixel(glm::ivec2(x,y),glm::vec4(r,g,0.0f,1.0f)); } } SDL_UpdateTexture(framebuffer.get(), nullptr, renderBuffer.getRawPixels(), renderBuffer.getStride() ); SDL_RenderCopy(renderer.get(), framebuffer.get(), nullptr, nullptr); SDL_RenderPresent(renderer.get()); } Okay, so, explanation time. What does this code do now? The first thing that warrants explanation is the **SDL_UpdateTexture()** part. It copies the raw bytes out of our render buffer, and copies them into the framebuffer. So basically, we have a double-buffering of sorts going on. https://wiki.libsdl.org/SDL_UpdateTexture https://en.wikipedia.org/wiki/Multiple_buffering#Double_buffering_in_computer_graphics The loop above it is a a quick demonstration, where we basically draw a gradient that looks sorta like... Yeah, like this. But when are we going to render triangles?! Be patient, my child. We just took the first great step towards implementing software-rendering, manipulating pixels that are going to appear on the screen. Still, if you are THAT impatient, you get a sneak peak from the next episode: https://read.cash/@Metalhead33/software-rendering-2-pipelines-and-triangles-f4379638 #ifndef RENDERINGPIPELINE_HPP #define RENDERINGPIPELINE_HPP #include <functional> #include <glm/glm.hpp> #include "Texture.hpp" template<typename VertexInType, typename VertexOutType, typename UniformType> struct RenderingPipeline { typedef std::function<VertexOutType(const UniformType&, const VertexInType&, const glm::ivec4&)> VertexShader; typedef std::function<void(Texture&, const VertexOutType&, const VertexOutType&, const VertexOutType&, float, float, float)> FragmentShader; void rasterize(const UniformType& uniform, const VertexOutType& v0, const VertexOutType& v1, const VertexOutType& v2, Texture& framebuffer, const FragmentShader& frag) { // TO BE IMPLEMENTED } void renderTriangle(const UniformType& uniform, const VertexShader& vert, const FragmentShader& frag, const glm::ivec4& viewport, Texture& framebuffer, const VertexInType& i0, const VertexInType& i1, const VertexInType& i2) { const VertexOutType o0 = vert(uniform,i0,viewport); const VertexOutType o1 = vert(uniform,i1,viewport); const VertexOutType o2 = vert(uniform,i2,viewport); rasterize(uniform,o0,o1,o2,framebuffer,frag); } void renderTriangles(const UniformType& uniform, const VertexShader& vert, const FragmentShader& frag, const glm::ivec4& viewport, Texture& framebuffer, const VertexInType* vertices, size_t vertexCount) { for(size_t i = 0; i < vertexCount; i += 3) { renderTriangle(uniform,vert,frag,viewport,framebuffer, vertices[i],vertices[i+1],vertices[i+2]); } } void renderTriangles(const UniformType& uniform, const VertexShader& vert, const FragmentShader& frag, const glm::ivec4& viewport, Texture& framebuffer, const VertexInType* vertices, unsigned* indices, size_t indexCount) { for(size_t i = 0; i < indexCount; i += 3) { renderTriangle(uniform,vert,frag,viewport,framebuffer, vertices[indices[i]],vertices[indices[i+1]],vertices[indices[i+2]]); } } }; #endif // RENDERINGPIPELINE_HPP All code is uploaded to Github... except the sneak peak. https://github.com/Metalhead33/SoftwareRendererTutorial

+15 more

@Metalhead33

Hardware acceleration was a mistake From an engineering and consumer point of view, the existence of GPUs - or more precisely, hardware acceleration - was a mistake. My friend Geri wrote an amazing article about why software rendering is better than hardware-accelerated rendering from a *programmer's/developer's* point of view - I'm not here to steal his bread, so this time, I'll instead write about why it's also better from a consumer's point of view. https://read.cash/@Geri/software-rendering-is-better-than-directx-or-opengl-b0837449 It all boils down to one factor: complexity. Hardware-acceleration adds one more point of failure to the mix. With software-rendering, your experience depends almost completely on the speed of your CPU and your RAM - the GPU's only role is to convert the bits and bytes within the framebuffer into a picture for your monitor to display. This means, that if a game is being slow ~~it was poorly programmed~~ the solution is upgrading your CPU and your RAM. https://en.wikipedia.org/wiki/Delta_Force_(video_game) With hardware-accelerated graphics, it gets a little bit more complex. You see, when your AAA 3D game is being slow, upgrading your CPU may not make it any faster. The source of the problem may be your video card. One more part to upgrade, one more part that will inevitably go obsolete. And they even want to create dedicated AI accelerator hardware now! https://www.youtube.com/watch?v=owe9cPEdm7k https://en.wikipedia.org/wiki/Summoner_(video_game) It's utterly crazy. The solution is **less** dedicated hardware, not more! And even 12 years ago, people were agreeing with me. It was predicted, that one day, GPUs will disappear, because we'll have CPUs with so many cores, that they'll be able to run software renderers so fast - and so highly parallelized - that it'll be functionally indistinguishable from hardware-accelerated rendering. https://blog.codinghorror.com/i-happen-to-like-heroic-coding/ Now, quite paradoxically, this means that the IBM PC's greatest strength - which allowed it to beat Commodore, Apple, and all alternatives - is also its Achilles' Heel. The IBM PC that we have known and loved since 1981, has exchangeable parts. You can upgrade the RAM, you can replace the CPU, you can install a new sound card, a new GPU, and this is all great from a shelf-life point of view *(when you can just keep upgrading your PC like Theseus' Ship, it'll multiply its lifespan by at least ten)*, but this encouraged the problem I mentioned at the very beginning: these exchangeable parts became almost as important as the CPU itself, with programmers relying on them so much *(especially on GPUs with hardware accelerated graphics)*, and thus, people started to consider them integral parts of a PC, which meant, that one more item was added to the list of PC parts that go obsolete every three years. **Was it really worth it?** Couldn't we have been fine with software rendering driving the evolution of CPUs? Any piece of dedicated hardware requires its own API to program, to allow it to be taken full advantage of. When 3D acceleration first became a thing, every GPU had its own proprietary API: 3dfx had Glide, ATI had ATICIF, Nvidia had NVLIB, PowerVR had SGL, etc. Eventually, these were consolidated into just Direct3D and OpenGL *(though, Vulkan appeared in 2016ish)*, but as Geri pointed it out, this still managed to be a major headache for programmers. https://read.cash/@Geri/software-rendering-is-better-than-directx-or-opengl-d94704d6#its-getting-more-complicated So, with this in mind, my next article will be a C++ tutorial on software rendering. https://read.cash/@Metalhead33/software-rendering-part-1-setting-it-all-up-dbd3bd40

+2 more

@Metalhead33

Simplicity We desire it Humans desire simplicity. If you don't believe me, I'll try to prove it to you in this article. A lot of human emotions - such as nostalgia - and even political ideologies can be easily boiled down to the human desire for simplicity. **Politics** - why do people support authoritarian dictators, why do people vote for populist demagogues, why do people fetishize totalitarian governments? When 63 million people voted for Donald Trump and voted for his slogan *"Make America Great Again!"*, what they were really voting for was *"Make America simple again"* - or more precisely: *"make politics simple again"*. A return to a simpler time, when Americans had the evil Soviet boogeyman to project everything bad onto, when there was a clear good and bad. Likewise, people put up with tyrannical rulers, because having a single ruler *- a father figure of sorts, albeit a highly abusive and corrupted one -* to obey makes life so much simpler, than having a multitude of choices. **Nostalgia** - Why do millenials wear Zelda T-shirts, and other shirts that feature characters from video games they have been playing since childhood? Not because they are so enamored with the game's gameplay that they feel a compulsion towards turning themselves into walking advertisement boards for that specific game - it's because the game reminds them of simpler times, when their biggest worries were school grades and *"What will my parents think?"*. As I grew from a kindergarden child into an elementary school student, then to a high school student, then to a college student, then from a failed college student into a wageslave, I always lamented that life constantly got more complex. Desire for simplicity, does not a simpleton make People like my older brother like to associate a distaste for complexity and complex bureaucracy with low intelligence, but in truth, it's the opposite. People who are cursed with too high intelligence *(I'll talk about it later)* tend to have an obsession with reducing everything to models and patterns, which is the pinnacle of desire for simplicity. Not to mention, intellectuals and philosophers have Occam's Razor, which also betrays a desire for simplicity. https://en.wikipedia.org/wiki/Occam%27s_razor There is no shame in admitting, that all the Byzantine bureaucracy that haunts every adult in their day-to-day life makes your head hurt. But what do I precisely mean by having too high intelligence? Is there really such a thing as being smart for your own good? **Yes, there is.** Now, I don't mean to tout my own horn in this little segue, but when I was an elementary school child, I easily aced every class without having to waste a single second at home studiyng. I was considered a gifted child. Then, as I grew older, my luck started running dry, until college came, where I simply failed, being completely unable to get myself to face the book for more than half an hour. Unfortunately, I was lucky in my early childhood, and this early success deprived me of a once-in-a-lifetime opportunity to learn how to study, to learn discipline. Meanwhile, children less lucky - and less intelligent - than me had to struggle hard, and they actually reaped the rewards. So yeah, let that be a lesson for you kids: smart people don't necessarily prosper in life - disciplined people do. The same is true for all the complexity in our life. It doesn't take a particularly intelligent person to get through all the complexities of the Byzantine bureaucracy that plagues our daily life. It takes a **disciplined** person to navigate through the harsh waters of the devilish office oligarchy. Point of view Now, complexity and simplicity are not only relative, but also matters of points of view. I work as a software developer, and I know this more than anyone else. From the programmer's point of view, simplicity basically equals the least amount of effort spent coding something, or clean code. From a programmer's point of view, a *"simple"* program is one that required the minimal amount of coding and programming to be done by the programmer, which is coincidentally a program that'll be considered *"complex"* by most users. A program that is simple from the programmer's view doesn't try to guess what the user wants - it straight-up asks the questions, asks for parameters. In contrast, from a user's point of view, a *"simple"* program is one where the user has to input the least amount of parameters, where the user has to spend the least amount of time navigating the GUI, where the program remembers the user's previous actions, or better yet: where the software reads your mind and already knows what you. Needless to say, all these features add *complexity* to the actual implementation from the programmer's point of view, so clearly, we have a clash of two opposing points of view. The user doesn't want to input parameters that he thinks should be self-evident, while the programmer doesn't want to write thousands of lines of code in a desperate attempt to create a program that tries to read the user's mind, remember all the clicks, etc. From a programmer's point of view, a simple program means one that responds well to a specified type of input, provided that said input is provided in the exact format that was specified with zero tolerance for deviations - from a user's point of view, a simple program is the exact opposite: the user inputs data, and the program tries to interpret it, validate it, mine out the relevant information, etc. Obscurity through complexity, weaponized bureaucracy So, it's obvious that people aren't really fond of unnecessary complexity, and generally desire simplicity. Why is our modern world so needlessly complex then? There are three answers to that question, albeit they aren't mutually exclusive, and all of them stand up to scrutiny in my mind. The first, more innocent answer is, that many of the complexities of our world are necessary evils. Refer back to the previous paragraph, just substitute the two actors: the government is the programmer, the ordinary citizen is the user - what we consider simple is complex to them, what they consider simple is complex to us. It's frustrating for an average citizen - regardless of their intelligence - to be forced to fill out an exact form in a specific manner, and send it to a specific building on a specific date, with little tolerance for deviations - but for the government and its bureaucracy, this is the fastest way to do it, and the only way to receive documents that are qualified to go through computer-aided processing, etc. Individual human beings might be implicit in their thinking, and operate on fuzzy logic, computers of every kind - both electronic computers *(computers as understood in everyday language)*, and biological computers *(the bureaucratic system made up of human bureaucrats)* - think only in the explicit, and typically operate with strict boolean logic. The second, somewhat more innocent answer is, that many of these complexities are there for legacy reasons. They are no longer necessary, but humans are creatures of habit, and there is no desire for reform - or rather, people who consider it an inconvenience aren't hurt by it to the degree that causes them to scream for reform. Humans are creatures of habit, after all. The third, much more sinister answer is, is that it is by design. Just like the Corporatese language, the Byzantine bureaucracy was possibly intentionally designed to screw people over. It's hard to fight back against a lawsuit, when the legal system is so complex, and there are so many laws that make it difficult to prove one's innocence. If you are proven innocent on one count, you'll be proven guilty on another. Having to go through a bazillion laws and rights discourages small people who wish to sue others, as well as small people who wish to defend themselves against and accusation of a crime they may or may not have committed. The government already has its armada of goons *(police, military)* to make it impossible for people to violently contest the laws - the Byzantine bureaucracy serves as a secondary layer of defense to make it impossible for people to nonviolently contest the laws. Instead of having a few simple laws that cover most things, they intentionally chop them up to a million regulations, that each have to be contested one by one. https://www.youtube.com/watch?v=4ab2ZeZ-krY The Byzantine bureaucracy is so big, that you don't even know who are the ones actually standing above you. That's terror. Terror built into the system. What is there to do? I originally intended this article to be more of a philosophical one, than a political one. Yet, as I'm a creature of habit, it quickly got political. There is no such thing as a flawless, ideal political system. Every political system in existence has its flaws. A libertarian society is arguably the best thing ever *(no one likes being censored, overregulated, their rights curtailed, etc.)*, until disaster strikes you and makes you unable to support yourself through no fault of your own. An authoritarian society is not without its advantages either, as a dictator could potentially do great things for his country with his dictatorial powers - but most dictators simply abuse their power and become tyrannical. The flaws within libertarianism can be solved by compensating for the lack of social security with a strong culture of solidarity and families sticking together - likewise, the flaws within authoritarianism can be solved by having a strong culture of vigilance, where rulers are made to understand that they role by the people's grace. However, all societies eventually descend into decadence and stop compensating for the system's shortcomings, allowing for the system's weaknesses to *"shine"* through. Where was I going again? Right, so... There is no such thing as a perfect political system. If we reduce the bureaucracy, we also reduce the government's and corporations' ability to automate data-processing. The only way to reduce the complexity of our society without reducing the quality of life, would be to improve **Artificial Intelligence** to the point where it can handle all the bureaucratic mess, and leave humans to do what they're good at - or leave humans to do nothing. https://read.cash/@Metalhead33/is-labour-saving-technology-inherently-statist-e69bf4f7

+10 more