Thursday, December 31, 2020

Wrapping C API with C++ Part 2

In my last post I talked about adding RAII to a C API by wrapping it using guard objects. In this post I'm going to talk about dealing with an API that does not maintain its invariant. The API I'm using is fortunately (or unfortunately) the same SDL2 API from the last post, and the same SDL_Renderer management API. I found the same problem as discussed in this Stack Overflow question. The root of the problem are a pair of functions SDL_SetRenderTarget and SDL_GetRenderTarget. These are, it appears, intended to function similarly to SDL_RenderSetClipRect
and SDL_RenderGetClipRect. The Get version should return the same value as the Set version; but does not. The reason is in one of the answeres to the Stack Overflow question. The bug manifests in that, unless you save the Render Target yourself under the correct circumstances the program will not behave as intended.

There are at least two ways to solve this:

  1. I could modify my Renderer class to provide storage for the last Render Target set and modify RenderTargetGuard to work with that storage instead.
  2. I could use the C++ Standard Library container std::stack to implement a stack of past Render Targets.

I chose option 2. If my code was going to be creating and destroying many Renderers in the course of operation option 1 would have been better. But that is not the case; only a small number of Renderers are created at start up and used  throughout the program. Option 2 makes the Renderer class more complex but also lends itself to adding convenience functions to bring more of the functions of the SDL_Renderer API within the scope of idiomatic C++. Here is my new Renderer class (at the time this is written):

/**
* @class Renderer
* @brief Written as a workaround for an issue in the SDL2 Library.
* @details https://stackoverflow.com/questions/50415099/sdl-setrendertarget-doesnt-set-the-tartget
*/
class Renderer {
protected:
friend class RenderTargetGuard;

/**
* @brief A functor to destroy an SDL_Renderer
*/
class RendererDestroy {
public:
void operator()(SDL_Renderer *sdlRenderer) {
SDL_DestroyRenderer(sdlRenderer);
}
};

using RendererPtr = std::unique_ptr<SDL_Renderer, RendererDestroy>; ///< An SDL_Renderer unique pointer
RendererPtr mRenderer{}; ///< The Renderer.

std::stack<SDL_Texture*> mTargetTextureStack{}; ///< The stack of render targets

/**
* @brief Pop a render target off the stack. If there are none, set the default render target.
* @details The top of the stack is the current render target, unless the default is current in which
* case the stack will be empty. If the stack has 1 or 0 render targets on it, the renderer is set
* to the default target.
* @return The return status of SDL_SetRenderTarget
*/
[[nodiscard]] int popRenderTarget() {
if (mTargetTextureStack.empty())
return SDL_SetRenderTarget(mRenderer.get(), nullptr);
else {
mTargetTextureStack.pop();
if (mTargetTextureStack.empty())
return SDL_SetRenderTarget(mRenderer.get(), nullptr);
else
return SDL_SetRenderTarget(mRenderer.get(), mTargetTextureStack.top());
}
}

/**
* @brief Set a new render target, and push it onto the stack.
* @param texture The new render target.
* @return The return status of SDL_SetRenderTarget
*/
[[nodiscard]] int pushRenderTarget(sdl::Texture &texture) {
mTargetTextureStack.push(texture.get());
return SDL_SetRenderTarget(mRenderer.get(), texture.get());
}

/**
* @brief Set the render target to the default, and push it onto the stack.
* @return The return status of SDL_SetRenderTarget
*/
[[nodiscard]] int pushRenderTarget() {
mTargetTextureStack.push(nullptr);
return SDL_SetRenderTarget(mRenderer.get(), nullptr);
}

public:
/**
* Construct an empty renderer.
*/
Renderer() = default;

/**
* Construct a renderer associated with a Window.
* @param window The associated Window.
* @param index The index argument to SDL_CreateRenderer.
* @param flags The flags argument to SDL_CreateRenderer.
*/
Renderer(Window& window, int index, Uint32 flags);

/**
* @brief Move assignment operator.
* @param renderer The renderer to assign, this becomes empty after the assignment.
* @return A reference to this renderer.
*/
Renderer& operator=(Renderer&& renderer) = default;

/**
* @brief Test the renderer.
* @return false if the renderer is empty, true if it is valid.
*/
explicit operator bool () const noexcept { return mRenderer.operator bool(); }

/**
* @brief The the underlying SDL_Renderer* for use with the SDL2 API.
* @return An SDL_Renderer*
*/
[[nodiscard]] auto get() const { return mRenderer.get(); }

/**
* @brief Set the SDL_BlendMode on the renderer.
* @param blendMode
*/
void setDrawBlendMode(SDL_BlendMode blendMode) {
SDL_SetRenderDrawBlendMode(mRenderer.get(), blendMode);
}

/**
* @brief Create a Texture with the given size.
* @param size The Texture size.
* @return a new Texture object.
*/
Texture createTexture(SizeInt size);

/**
* @brief Copy source Texture to destination Texture and set the BlendMode on the destination Texture.
* @details The function uses RenderTargetGuard to temporarily set the render Target to the destination,
* calls SDL_RenderCopy to copy the texture, and sets the BlendMode on the destination texture to
* SDL_BLENDMODE_BLEND.
* @param source
* @param destination
*/
void copyFullTexture(sdl::Texture &source, sdl::Texture &destination);

/**
* @brief Calls SDL_RenderClear on the renderer.
* @return The return status from SDL_RenderClear.
*/
int renderClear() { return SDL_RenderClear(mRenderer.get()); }

/**
* @brief Calls SDL_RenderPresent on the renderer.
*/
void renderPresent() { SDL_RenderPresent(mRenderer.get()); }

/**
* @brief Calls SDL_RenderCopy to copy the source texture to the current render target.
* @details SDL_RenderCopy is called with nullptr for srcrect and dstrect.
* @param texture The texture to copy.
* @return The return status of SDL_RenderCopy.
*/
int renderCopy(Texture &texture) { return SDL_RenderCopy(mRenderer.get(), texture.get(), nullptr, nullptr); }

/**
* @brief Calls SDL_RenderFillRect after setting the RenderDrawColor to color.
* @details The existing RenderDrawColor is saved and restored.
* @param rectangle The rectangle to fill.
* @param color The fill color.
* @return The return status of SDL_RenderFillRect.
*/
int fillRect(RectangleInt rectangle, Color color);
};

