<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://joeonit.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://joeonit.github.io/" rel="alternate" type="text/html" /><updated>2026-06-04T18:04:45+00:00</updated><id>https://joeonit.github.io/feed.xml</id><title type="html">Youssef’s Blog</title><subtitle>Personal blog</subtitle><entry><title type="html">Week 1: gr-cyberether takes shape</title><link href="https://joeonit.github.io/gsoc/week1" rel="alternate" type="text/html" title="Week 1: gr-cyberether takes shape" /><published>2026-06-04T10:00:00+00:00</published><updated>2026-06-04T10:00:00+00:00</updated><id>https://joeonit.github.io/gsoc/week-1-gr-cyberether-takes-shape</id><content type="html" xml:base="https://joeonit.github.io/gsoc/week1"><![CDATA[<p>This is the first weekly update on my Google Summer of Code project with GNU
Radio: <strong>gr-cyberether</strong>, an out-of-tree (OOT) module that brings CyberEther’s
GPU-accelerated visualization (Superluminal) into GNU Radio flowgraphs as
native sinks.</p>

<p>Week 1 was deliberately about scaffolding, not features: get the build chain right, prove that the headers and the linker work, set up CI, and get data out of GNU Radio into CyberEther (implementing a minimal <code class="language-plaintext highlighter-rouge">cyber_lineplot_sink</code>). Here’s how it went.</p>

<h2 id="building-against-cyberether">Building against CyberEther</h2>

<p>Starting from v1.4.0, CyberEther ships distributed binaries for all major
operating systems plus a Superluminal Python wheel. That sounds like it should
make the OOT build trivial: link the lib, ship the module. The catch is that
<strong>none of the distributed artifacts expose the C++ development surface an OOT
needs</strong>: the <code class="language-plaintext highlighter-rouge">&lt;jetstream/superluminal.hh&gt;</code> header, the rest of the
<code class="language-plaintext highlighter-rouge">jetstream/</code> include tree, and the <code class="language-plaintext highlighter-rouge">jetstream.pc</code> pkg-config file. The Python
wheel only helps Python consumers, and gr-cyberether is a C++ module.</p>

<p>So we have two paths: have the user build CyberEther from source so the
headers + <code class="language-plaintext highlighter-rouge">.pc</code> show up in their prefix, or vendor CyberEther as a git
submodule inside the OOT. For now I’m going with the first, and pinning a minimum version through pkg-config:</p>

<div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">set</span><span class="p">(</span>CYBERETHER_MIN_VERSION <span class="s2">"1.4.0"</span>
    CACHE STRING <span class="s2">"Minimum required CyberEther version"</span><span class="p">)</span>

