OK, isso foi fácil. Acontece que o banco de dados tem que preparar o plano de consulta antes de conhecer os parâmetros, o que leva a resultados ruins. A solução foi usar plpgsql e retornar QUERY EXECUTE. Agora o desempenho é o mesmo, como esperado.
CREATE OR REPLACE FUNCTION GetMyReport(IN fromdate timestamp without time zone, IN todate timestamp without time zone)
RETURNS TABLE(Name1 character varying, Name2 character varying) AS
$BODY$
BEGIN
RETURN QUERY EXECUTE'
select Column1 as Name1, Column2 as Name2
from sometable tbl
inner join ...
where ...
and ...
and $1 <= somedate
and $2 >= somedate
group by ...
order by ...;' USING $1, $2
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 10
ROWS 1000;