And here is the new RenderTargetGuard class:

/**
* @class RenderTargetGuardException
* @brief Thrown by RenderTargetGuard on errors.
*/
class RenderTargetGuardException : public SdlRuntimeException {
public:
explicit RenderTargetGuardException(const std::string &what_arg) : SdlRuntimeException(what_arg) {}
};

/**
* @class RenderTargetGuard
* @brief Store the current render target replacing it with a new render target. When the object is
* destroyed (by going out of scope) the old render target is restored.
*/
class RenderTargetGuard {
protected:
Renderer &mRenderer;
bool popped{false};
int status{0};

public:
RenderTargetGuard() = delete;
RenderTargetGuard(const RenderTargetGuard&) = delete;

/**
* @brief Set the old render target back on the renderer when destroyed.
*/
~RenderTargetGuard() noexcept(false) {
if (!popped) {
status = mRenderer.popRenderTarget();
if (status)
throw RenderTargetGuardException(util::StringCompositor("Call to SDL_SetRenderTarget failed:",
SDL_GetError()));
}
}

/**
* @brief Test the status of the RenderTargetGuard.
* @details The status is good (true is returned) if the status value returned from the last
* operation on the Renderer object returned 0.
* @return True if the last operation succeeded.
*/
explicit operator bool () const noexcept { return status == 0; }

/**
* @brief Constructor
* @param renderer The renderer which render target will be managed.
* @param texture The texture which will become the new render target.
*/
RenderTargetGuard(Renderer &renderer, Texture &texture) : mRenderer(renderer) {
status = mRenderer.pushRenderTarget(texture);
}

/**
* @brief Clear the render target so rendering will be sent to the screen backing buffer.
*/
void clear() {
status = mRenderer.popRenderTarget();
popped = true;
}

/**
* @brief Set a new render target without pushing it on the stack.
* @details This may be used when a number of render target changes are needed in a context block.
* A RenderTargetGuard is created, calls to setRenderTarget are used to manipulate the render target.
* When the RenderTargetGuard goes out of scope the original render target is restored.
* @param texture
* @return
*/
int setRenderTarget(Texture &texture) {
status = SDL_SetRenderTarget(mRenderer.get(), texture.get());
return status;
}
};