<span class="nb">find_package</span><span class="p">(</span>PkgConfig REQUIRED<span class="p">)</span>
<span class="nf">pkg_check_modules</span><span class="p">(</span>CYBERETHER REQUIRED IMPORTED_TARGET GLOBAL
    <span class="s2">"jetstream&gt;=</span><span class="si">${</span><span class="nv">CYBERETHER_MIN_VERSION</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
</code></pre></div></div>

<p>In my last meeting with my mentors, Luigi said that in the coming weeks CyberEther would have a new major release, possibly with some minor API changes. As for the build, he’s still considering adding another release channel targeted at development, one that would expose the linkable libs and the <code class="language-plaintext highlighter-rouge">jetstream</code> headers. But for now, I’ll depend on building from source.</p>

<h2 id="the-null-sink-proving-the-chain-end-to-end">The null sink: proving the chain end-to-end</h2>

<p>Before writing any real block I wanted to prove the build chain works. So the
first thing in the repo is a <strong>null sink</strong>: one complex input, <code class="language-plaintext highlighter-rouge">work()</code>
returns immediately, no plotting. The point isn’t to do anything useful; it’s
to answer two questions:</p>

<ul>
  <li>Do the <code class="language-plaintext highlighter-rouge">&lt;jetstream/...&gt;</code> headers actually compile under C++20 inside a GR block?</li>
  <li>Does <code class="language-plaintext highlighter-rouge">libjetstream</code> actually link?</li>
</ul>

<p>The constructor does two cheap things as proof:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// compile-time proof the headers are visible</span>
<span class="n">JST_INFO</span><span class="p">(</span><span class="s">"[gr-cyberether] D0 build test: built against CyberEther v{}."</span><span class="p">,</span>
         <span class="n">JETSTREAM_VERSION_STR</span><span class="p">);</span>

<span class="c1">// link-time proof (this symbol must resolve against libjetstream)</span>
<span class="n">JST_LOG_SET_DEBUG_LEVEL</span><span class="p">(</span><span class="n">JST_LOG_DEBUG_DEFAULT_LEVEL</span><span class="p">);</span>
</code></pre></div></div>

<p>If it compiles and links, the CMake + pkg-config work above is correct and we
can move on to a block that actually does something.</p>

<h2 id="dual-branch-ci-310-and-main">Dual-branch CI: 3.10 and main</h2>

<p>GNU Radio is mid-stream between 3.10 and main, so the OOT has to keep working
on both. I added <code class="language-plaintext highlighter-rouge">.github/workflows/build.yml</code> so every PR builds against
<strong>both GR 3.10 and GR main</strong> before it can merge, as a matrix with
<code class="language-plaintext highlighter-rouge">fail-fast: false</code> so one leg breaking still shows the other.</p>

<p>CyberEther itself is built from source at the pinned v1.4.0 once per CI run
and cached; both GR legs reuse the cache. The two GR legs differ in setup:</p>

<ul>
  <li><strong>3.10</strong> ships in apt on Ubuntu 24.04, so it’s just <code class="language-plaintext highlighter-rouge">gnuradio-dev</code>.</li>
  <li><strong>main</strong> isn’t packaged, so it’s built from source and cached too.</li>
</ul>

<p>The last step is a sanity check that the version we pinned is the one
pkg-config actually picked up:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Confirm the pinned CyberEther version was found</span>
  <span class="na">run</span><span class="pi">:</span> <span class="s">pkg-config --modversion jetstream</span>
</code></pre></div></div>

<h2 id="the-line-plot-sink-first-working-prototype">The line plot sink: first working prototype</h2>

<p>With the chain proven, the first real block is the <strong>line plot sink</strong>. The
path is: complex stream in → ring buffer → a <code class="language-plaintext highlighter-rouge">CF32</code> Jetstream tensor →
registered with Superluminal as a time-domain <code class="language-plaintext highlighter-rouge">Line</code> plot showing the real
part of each sample.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Superluminal</span><span class="o">::</span><span class="n">Plot</span><span class="p">(</span><span class="n">d_name</span><span class="p">,</span> <span class="n">layout</span><span class="p">,</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">buffer</span>    <span class="o">=</span> <span class="n">d_tensor</span><span class="p">,</span>
    <span class="p">.</span><span class="n">type</span>      <span class="o">=</span> <span class="n">Superluminal</span><span class="o">::</span><span class="n">Type</span><span class="o">::</span><span class="n">Line</span><span class="p">,</span>
    <span class="p">.</span><span class="n">source</span>    <span class="o">=</span> <span class="n">Superluminal</span><span class="o">::</span><span class="n">Domain</span><span class="o">::</span><span class="n">Time</span><span class="p">,</span>
    <span class="p">.</span><span class="n">display</span>   <span class="o">=</span> <span class="n">Superluminal</span><span class="o">::</span><span class="n">Domain</span><span class="o">::</span><span class="n">Time</span><span class="p">,</span>
    <span class="p">.</span><span class="n">operation</span> <span class="o">=</span> <span class="n">Superluminal</span><span class="o">::</span><span class="n">Operation</span><span class="o">::</span><span class="n">Real</span><span class="p">,</span>
<span class="p">});</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">work()</code> is just a ring-buffer write into the tensor, no rendering happens
on the GR scheduler thread.</p>

<h3 id="lazy-init--the-teardown-crash">Lazy init — the teardown crash</h3>

<p>A GR block has two lives: construction and running. My first version did
<code class="language-plaintext highlighter-rouge">Superluminal::Initialize()</code> and <code class="language-plaintext highlighter-rouge">Plot()</code> in the constructor, so the whole
Metal instance booted the moment the block was instantiated. Problem is the
only thing that tears the Superluminal instance down is <code class="language-plaintext highlighter-rouge">Show()</code>, which only
runs from <code class="language-plaintext highlighter-rouge">present()</code>. A typical flowgraph does <code class="language-plaintext highlighter-rouge">start()</code> and <code class="language-plaintext highlighter-rouge">wait()</code> and
never calls <code class="language-plaintext highlighter-rouge">present()</code>, so the instance booted but <strong>never</strong> shut down.</p>

