Today I shall discuss the issues that arise when migrating 32 bit code to 64 bit. Without falling into the typical jargon.
George is a city planner. He likes to plan out cities and determine who will live where.
There's also the home builder Alan. He likes building homes for people within cities.
Both have to work together when building new cities. Alan needs space for his houses. George needs to know how large Alan's houses will be.
Alan knows there are various types of houses. Small, medium, and large. They are 10 square feet, 15 square feet, and 20 square feet in usable floor space respectively. Then there are the variants, such as a bungalow which takes the same amount of space as a small home and a mansion which takes the same amount of space as a large home.
Sometimes, some of Alan's clients ask him to change a small home for a bungalow, etc. when this happens, he doesn't have to notify George since the amount of space needed is the same.
For years, the two have happily built houses forming complete cities without a hitch. Actually, they just copy the same city all over the place.
However, one day, Prime Minister Slim looked at what was being built. Bungalows, typically lived in by families, should be larger he ascertained. He saw families squished into their miniature bungalows. So, he put out a decree, all Bungalows shall be 15 feet, not 10.
For George, this entails he must alter how he plans the layout of his cities. For Alan, he has a problem. The interchangeable use of small houses and bungalows means he must inform George of which homes are larger than the initial plan.
And herein lies the problem with going from 32 bit to 64 bit. The person making the software has made a lot of assumptions that held true for years but are no longer the case.
Thursday, November 20, 2014
Sunday, November 2, 2014
SpriteKit and Normal Maps
Since iOS 8 SpriteKit now has some rudimentary lighting capabilities which has been a feature that I wanted for quite a while in a 2D engine. Below you'll find my thoughts and details related to performance and aesthetics.
First, there are many potential pitfalls that may destroy the frame rate of your application on devices that do not have an A7, or later, chip. The issues I discuss below tend to only crop up on low end devices. We will look at batching, the fragment shader, and potential optimizations.
Batching, as I have discovered, can be broken by having various sprites at various angles when a light shines upon them. As far as I can tell, the issue is that the implementation uses a single uniform to describe the angle of rotation when converting normals from view space to world space.
The fragment shader is hefty. On older devices, multiple lights can take a significant portion of the time used to render a frame. My intuition tells me that these devices are having trouble with the branching and looping found within the shader.
As an optimization, one option is to have lights affect as few objects as possible through the light mask. Another option, which is a bit more extreme, would be to have a custom fragment program that is fine tuned for the number of lights present in the scene. If you need to go further, then you may find yourself outgrowing SpriteKit and using Unity or a custom engine which tessellates around the image to minimize the number of times the fragment shader is run. And if you go that far, you might want to consider moving to a deferred pipe where lights are much less expensive, even if the frame rate would be passable at best on the low end devices.
If you are using batching (a folder with a .atlas extension), have a separate atlas for the colour and normal maps. If you don't, I've noticed SpriteKit generates its own rather than use the provided ones.
Second, aesthetics plays a pivotal role in whether this capability should be used. My comments are divided between the shadows and the lights.
Shadows are not as expensive as I would expect them to be. Shadows can be enabled through a mask to ensure only the most important objects are affected. If you have a series of images that share edges, like a series of blocks piled upon one another, then be careful if there is anti-aliasing in the image since the shadow extends the alpha portion of the image which may lead to weird lines that are undesired (a break in what should be a continuous flat shadow). Also, be sure to test on device as the algorithm seems to be slightly different. (It might be using the bounding box rather than the silhouette)
In terms of looks, I found that having a few lights led to some interesting results with little effort. Unfortunately, I believe much more could be done. What if the light is behind a sprite illuminating but its edges? At the moment it feels like it's slightly above the sprites thus illuminating them directly. A light is like a 3D object in this 2D world, limiting it to two axis reduces the number of possible effects.
In the end, SpriteKit introduces a very rudimentary concept of lighting. It is not sophisticated, but sufficient to add a little extra detail to an application. On the down side, it is too easy to hit the limits of what the implementation can do and be forced to change technology.
Edited on February 27th, 2015: edited the text for legibility. It seems to have been written when I was exhausted, hence barely comprehensible. Certain details I did not have offhand (how shadows behave on device versus on the Mac), and will fill that in later when I get the chance. Doesn't deviate from the fact that testing on device is always a good idea.
I have written a quick set of thoughts when SpriteKit first came out.
First, there are many potential pitfalls that may destroy the frame rate of your application on devices that do not have an A7, or later, chip. The issues I discuss below tend to only crop up on low end devices. We will look at batching, the fragment shader, and potential optimizations.
Batching, as I have discovered, can be broken by having various sprites at various angles when a light shines upon them. As far as I can tell, the issue is that the implementation uses a single uniform to describe the angle of rotation when converting normals from view space to world space.
The fragment shader is hefty. On older devices, multiple lights can take a significant portion of the time used to render a frame. My intuition tells me that these devices are having trouble with the branching and looping found within the shader.
As an optimization, one option is to have lights affect as few objects as possible through the light mask. Another option, which is a bit more extreme, would be to have a custom fragment program that is fine tuned for the number of lights present in the scene. If you need to go further, then you may find yourself outgrowing SpriteKit and using Unity or a custom engine which tessellates around the image to minimize the number of times the fragment shader is run. And if you go that far, you might want to consider moving to a deferred pipe where lights are much less expensive, even if the frame rate would be passable at best on the low end devices.
If you are using batching (a folder with a .atlas extension), have a separate atlas for the colour and normal maps. If you don't, I've noticed SpriteKit generates its own rather than use the provided ones.
Second, aesthetics plays a pivotal role in whether this capability should be used. My comments are divided between the shadows and the lights.
Shadows are not as expensive as I would expect them to be. Shadows can be enabled through a mask to ensure only the most important objects are affected. If you have a series of images that share edges, like a series of blocks piled upon one another, then be careful if there is anti-aliasing in the image since the shadow extends the alpha portion of the image which may lead to weird lines that are undesired (a break in what should be a continuous flat shadow). Also, be sure to test on device as the algorithm seems to be slightly different. (It might be using the bounding box rather than the silhouette)
In terms of looks, I found that having a few lights led to some interesting results with little effort. Unfortunately, I believe much more could be done. What if the light is behind a sprite illuminating but its edges? At the moment it feels like it's slightly above the sprites thus illuminating them directly. A light is like a 3D object in this 2D world, limiting it to two axis reduces the number of possible effects.
In the end, SpriteKit introduces a very rudimentary concept of lighting. It is not sophisticated, but sufficient to add a little extra detail to an application. On the down side, it is too easy to hit the limits of what the implementation can do and be forced to change technology.
Edited on February 27th, 2015: edited the text for legibility. It seems to have been written when I was exhausted, hence barely comprehensible. Certain details I did not have offhand (how shadows behave on device versus on the Mac), and will fill that in later when I get the chance. Doesn't deviate from the fact that testing on device is always a good idea.
I have written a quick set of thoughts when SpriteKit first came out.
Sunday, October 19, 2014
The Code Fallacy
Once upon a time, there was a master in the arts of taping things together. His works were shown in museums all over the world. To the untrained eye, they may appear as loose balls of duct tape dangling from the ceiling, however they were built in a very particular fashion.
First, the master was given hundreds of strands of tape of various sizes. Each strand was numbered. Each numbered strand had to connect to a specific set of other numbered strands. The master's work, which set him apart from the rest, was his ability to plan out and leave a lot of space between the strands of tape, minimizing the entanglement, and making it possible to easily add or remove connections between pieces of tape as needed. And there were always changes.
Typically, the master worked at his own pace. It felt frigid to most, as he'd spend much longer getting his initial ball of tape according to requirements.
One day, the master was asked to make a work and was given a standard set of specifications. Except he had a month to do it. He'd normally take three. However, he had no choice, his secretary had already agreed on his behalf.
So he started planning. He spent all of his days rather than reserve some time to rest. By the second week, the locals described him as a zombie, barely aable to articulate himself, always forcing himself to work on the problem with which he was tasked.
By the third week, he had to start building his ball of tape. As requests for changes would pour in the next day. Except - has wasn't ready. His plan had pieces of tape coming too close for comfort, but he had no choice but to continue. Feeling sick to his stomach as he assembled a work that he, the master of tape, could never be proud of.
Then, the day before, requests for 150 new strands of tape to be added came in. The ball would comprise of about 1000 strands of tape total. He worked through the night, as his initial plan was half-baked, and any new piece of tape was frought with peril. Often, he had to move hundreds of pieces of tape just to add a new one. And with these changes, the stickiness of the tape waned. And the master had to resort to using new pieces of tape to ensure the integrity of the old ones.
By morning, when he was done, he looked at his ball of tape, improperly drooping, knowing that it would damage his reputation. He had done it on time, but it was his worse work. And when it came to be put up, it looked as though it would collapse. But it didn't.
The week after, the master got a request to add a few hundred more strands and to remove fifty. In a day. He looked at the ball. He looked at the request, and replied that it couldn't be done. He would neeed to spend two months to complete his plan and build it correctly. His rejection was rejected, and was reminded that he must do the changes since if he didn't the museum would go bankrupt for having to refund thtickets to see the ball that it had already sold.
Not wanting the demise of a museum to be his fault, he got to work. By morning, he still had a dozen strands to put up. The museum's curator was angru with him, for not completing his work on time. The museum suffered, as people requested refunds seeing that the ball of tape was not ready for viewing on time. The master was blamed.
And so things continued for the rest of the contract. Always having to deal with a ball of tape that should have been thought out more thoroughly, and regretting it untilt he completion of the contract.
The curator and the visitors couldn't tell it was improperly built. They just saw a ball of tape. It had all the right connections. But to the master, it was an abomination.
Henceforth, he ensured that his secretaary nnever agreed to time limits again. So he may do his work properly and be proud.
----
What does this have to do with software?
The ball of tape is a metaphore for software. Aggressive schedules always backfire in the long run. Like the ball of tape, how can you judge how well software is written when you only see a polished interface with an engine forever hidden bedhind it.
First, the master was given hundreds of strands of tape of various sizes. Each strand was numbered. Each numbered strand had to connect to a specific set of other numbered strands. The master's work, which set him apart from the rest, was his ability to plan out and leave a lot of space between the strands of tape, minimizing the entanglement, and making it possible to easily add or remove connections between pieces of tape as needed. And there were always changes.
Typically, the master worked at his own pace. It felt frigid to most, as he'd spend much longer getting his initial ball of tape according to requirements.
One day, the master was asked to make a work and was given a standard set of specifications. Except he had a month to do it. He'd normally take three. However, he had no choice, his secretary had already agreed on his behalf.
So he started planning. He spent all of his days rather than reserve some time to rest. By the second week, the locals described him as a zombie, barely aable to articulate himself, always forcing himself to work on the problem with which he was tasked.
By the third week, he had to start building his ball of tape. As requests for changes would pour in the next day. Except - has wasn't ready. His plan had pieces of tape coming too close for comfort, but he had no choice but to continue. Feeling sick to his stomach as he assembled a work that he, the master of tape, could never be proud of.
Then, the day before, requests for 150 new strands of tape to be added came in. The ball would comprise of about 1000 strands of tape total. He worked through the night, as his initial plan was half-baked, and any new piece of tape was frought with peril. Often, he had to move hundreds of pieces of tape just to add a new one. And with these changes, the stickiness of the tape waned. And the master had to resort to using new pieces of tape to ensure the integrity of the old ones.
By morning, when he was done, he looked at his ball of tape, improperly drooping, knowing that it would damage his reputation. He had done it on time, but it was his worse work. And when it came to be put up, it looked as though it would collapse. But it didn't.
The week after, the master got a request to add a few hundred more strands and to remove fifty. In a day. He looked at the ball. He looked at the request, and replied that it couldn't be done. He would neeed to spend two months to complete his plan and build it correctly. His rejection was rejected, and was reminded that he must do the changes since if he didn't the museum would go bankrupt for having to refund thtickets to see the ball that it had already sold.
Not wanting the demise of a museum to be his fault, he got to work. By morning, he still had a dozen strands to put up. The museum's curator was angru with him, for not completing his work on time. The museum suffered, as people requested refunds seeing that the ball of tape was not ready for viewing on time. The master was blamed.
And so things continued for the rest of the contract. Always having to deal with a ball of tape that should have been thought out more thoroughly, and regretting it untilt he completion of the contract.
The curator and the visitors couldn't tell it was improperly built. They just saw a ball of tape. It had all the right connections. But to the master, it was an abomination.
Henceforth, he ensured that his secretaary nnever agreed to time limits again. So he may do his work properly and be proud.
----
What does this have to do with software?
The ball of tape is a metaphore for software. Aggressive schedules always backfire in the long run. Like the ball of tape, how can you judge how well software is written when you only see a polished interface with an engine forever hidden bedhind it.
Monday, September 15, 2014
Ads within Blogger
Being the good capitalist, I thought to myself, why not have ads at the bottom of the page? if what the reader reads is interesting, and they read it in totality, I doubt that they'd mind a simple little ad at the bottom of the page.
So it began, going through the simple few clicks to request the permission to put ads on this blog. Things went as smooth as butter, I must admit. Within a day it was approved! All that was left was to agree to the TOS and finalize what would be presented and how.
As you may notice, there are no ads at the bottom. The TOS has a new frightening set of clauses that I have not yet encountered before. That of various clauses surviving indefinitely even after use of the service terminates.
Typically, I would see a timeframe on the survival, a few years at most. And that is on NDAs that I've signed with previous employers.
It may be argued that the ads service is different - that it does offer a way to interact with various entities where abuse would lead to extra money for whoever benefits from placing the ad. Sure.
However, everything is becoming a service. Look at Photoshop - you just rent it. Same for the developer tools from Apple. Many things that you used to indefinitely license for a single large sum are being rented on a monthly basis.
MMOs, one of gamings rented games, can be abused like Google ads to make money. These games typically have local economies and some people will pay real money for what others picked up over long periods of play. Farming for gold pops to mind. Should a video game have a contract with clauses that indefinitely survive termination?
For legal purposes, it is easy to assume that anything that is done over the Internet is logged. Every conversation, every photo. Be it by the state (see the NSA insanity) or by individual providers (wasn't it reddit that said they can't resist handing logs to suits?)... of course this survival is used to ensure that if something sketchy appears after termination, it can be caught and dealt with.
But back to the issue - can the survival of termination be abused? Could it ever become a boon years down the road as companies go back to these old contracts and try to find any reason to extract funds from old users? I'm not implying any company would do this, but the door could very well be opened at some point.
Or so I believe. Reading these myriad of software EULAs and TOS just bore me to death. And given I'm not a lawyer, I'm sure I've misread a few as I sometimes read clauses multiple times to understand an English that, since it leaves no room to interpretation, can be quite dense.
But typically these end. Mor can be terminated if I got the gist wrong during the first reading. This one doesn't forgive.
On a side-note, I can completely understand why no-one reads these things. They are long, and typically can be summarized as "we are not responsible for anything (nor have any obligation towards you), you are responsible. Oh, and give us money if it's a paid service!"...
So it began, going through the simple few clicks to request the permission to put ads on this blog. Things went as smooth as butter, I must admit. Within a day it was approved! All that was left was to agree to the TOS and finalize what would be presented and how.
As you may notice, there are no ads at the bottom. The TOS has a new frightening set of clauses that I have not yet encountered before. That of various clauses surviving indefinitely even after use of the service terminates.
Typically, I would see a timeframe on the survival, a few years at most. And that is on NDAs that I've signed with previous employers.
It may be argued that the ads service is different - that it does offer a way to interact with various entities where abuse would lead to extra money for whoever benefits from placing the ad. Sure.
However, everything is becoming a service. Look at Photoshop - you just rent it. Same for the developer tools from Apple. Many things that you used to indefinitely license for a single large sum are being rented on a monthly basis.
MMOs, one of gamings rented games, can be abused like Google ads to make money. These games typically have local economies and some people will pay real money for what others picked up over long periods of play. Farming for gold pops to mind. Should a video game have a contract with clauses that indefinitely survive termination?
For legal purposes, it is easy to assume that anything that is done over the Internet is logged. Every conversation, every photo. Be it by the state (see the NSA insanity) or by individual providers (wasn't it reddit that said they can't resist handing logs to suits?)... of course this survival is used to ensure that if something sketchy appears after termination, it can be caught and dealt with.
But back to the issue - can the survival of termination be abused? Could it ever become a boon years down the road as companies go back to these old contracts and try to find any reason to extract funds from old users? I'm not implying any company would do this, but the door could very well be opened at some point.
Or so I believe. Reading these myriad of software EULAs and TOS just bore me to death. And given I'm not a lawyer, I'm sure I've misread a few as I sometimes read clauses multiple times to understand an English that, since it leaves no room to interpretation, can be quite dense.
But typically these end. Mor can be terminated if I got the gist wrong during the first reading. This one doesn't forgive.
On a side-note, I can completely understand why no-one reads these things. They are long, and typically can be summarized as "we are not responsible for anything (nor have any obligation towards you), you are responsible. Oh, and give us money if it's a paid service!"...
Sunday, September 14, 2014
Aren't games counter productive?
For the longest time, I tried to reign in my desire to play games so I may be productive at other things. The problem was, no matter how much control I exerted over myself, there was not net increase in productivity.
Then it dawned upon me: I'm a Sim. There's a progress bar that craves the entertainment provided by video games. And that bar was empty.
So I binged, and played about 14 hours of a game in two days. By the end of it, I was fed up, my meter completely filled, I could then do something else such as work on a web comic. Read, work on a web comic and be happy about it, laughing as the details came together.
It seems that any rhetoric that advocates for anything in the extreme is worrisome. A balanced life requires play in addition to work. And work can't proceed as efficiently until neglected needs are satiated.
That's my theory, and I'm sticking to it. Mainly since it makes me happier. :)
It seems that any rhetoric that advocates for anything in the extreme is worrisome. A balanced life requires play in addition to work. And work can't proceed as efficiently until neglected needs are satiated.
That's my theory, and I'm sticking to it. Mainly since it makes me happier. :)
Thursday, September 11, 2014
Digging a bit further into SpriteKit
A few throw-away prototypes later, and a few things about SpriteKit have become apparent to me.
First -- SpriteKit is highly optimized for image data. That is -- sprites. It is very efficient, put your images in a ".atlas" folder and a texture atlas is built reducing your costly draw calls from dozens (or hundreds depending on your app) to one.
Caveat, that is what SpriteKit does best.
As of writing, a text node will defer to core text Core text will create a texture. That will be slapped onto, what I guess is, an SKSpriteNode. Each text element will incur an additional draw call (even if the text is duplicated - each duplicate will make a new texture. Copying the SKTextNode results in a copy of the texture as well). For a simple HUD, this is perfectly acceptable. I dreamt of a text adventure recently, and SpriteKit would need a bitmap font rendered as SKSpriteNodes even though another framework would be just as good.
The other is drawing filled rects. My best guess is that they are there as placeholders for an actual image-based sprite object. They too don't batch that well together, which did surprise me.
How to track the draw calls? Two ways. One is to do a frame capture while the application is running on device. The other, which I find more convenient, is to set the "showDrawCount" property of the "SKView" to true. Turning on that property was an eye opener.
If we return to the colour-based SKSpriteNodes, be sure to give them a separate zPosition so they blend properly with the other nodes. I have noticed that the drawing order differs when batching becomes possible.
Speaking of "zPosition"; it is safe to assume that each "zPosition" will result in an additional draw call. The furthest are drawn first in one call, the nearer in subsequent call. The mental model that I create for myself is that of creating a bin for each "zPositon" and dispatching each bin at the end.
Before making an image for each possible colour combination, tinting does not affect the number of draw calls. If you were a stubborn person, you could make a white rectangle, create a sprite, and tint it to your heart's desire.
Notice that I have not delved into physics. None of my little experiments needed (sophisticated) physics so far hence I can't comment on how that part of SpriteKit behaves.
Overall, I'm actually quite happy with SpriteKit. The caveats I mention are minimal. Implementing (or finding an implementation of) a bitmap font for SpriteKit is rather trivial with building the font being the time consuming part. And as mentioned, coloured blocks can be simulated with flat textures.
And, in most real-world use-cases, I would guess that the caveats I mention are neglible. For example, when will you display 100 SKLabelNodes? Or do heavy pixelation with 10,000 SKSpriteNodes?
First -- SpriteKit is highly optimized for image data. That is -- sprites. It is very efficient, put your images in a ".atlas" folder and a texture atlas is built reducing your costly draw calls from dozens (or hundreds depending on your app) to one.
Caveat, that is what SpriteKit does best.
As of writing, a text node will defer to core text Core text will create a texture. That will be slapped onto, what I guess is, an SKSpriteNode. Each text element will incur an additional draw call (even if the text is duplicated - each duplicate will make a new texture. Copying the SKTextNode results in a copy of the texture as well). For a simple HUD, this is perfectly acceptable. I dreamt of a text adventure recently, and SpriteKit would need a bitmap font rendered as SKSpriteNodes even though another framework would be just as good.
The other is drawing filled rects. My best guess is that they are there as placeholders for an actual image-based sprite object. They too don't batch that well together, which did surprise me.
How to track the draw calls? Two ways. One is to do a frame capture while the application is running on device. The other, which I find more convenient, is to set the "showDrawCount" property of the "SKView" to true. Turning on that property was an eye opener.
If we return to the colour-based SKSpriteNodes, be sure to give them a separate zPosition so they blend properly with the other nodes. I have noticed that the drawing order differs when batching becomes possible.
Speaking of "zPosition"; it is safe to assume that each "zPosition" will result in an additional draw call. The furthest are drawn first in one call, the nearer in subsequent call. The mental model that I create for myself is that of creating a bin for each "zPositon" and dispatching each bin at the end.
Before making an image for each possible colour combination, tinting does not affect the number of draw calls. If you were a stubborn person, you could make a white rectangle, create a sprite, and tint it to your heart's desire.
Notice that I have not delved into physics. None of my little experiments needed (sophisticated) physics so far hence I can't comment on how that part of SpriteKit behaves.
Overall, I'm actually quite happy with SpriteKit. The caveats I mention are minimal. Implementing (or finding an implementation of) a bitmap font for SpriteKit is rather trivial with building the font being the time consuming part. And as mentioned, coloured blocks can be simulated with flat textures.
And, in most real-world use-cases, I would guess that the caveats I mention are neglible. For example, when will you display 100 SKLabelNodes? Or do heavy pixelation with 10,000 SKSpriteNodes?
Wednesday, September 10, 2014
Return to Class
It has been over 5 years since I last attended a lecture within a formal class setting. Lately, some classes in mathematics have gotten my attention, and since I don't care about the degree anymore, why not just sit in and enjoy the show?
There were two classes that I requested to sit in, and the professors were very kind and said I was welcome as long as I didn't add any work. Fair.
Not caring whether I passed or failed, I opted to go to the more advanced course first. In this class, the students were silent and respected the professor. It was also a challenging class, my mind needed to work quite a bit to follow what was being said.
Even though I could follow if I put in the effort, work was looming over me so I conceded and opted to try the lower-level class. The culture was completely different. Throughout I heard other students talking, playing games, navigating Facebook, eating lunch, questioning the professor... (the prof knew his stuff inside out). There was a sense of boredom, as though similar material has been seen over and over again (set theory is, admittedly, kind of basic -- and certain concepts can be seen in other classes such as linear algebra). I felt bad for the students who just sat there playing your colour-matching game-du-jour; they would learn just as much sitting outside (it was this high-school-esque forced attendance rather than this "I wish to learn" voluntary attendance.
Then came the stark realization; I'm better off listening to the lectures that are put out by the major universities. Even though I enjoy having forced times, these past few times I went to the lectures my mind wasn't thinking of math but of algorithms.
And then my thirst for knowledge would follow my work habits and be satiated when my mind is focused on the subject.
Since I don't care about grades.... why not?
There were two classes that I requested to sit in, and the professors were very kind and said I was welcome as long as I didn't add any work. Fair.
Not caring whether I passed or failed, I opted to go to the more advanced course first. In this class, the students were silent and respected the professor. It was also a challenging class, my mind needed to work quite a bit to follow what was being said.
Even though I could follow if I put in the effort, work was looming over me so I conceded and opted to try the lower-level class. The culture was completely different. Throughout I heard other students talking, playing games, navigating Facebook, eating lunch, questioning the professor... (the prof knew his stuff inside out). There was a sense of boredom, as though similar material has been seen over and over again (set theory is, admittedly, kind of basic -- and certain concepts can be seen in other classes such as linear algebra). I felt bad for the students who just sat there playing your colour-matching game-du-jour; they would learn just as much sitting outside (it was this high-school-esque forced attendance rather than this "I wish to learn" voluntary attendance.
Then came the stark realization; I'm better off listening to the lectures that are put out by the major universities. Even though I enjoy having forced times, these past few times I went to the lectures my mind wasn't thinking of math but of algorithms.
And then my thirst for knowledge would follow my work habits and be satiated when my mind is focused on the subject.
Since I don't care about grades.... why not?
Subscribe to:
Posts (Atom)