Wednesday, December 9, 2020

Wrapping C APIs with C++

If you write in C++ it is nice not to have to switch mental gears back to C, but sometimes it is inevitable because many of the libraries and interfaces we use are written in C. There are many good reasons for this that you can find elsewhere.

I'm currently writing a GUI library targeting the Raspberry Pi frame buffer (no desktop GUI interface loaded) with touchscreen or mouse as the gesture interface. I am building this on top of SDL2, which uses a C interface style, so that's where my examples come from.

The first thing that normally comes up when talking about wrapping C APIs in C++ is how to deal with bear pointers. Of course the thing to do is use a smart pointer, either a std::unique_ptr or a std::shared_ptr depending on the context. Usually for a pointer that the calling program is responsible for deleting a std::unique_ptr is best, but give it some thought. One such bare pointer in the SDL2 libarary is the SDL_Renderer which is created by SDL_CreateRenderer and destroyed by SDL_DestroyRenderer. I usually create a function class to destroy the pointer, and declare a type alias for the std::unique_ptr:

/**
* @brief A functor to destroy an SDL_Renderer
*/
class RendererDestroy {
public:
void operator()(SDL_Renderer *sdlRenderer) {
SDL_DestroyRenderer(sdlRenderer);
}
};
///< An SDL_Renderer unique pointer
using Renderer = std::unique_ptr<SDL_Renderer, RendererDestroy>;

Once this is done you can use these in code and know that when the Renderer goes out of scope, the pointer will be properly destroyed and can't be used again. When you need to pass the actual C style bare pointer to an API call, simply use the get() method of the std::unique_ptr.