<p>Fix: make the whole Superluminal lifecycle <strong>lazy</strong>. The constructor now only
allocates the buffer. <code class="language-plaintext highlighter-rouge">Initialize</code>, <code class="language-plaintext highlighter-rouge">Plot</code>, and <code class="language-plaintext highlighter-rouge">Show</code> all move into
<code class="language-plaintext highlighter-rouge">present()</code>, gated on a first-call flag:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">present</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">d_initialized</span><span class="p">)</span> <span class="p">{</span>           <span class="c1">// first call only</span>
        <span class="n">Superluminal</span><span class="o">::</span><span class="n">Initialize</span><span class="p">();</span>
        <span class="n">Superluminal</span><span class="o">::</span><span class="n">Plot</span><span class="p">(...);</span>
        <span class="n">d_initialized</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">Superluminal</span><span class="o">::</span><span class="n">Show</span><span class="p">();</span>            <span class="c1">// opens window, runs loop, tears down on close</span>
<span class="p">}</span>
</code></pre></div></div>

<p>So if you never open a window, nothing boots and there’s nothing to crash
on exit. If you do open one, the same call that powers it on also powers it
off.</p>

<h2 id="whats-next">What’s next</h2>

<ul>
  <li><strong>Buffer model.</strong> The current ring-buffer write into a single persistent
tensor accepts a benign writer/reader race for D1. Week 2 starts the proper
buffer research: lock-free per-sink ring, what Superluminal expects, and
what the minimum-copy path actually looks like.</li>
  <li><strong>Threading model.</strong> D1 uses one <code class="language-plaintext highlighter-rouge">present()</code> per sink owning the main
thread. Week 2 sketches a shared <code class="language-plaintext highlighter-rouge">cyber_context</code> render thread so multiple
sinks can share one window/event loop, which is the realistic shape for any
flowgraph that wants more than one plot.</li>
  <li>Research the distribution plan for <code class="language-plaintext highlighter-rouge">gr-cyberether</code> as the mentor suggested.</li>
</ul>

<p>Repo: <a href="https://github.com/joeonit/gr-cyberether">gr-cyberether</a><br />
Mentor: <a href="https://github.com/luigifcruz">Luigi Cruz</a> (CyberEther). <a href="https://github.com/haakov">Håkon Vågsether</a> (GNU Radio).</p>]]></content><author><name></name></author><category term="gsoc" /><summary type="html"><![CDATA[This is the first weekly update on my Google Summer of Code project with GNU Radio: gr-cyberether, an out-of-tree (OOT) module that brings CyberEther’s GPU-accelerated visualization (Superluminal) into GNU Radio flowgraphs as native sinks.]]></summary></entry><entry><title type="html">Week 0: Introduction and Project Kick-off</title><link href="https://joeonit.github.io/gsoc/week0" rel="alternate" type="text/html" title="Week 0: Introduction and Project Kick-off" /><published>2026-05-28T10:00:00+00:00</published><updated>2026-05-28T10:00:00+00:00</updated><id>https://joeonit.github.io/gsoc/week-0-introduction-and-project-kickoff</id><content type="html" xml:base="https://joeonit.github.io/gsoc/week0"><![CDATA[<p>Welcome to my first post on the blog! I am incredibly excited to share that I will be spending this summer contributing to the GNU Radio community as part of Google Summer of Code 2026.</p>

<h3 id="the-project">The Project</h3>
<p>My accepted project is <a href="https://summerofcode.withgoogle.com/programs/2026/projects/wLfV60LS">Graphical interoperability between CyberEther and GNU Radio</a>.</p>

<p>The ultimate goal for the summer is to successfully integrate CyberEther’s GPU-accelerated visualizations into GNU Radio. To achieve this, I will be building an Out-Of-Tree (OOT) sink module that bridges the two ecosystems, providing high-performance graphical sinks for SDR applications.</p>

<h3 id="community-bonding--getting-up-to-speed">Community Bonding &amp; Getting Up to Speed</h3>
<p>I am relatively new to the RF and SDR world, but I have been learning a great deal over the past month. During the community bonding period, I have been meeting weekly with my mentors, Luigi Cruz and Håkon Vågsether. We have been outlining the project architecture and establishing the foundational knowledge I need to get started.</p>

<p>I am very grateful for their guidance so far and excited to be a part of this community.</p>

<h3 id="whats-next">What’s Next</h3>
<p>The official coding period starts today! I will be using this blog to document my weekly progress, technical challenges, and milestones throughout the summer.</p>

<p>I will be sharing my updates over at the GNU Radio Matrix channel as well, and I would be very happy to receive feedback, code reviews, or advice from the community as the module takes shape.</p>

<p>Stay tuned for the Week 1 update!</p>]]></content><author><name></name></author><category term="gsoc" /><summary type="html"><![CDATA[Welcome to my first post on the blog! I am incredibly excited to share that I will be spending this summer contributing to the GNU Radio community as part of Google Summer of Code 2026.]]></summary></entry></feed>