Renderer renderer = SDL_CreateRenderer(mSdlWindow.get(), -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
if (renderer) {
SDL_SetRenderDrawBlendMode(renderer.get(), SDL_BLENDMODE_BLEND);
} else {
mErrorCode = RoseErrorCode::SDL_RENDERER_CREATE;
std::cerr << "Could not create renderer: " << SDL_GetError() << '\n';
}

But that isn't all you can do to wrap up the API. Sometimes you wish to set a property on a pointer, use it within a scope and then remove it restoring the original state. This can involve a bunch of tedious temporary variables and ensuring calls with the correct variables are made in the correct sequence. Take this guard class for an SDL Clipping Rectangle for example:

/**
* @class ClipRectangleGuard
* @brief Store the current clip rectangle replacing it with a new clip rectangle. When the object is
* destroyed (by going out of scope) the old clip rectangle is set.
*/
class ClipRectangleGuard {
protected:
Renderer &mRenderer; ///< The renderer to which the clip rectangles are set.
SDL_Rect mOldClip{}; ///< The old clip rectangle

public:
ClipRectangleGuard() = delete;
ClipRectangleGuard(ClipRectangleGuard&) = delete;

/**
* @brief Set the old clip rectangle back on the renderer when destroyed.
*/
~ClipRectangleGuard() {
SDL_RenderSetClipRect(mRenderer.get(), &mOldClip);
}

/**
* @brief Constructor. Store the current clip rectangle and set the new one.
* @param renderer The renderer to set the clip rectangles on.
* @param clip The new clip rectangle.
*/
ClipRectangleGuard(Renderer& renderer, const SDL_Rect &clip) : mRenderer(renderer) {
SDL_RenderGetClipRect(mRenderer.get(), &mOldClip);
SDL_RenderSetClipRect(mRenderer.get(), &clip);
}

/**
* @brief Constructor. Store the current clip rectangle and set the new one.
* @param renderer The renderer to set the clip rectangles on.
* @param x X co-ordinate of the new clip rectangle.
* @param y Y co-ordinate of the new clip rectangle.
* @param w Width of the new clip rectangle.
* @param h Height of the new clip rectangle.
*/
ClipRectangleGuard(Renderer& renderer, int x, int y, int w, int h)
: ClipRectangleGuard(renderer, SDL_Rect{x,y,w,h}) {}
};

This is used to set a new Clipping Rectangle, while saving the current value, for the remainder of the scope. The original value is restored as soon as the guard is destroyed.

void ScrollArea::draw(Renderer &renderer, SizeInt size, PositionInt position) {
auto screenPos = getScreenPosition();
ClipRectangleGuard clipRectangleGuard(renderer, screenPos.x(), screenPos.y(), mSize.width(), mSize.height());
Container::draw(renderer, SizeInt{}, mScrollOffset);
}

These are two steps that can help to make calling C style APIs much less error prone by getting the details correct once, and then using C++ idiom from that point on.

Monday, June 24, 2019

Artificial Intelligence

TLDR

Artificial Intelligence is a very deeply mathematical subject. I won't be getting into the math to any great degree here, others have done a much better job of that. I will include some links you may visit if you wish. In case you want to skip ahead the points I try to make here are:
  • The math behind AI has been around a long, long time. Longer than computers.
  • "Learning" algorithms have been around almost as long as computers.
  • All "Learning" algorithms boil down to the premise that even if we can't devise a deterministic way of making a decision, we can always devise a probabilistic way of making the same decision.
  • Yes probabilistic decision making is just a fancy way of saying "flipping a coin" though rolling an n-sided die would be more accurate.

Bayesian Inference

 Bayesian inference a means of statistical inference (decision making) where Bayes' theorem is used to update the probability of an hypothesis (a decision) as more evidence becomes available. We use the term Bayesian as a nod to Thomas Bayes (1702-1761) who proved a special case of Bayes' theorem though much is also due to Pierre-Simon Laplace (1749–1827) who introduced a general version of the theorem. Charles Babbage began work on his difference engine, often called the first computing device, in 1819. However, the first Turing-complete computer was ENIAC completed in 1945.

A Bayesian inference could work like this: Let's say we are playing a coin flipping game. If the coin is fair then on each flip each side (heads or tails) has an equal chance of coming up. If the idea of the game is to predict which side will be up you will be able to, over the long term, correctly predict the result one half or 50% of the flips. But let's say the coin is not fair, but that heads comes up 70% of the time. If you continue to predict each flip as if the coin is fair you will continue to guess correctly half the time. You may, if you are paying attention, notice heads are coming up more often than tails. Using Bayesian inference you could alter your prediction strategy to increase the number of times you predict heads over tails. This will improve the number of guesses you get right. If you continue this strategy you will ultimately guess heads all the time. A simple program (see below) can simulate this with these results (for 100 million coin tosses for each test):

Coin bias: 0.5, Guess bias: 0.5, Heads: 50000082, Wins: 50004461, Losses: 49995539
Coin bias: 0.7, Guess bias: 0.5, Heads: 70006538, Wins: 50001384, Losses: 49998616
Coin bias: 0.7, Guess bias: 0.6, Heads: 69995464, Wins: 53996254, Losses: 46003746
Coin bias: 0.7, Guess bias: 0.9, Heads: 69998373, Wins: 66004210, Losses: 33995790
Coin bias: 0.7, Guess bias: 1.0, Heads: 70000956, Wins: 70000956, Losses: 29999044

Of course most AI programs use much more sophisticated algorithms to deal with much more complex situations, but they are all based generally on the Bayesian inference concept.

Fixed vs Continuous Learning

What I have described above is a form of continuous learning. If, as we are playing, you continue to monitor the heads to tails ratio of the coin you can continue to update your prediction strategy. The advantage here is that you can adapt to fair and unfair coins, even coins that favor tails. The disadvantage is the work you have to do.

The other strategy is fixed learning. You build a model of coin flipping from data you already have on hand, perhaps using Bayesian inference. If that data contains results from coins that favor heads as well as fair coins you may settle on a strategy of always guessing heads. Much less work going forward, but a big disadvantage if you encounter any unfair coins that were not represented in your data set.

Continuous learning is often the ideal solution, but it comes with two very large costs. First, training very sophisticated AI algorithms can require lots of computation. This implies large fast computers, or lots of time. Second, Training requires data to be presented to the algorithm, and a score assigned to the output of the algorithm. This score, often called the cost function, is essentially how far the algorithm result is from the

Markov Models

Bayesian inference, along with more advanced but similar techniques, are great for determining the probability single isolated events; determining if a picture is of a cat or dog, or some other kind of animal, or of the owner of a phone. But very often the problem is not to classify a single event but to classify a sequence of events. Speech to text is a very common example today with Siri, Alexa, Cortana and Google. This is where Markov models come in. A Markov model is a stochastic model (a description of a random process) used to model randomly changing systems. Clearly it would not be useful if the speech to be recognized was a truly random process; but since the content of speech can not usually be predicted until it has started it is useful to treat it as random.

From Markov models it is a short step to hidden Markov Models in which the process being modeled has observable states. This is a nice feature since it means the process states don't have to be enumerated before training starts, and training doesn't have to start over if new states are discovered or evolve. And hidden Markov models can be represented by simple dynamic Bayesian networks, bringing the problem nearly full circle. In a speech recognition system one might use each word as a state. Each word in the speech would be dependent on the previous word according to the language syntax.

So Bayesian inference allows us to adjust the probability of an event as we gather more information over time. Hidden Markov models allow us to reason about a sequence of events and draw inferences from the sequence. It doesn't take very many building blocks such as these to come up with a system that is eerily similar to what we call intelligence.

My History with AI

This is only the beginning of what we now call Artificial Intelligence, but it is enough to talk about my early experience and work with learning algorithms. I wrote my first learning algorithm in the 1970's when I was 16 years old. My program was based on an article I read in Scientific American which discussed some of these mathematical techniques. I wrote the program on a Digital pdp-8e (pictured on the top of this blog) made available by the local community college. The software was based on a very simple dynamic Bayesian network.

In the late 1980's I became involved in a programming project using these, and more advanced, techniques on Masscomp high performance real time mini-computers and Cray supper computers. During this work I was struck by two things: 1) knowing who the software worked from the inside out, and therefor knowing how easily it could be lead astray by bad data I was always carefully skeptical and cautiously optimistic; 2) people who did not understand or appreciate how the software worked would often believe the output implicitly and assume if the program generated an answer at all it would be the right answer. Things haven't changed much since, unfortunately.

My work with AI didn't end in the 1980's. I will probably write more when it is relevant to more recent topics I want to write about.

Pictures

Masscomp computer. Rhode Island Computer Museum.
Cray Y-MP
Cray T-94 System Source computer Museum

Software


Coin Toss Simulation

#include <iostream>
#include <random>
#include <functional>
#include <array>

class CoinToss {
public:
    CoinToss() = delete;
    template <class Sseq>
    explicit CoinToss(Sseq& seed, double weight = 0.5) : generator{seed}, weight{weight} {}

    int operator()() {
        auto v = distribution(generator);
        return v < weight ? 1 : 0;
    }

protected:
    double weight;
    std::mt19937 generator;
    std::uniform_real_distribution<double> distribution{0,1};
};

void run_test(double coin_bias, double guess_bias, int flip_count) {
    std::random_device randomDevice;
    std::seed_seq seedToss{randomDevice()};
    std::seed_seq seedGuess{randomDevice()};

    CoinToss    toss(seedToss, coin_bias);
    CoinToss    guess(seedGuess, guess_bias);
    int wins{0};
    int heads{0};

    std::cout << "Coin bias: " << coin_bias << ", Guess bias: " << guess_bias;

    for( int idx = 0; idx < flip_count; ++idx ) {
        auto t = toss();
        auto g = guess();
//        std::cout << t << ' ' << g << '\n';        if( t == g )
            ++wins;
        if( t )
            ++heads;
    }

    std::cout << ", Heads: " << heads << ", Wins: " << wins << ", Losses: " << flip_count - wins << '\n';
}

int main() {
    run_test( 0.5, 0.5, 100000000);
    run_test( 0.7, 0.5, 100000000);
    run_test( 0.7, 0.6, 100000000);
    run_test( 0.7, 0.9, 100000000);
    run_test( 0.7, 1.0, 100000000);
    return 0;
}

Tuesday, June 18, 2019

Reboot

Having retired, and with the end of BB10 approaching, I feel it is time to turn a page and make a fresh start with blogging. I'm not sure what is going to develop here but it will be information processing focused with the occasional personal post.

Subjects I have in mind are:
  • AI, Deep Learning, etc. - how application affect modern life.
  • On-Line instruction.
  • Web content management using primarily C++.